I have a backbone page which behaves as follows.
Collection - > Models -> Views
In that I have a collection which contains search results of N length. Each of these models is tied to an instance of a view which in this case is a row of data being displayed.
I want to be able to toggle each row from their 'details' view to their 'advanced' view which contains more information. At the moment I have the parent view rendering N number of views for each model. I can toggled the change in state by updating the model and listening to the change event and then only re-rendering the part of the view I have clicked on. I noticed a problem whilst doing this. The problem is the viewport jump to the top of the page which isn't great UX.
Whilst debugging this I noticed something strange
The parent view's (page that holds the search results) render function is being called, which in turn is calling the render function of each of the rows. I think this is what's causing each of the child views to re-render.
Here are some code examples to demonstrate the problem:
// The child view's render and initialis
var SearchInviteRow = Backbone.View.extend({
tagName: "invite-section-result-row",
initialize: function(params){
this.template = templateHTML;
this.extendedTemplate = extendedTemplate;
this.listenTo(this.model, 'change', this.render);
},
events : {
"click .toggle-attachments" : "renderWithAttachments"
},
render: function(){
var view = this, render;
var that = this;
if(!this.model.get("expand")){
var rendered = Mustache.render(view.template, that.model.toJSON());
this.$el.html(rendered);
} else {
var rendered = Mustache.render(view.extendedTemplate, that.model.toJSON());
this.$el.html(rendered);
}
return this;
},
close: function(){
this.remove();
},
renderWithAttachments: function(){
if( !this.model.get("expand") ) {
this.model.set( {"expand" : true } );
this.model.getAttachments();
} else {
this.model.set( {"expand" : false } );
}
}
});
This is the part of the parent's render that iterates over the collection appending the rows to the search tiles.
for (var i = 0; i < this.collection.length; i++) {
view.subViewArray[i] = new SearchInviteRow({
model: this.collection.at(i)
});
this.$(".invite-section-inside").append(view.subViewArray[i].render().el);
}
What I can't work out is why the parent's render function is being called which is causing my other problems.
There are two ways this can happen, one is if you are listening to the backbone events on your collection the second is if it's a DOM event that's being triggered.
With the backbone events, if you look at the documentation you will see that events triggered on a model in a collection will also be triggered on the collection itself
Any event that is triggered on a model in a collection will also be triggered on the collection directly, for convenience. This allows you to listen for changes to specific attributes in any model in a collection, for example: documents.on("change:selected", ...)
That being the case, you should either check in the events callback whether you want to act on it (see what triggered it, perhaps passing in a extra flag when triggering the event ), or make sure you are only listening to the specific events you are interested in acting on. For example instead of listening to you collections generic change event you might want to listen to the more specific version of it (change:someProperty).
With DOM events, this can easily happen if you are listening to the same selector in your parents view as in your child's view, since the parent's root el is also a parent to the child's el.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With