backbone layout manager: how to remove child views - backbone.js

How to clean up the children view when the parent view is removed. There are some cleanup activities involved in the child view. when I call the remove parent view there is no hook to remove the child view.

add the close function to your parent view (it: it was good if all children extends parent view or all your views extends a gad view, and the close function is inside the gad view )
close: function() {
var err, i, view, _ref1;
try {
_ref1 = this.childViews;
for (i in _ref1) {
view = _ref1[i];
if (typeof view.close === "function") {
view.close();
}
}
this.unbind();
this.stopListening();
this.undelegateEvents();
return this.$el.empty();
} catch (_error) {
err = _error;
throw new Error(err);
}
}

Related

Marionette on adding an item to collection event is firing twice

I'm using Marionette 2.4 and have a layoutView which is listening to an event in the childView. When the event fires I search for an existing model within the collection and if it is not there I create a new model and add it to the collection. If it is found I remove the model from the collection. The problem is that the event seems to be firing twice. The first time it fires, it will create the model, but then as it is firing twice, it then finds the newly created model in the collection and then removes it.
var layout = Marionette.LayoutView.extend({
childEvents: {
'channel:selected': 'onChildviewChannelSelected'
},
onChildviewChannelSelected: function (childView, args) {
var linkCollection = this.getRegion('regionWithCollectionView').currentView.collection;
var modelToUpdate = linkCollection.where({channel: args.currentTarget.value});
if(modelToUpdate) {
this.removeModel(linkCollection, modelToUpdate);
} else {
this.addModel(linkCollection, args.currentTarget.value);
}
},
removeModel: function (collection, model) {
collection.remove(model);
},
addModel: function (collection, channel) {
var newEntity = new MyApp.Entities.Link();
newEntity.set('channel', channel);
collection.add(newEntity);
}
});
and here is the child view that fires the 'channel:selected' event....
var childView = Marionette.ItemView.extend({
events: {
'change input[type="checkbox"]': 'channelSelected'
},
channelSelected: function(args) {
this.triggerMethod('channel:selected', args);
}
});
Any idea why the childView fires the 'channel:selected' event twice?
It isn't the view that holds the collection that is being added to, but perhaps there is something that happens when a collection is added to that it will trigger the event again for some reason.
It looks like your function is getting fired twice because of Marionette's "childview* event bubbling". From the documentation:
When a child view within a collection view triggers an event, that
event will bubble up through the parent collection view with
"childview:" prepended to the event name.
That is, if a child view triggers "do:something", the parent
collection view will then trigger "childview:do:something".
This means that "childview:channel:selected" is already being triggered on your layoutview (which means that the onChildviewChannelSelected function is automatically executed on the parent view if it exists http://marionettejs.com/docs/v2.4.7/marionette.functions.html#marionettetriggermethod).
It seems there are a couple potential workarounds. 1 - don't specify a childEvents handler if your handler/function name follows Marionette conventions.
var LayoutView = Marionette.LayoutView.extend({
template: false,
el: '.container',
regions: {
'regionWithCollectionView': '.collection-view-container'
},
onChildviewChannelSelected: function (childView, args) {
console.log("layoutview::channelSelected - child " + childView.model.get('channel') + " selected");
}
});
Fiddle showing workaround #1: https://jsfiddle.net/kjftf919/
2 - Rename your LayoutView's childview function handler to something that doesn't conflict with Marionette's automatic event bubbling.
var LayoutView = Marionette.LayoutView.extend({
template: false,
el: '.container',
regions: {
'regionWithCollectionView': '.collection-view-container'
},
childEvents: {
'channel:selected':'channelSelected'
},
channelSelected: function (childView, args) {
console.log("layoutview::channelSelected - child " + childView.model.get('channel') + " selected");
}
});
Fiddle showing workaround #2: https://jsfiddle.net/kac0rw6j/

Marionette prevent region destroy

I am using Marionette region to display templates based on user radio input:(text/file).
Here is my itemview
var fileTemplateView = Marionette.ItemView.extend({
template : "#file-upload-template"
});
and region defined as
regions : {
composeRegion : "#compose-region",
}
and event declared as
events : {
"click #msg-input-type input:radio" : "changedRadio"
}
and event trigger function is
changedRadio : function(evt) {
var self = this;
var checkedObject = evt.currentTarget;
console.log('Radio Change Event'+checkedObject.value);
if (checkedObject.value === "file") {
if (self.fileView === undefined) {
self.fileView = new fileTemplateView();
}
this.composeRegion.show(self.fileView, { preventDestroy: true });
} else if (checkedObject.value === "text") {
if (self.textView === undefined) {
self.textView = new textTemplateView();
}
this.composeRegion.show(self.textView, { preventDestroy: true });
}
But preventDestroy method may not be working as defined where template is resetting on everytime radio event happen.
Your help is appreciated.
The preventDestroy option prevents the swapped view from being destroyed. This doesn't mean that is won't be re-rendered the next time it is shown. Make sure you are saving the state of the view so it can be used to reconstruct the view properly the next time.

How to access user defined methods of widget parent

I have a parent widget which extends Window class (qx.ui.window.Window) and this window now has couple of children (I have created the children by overriding childControlImpl).
Now I would like to access my methods in Parent class from one of the child classes. I don't want to create an object to call the methods, instead I would like to use getLayoutParent method to do this.
But when I can call getLayoutParent method from the child class, all I can access are the built-in methods, but I can't access any methods which I have created.
How can I get to do this ?
code Sample:
qx.Class.define("project.WrkAttrWindow",{
extend : qx.ui.window.Window,
construct: function() {
this.base(arguments);
this.__table = this._createChildControl("table");
},
members: {
__table:null
_createChildControlImpl : function(id)
{
var control;
switch(id)
{
case "table":
control = new project.WrkAttrTable();
this.add(control);
break;
}
return control || this.base(arguments, id);
},
getPrjId:function() {
console.log(I want to call this function);
}
});
Child Widget
qx.Class.define("project.WrkAttrTable",{
extend: qx.ui.table.Table,
statics: {
colKeys:["id","name","description"]
},
construct: function() {
this.base(arguments);
//some code here
},
members:
{
//call parent method from here
this.getLayoutParent().getPrjId(); // does not work
}
});
Despite the cross-post on Nabble, here is the gist from the answer:
In _createChildControlImpl, use this._add instead of this.add.

Events not firing after extending a custom backbone view

What's the correct way to have both events from the parent and child views be registered properly and fire?
With this approach the praent's events events wipe out the child's. I've also tried to pass in the child's events as part of the options to the parent, and then have the parent extend them before registering but then the parent's events no longer work.
Parent
// this is helpers/authorization/views/authHelper
export class AuthView extends Backbone.View {
constructor(options?) {
this.events = {
'keypress #auth': 'setAuthorizationCodeKeypress',
'click .create': 'setAuthorizationCode'
};
super(options);
}
}
Child
import AV = module("helpers/authorization/views/authHelper")
export class PageHelperView extends AV.AuthView {
constructor(options?) {
this.events = {
'click .configHead': 'toggle'
}
super(options);
}
}
I'd prefer them to share the same element and only require a call to new EHV.EncoderAPIHelperView().render(); to render them.
NOTE: edited with probably better answer
You can declare parent events directly inside the object, by doing that, you won't have to create new constructor. Parent view would look like this:
export class AuthView extends Backbone.View {
events = {
'keypress #auth': 'setAuthorizationCodeKeypress',
'click .create': 'setAuthorizationCode'
}
}
Now you can rewrite child to this:
import AV = module("helpers/authorization/views/authHelper")
export class PageHelperView extends AV.AuthView {
initialize(options?) {
this.events = {
'click .configHead': 'toggle'
}
}
}
_.extend call add missing entries to events and replace entries that share keys. (see more here)
Also, I'm not really great with typescript, so there might be a problem or two with this code.
The complete working solution:
Parent view:
export class AuthView extends Backbone.View {
constructor(options?) {
this.events = {
'keypress #auth': 'setAuthorizationCodeKeypress',
'click .create': 'setAuthorizationCode'
};
super(options);
}
}
Child view:
import AV = module("helpers/authorization/views/authHelper")
export class PageHelperView extends AV.AuthView {
constructor(options?) {
super(options);
}
initialize(options) {
this.events = _.extend(this.events, {
'click .configHead': 'toggle'
});
}
}

Find a Backbone.js View if you know the Model?

Given a page that uses Backbone.js to have a Collection tied to a View (RowsView, creates a <ul>) which creates sub Views (RowView, creates <li>) for each Model in the collection, I've got an issue setting up inline editing for those models in the collection.
I created an edit() method on the RowView view that replaces the li contents with a text box, and if the user presses tab while in that text box, I'd like to trigger the edit() method of the next View in the list.
I can get the model of the next model in the collection:
// within a RowView 'keydown' event handler
var myIndex = this.model.collection.indexOf(this.model);
var nextModel = this.model.collection.at(myIndex+1);
But the question is, how to find the View that is attached to that Model. The parent RowsView View doesn't keep a reference to all the children Views; it's render() method is just:
this.$el.html(''); // Clear
this.model.each(function (model) {
this.$el.append(new RowView({ model:model} ).render().el);
}, this);
Do I need to rewrite it to keep a separate array of pointers to all the RowViews it has under it? Or is there a clever way to find the View that's got a known Model attached to it?
Here's a jsFiddle of the whole problem: http://jsfiddle.net/midnightlightning/G4NeJ/
It is not elegant to store a reference to the View in your model, however you could link a View with a Model with events, do this:
// within a RowView 'keydown' event handler
var myIndex = this.model.collection.indexOf(this.model);
var nextModel = this.model.collection.at(myIndex+1);
nextModel.trigger('prepareEdit');
In RowView listen to the event prepareEdit and in that listener call edit(), something like this:
this.model.on('prepareEdit', this.edit);
I'd say that your RowsView should keep track of its component RowViews. The individual RowViews really are parts of the RowsView and it makes sense that a view should keep track of its parts.
So, your RowsView would have a render method sort of like this:
render: function() {
this.child_views = this.collection.map(function(m) {
var v = new RowView({ model: m });
this.$el.append(v.render().el);
return v;
}, this);
return this;
}
Then you just need a way to convert a Tab to an index in this.child_views.
One way is to use events, Backbone views have Backbone.Events mixed in so views can trigger events on themselves and other things can listen to those events. In your RowView you could have this:
events: {
'keydown input': 'tab_next'
},
tab_next: function(e) {
if(e.keyCode != 9)
return true;
this.trigger('tab-next', this);
return false;
}
and your RowsView would v.on('tab-next', this.edit_next); in the this.collection.map and you could have an edit_next sort like this:
edit_next: function(v) {
var i = this.collection.indexOf(v.model) + 1;
if(i >= this.collection.length)
i = 0;
this.child_views[i].enter_edit_mode(); // This method enables the <input>
}
Demo: http://jsfiddle.net/ambiguous/WeCRW/
A variant on this would be to add a reference to the RowsView to the RowViews and then tab_next could directly call this.parent_view.edit_next().
Another option is to put the keydown handler inside RowsView. This adds a bit of coupling between the RowView and RowsView but that's probably not a big problem in this case but it is a bit uglier than the event solution:
var RowsView = Backbone.View.extend({
//...
events: {
'keydown input': 'tab_next'
},
render: function() {
this.child_views = this.collection.map(function(m, i) {
var v = new RowView({ model: m });
this.$el.append(v.render().el);
v.$el.data('model-index', i); // You could look at the siblings instead...
return v;
}, this);
return this;
},
tab_next: function(e) {
if(e.keyCode != 9)
return true;
var i = $(e.target).closest('li').data('model-index') + 1;
if(i >= this.collection.length)
i = 0;
this.child_views[i].enter_edit_mode();
return false;
}
});
Demo: http://jsfiddle.net/ambiguous/ZnxZv/

Resources