Backbone: Multiple views subscribing to a single collection's event? Is this a bad practice?

daedelus_j picture daedelus_j · Apr 26, 2012 · Viewed 9k times · Source

I have a question, really basic stuff I think but:

I've only seen examples with a collection view and single view dependent on a collection being updated. What if you have multiple views trying to subscribe to a collections events, ie reset, addOne, addAll etc...

Am I missing some point about doing/not doing this? Do you have any examples of this? Does that even make sense?

Any info is MUCH appreciated

    var Coll = Backbone.Collection.extend({
        model: SingleModel,
        url: 'service',
        initialize: function(){
            console.log('collection inited')

        }        

    });

    var SingleModel = Backbone.Collection.extend({});            

    var CollView = Backbone.View.extend({                
        el: 'ul',
        template: Handlebars.compile(someContainerTemplate),
        init: function(){
            _.bindAll(this, 'render', 'addAll', 'addOne');
            this.collection.bind("reset", this.addAll);

            this.collection.fetch();
        },
        render: function(){
            $(this.el).append(this.template())
        },

        addAll: function(){
            this.collection.each(this.addOne);
        },

        addOne: function(model){
            var view = new SingleView({ model: model })            
        }
    })

    var SingleView = Backbone.View.extend({
        tagName: "li",
        events: {
            "click .delete": "remove"                    
        },
        template: Handlebars.compile(someTemplateForSingleItem),  
        initialize: function() {
        _.bindAll(this,'render');

            this.model.bind('save', this.addOne);
            this.model.bind('destroy', removeEl);
        },

        remove: function(){
          this.model.destroy();  
        },

        removeEl: function(){
          $(this.el).remove();  
        },

        render: function() {
            var context = this.model.toJSON();
            return $(this.el).append(this.template(context));
        },       
    })


    // standard so far (excluding any bad practices), 
    // but what if you have another view dependent on 
    // say the number of length of the collection, and 
    // you want it to update if any single models are destroyed

    var HeaderView = Backbone.View.extend({
        tagName: "div#header",
        template: Handlebars.compile(someHeaderTemplate), 
        initialize: function() {
        _.bindAll(this,'render');

        this.model.bind('save', this.addOne);
        },

        render: function() {
            //assigning this collection length

            var context = this.collection.length;
            return $(this.el).append(this.template(context));
        },      
    });        

    var coll = new Coll();
    new CollView({ collection: coll });
    new HeaderView({ collection: coll});

Answer

mu is too short picture mu is too short · Apr 26, 2012

What you're doing is perfectly fine and part of the reason for using Backbone. From the Backbone introduction:

Whenever a UI action causes an attribute of a model to change, the model triggers a "change" event; all the Views that display the model's state can be notified of the change,

Note that they say "all the Views", not "the view".

One example of multiple views for a single collection would be a chat system. Suppose you have a collection of users that are online; then you might have one simple view in the header that displays the number of people that are online and another view (of the same collection) that lists the users:

var User = Backbone.Model.extend({});
var OnlineUsers = Backbone.Collection.extend({
    model: User
});

var CounterView = Backbone.View.extend({
    tagName: 'span',
    initialize: function() {
        _.bindAll(this, 'render');
        this.collection.on('add', this.render);
        // other interesting events...
    },
    render: function() {
        this.$el.text(this.collection.size());
        return this;
    }
});
var ListView = Backbone.View.extend({
    initialize: function() {
        _.bindAll(this, 'render');
        this.collection.on('add', this.render);
        // other interesting events...
    },
    render: function() {
        var html = this.collection.map(function(m) {
            return '<p>' + m.get('name') + '</p>';
        });
        this.$el.html(html.join(''));
        return this;
    }
});

Then you'd have one OnlineUsers instance but both your CounterView and ListView instances would be watching it. When people come online or go offline, both views will be updated as desired.

Simple demo: http://jsfiddle.net/ambiguous/eX7gZ/

The above example situation sounds like exactly the sort of thing you're doing and it is exactly the sort of thing that Backbone is for. Good job.