3

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.

Sam Marland
  • 552
  • 3
  • 24
  • You haven't posted your parents view or your markup, so it's hard to say for sure, but if your using the same selector for your events hash then since the events are delegated to the root *el* and the parents views root el is also a parent to the children's *e*` it would be triggered as well. – Jack Jul 17 '15 at 13:32
  • @Jack I've just done some more reading and it appears that events on models bubble up to their parent's collection. This would cause the parent to re-render. Is it possible to stop event propagation? – Sam Marland Jul 17 '15 at 13:37
  • Not exactly, but in your event's callback you should be able to see what triggered the event and act accordingly. That said why is your collection view listening to the models *expand* event if you don't want to act on it? – Jack Jul 17 '15 at 13:42
  • @Jack the collection is now explicitly listening to model change event. Apparently a model change event will kick off its parent collection's change event. so I'm changing one property on a model which in turn is sending an event to say the collection has changed which in turn is re-rendering the parent view. http://stackoverflow.com/questions/9951222/backbone-events-on-model-firing-on-collection-double-firing – Sam Marland Jul 17 '15 at 13:45
  • Perhaps instead of listening to any models change event in your collection you should instead listen to the specific (or multiple specific) events your interested in (for example `this.listenTo(this.collection, 'change:someProperty',this.render);`) – Jack Jul 17 '15 at 13:52
  • @Jack that was it, I stopped listening to the collection's change event and I only listened to the sort event which is all I need for now. If you want to post an answer I'll accept it. – Sam Marland Jul 17 '15 at 13:54
  • OK I've also included the part how this might happen with DOM events sine in your question you aren't actually specific as to which event you are listening to in your collection's view. – Jack Jul 17 '15 at 14:06

1 Answers1

1

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.

Jack
  • 10,943
  • 13
  • 50
  • 65