45
votes

In my simple project I have 2 views - a line item view (Brand) and App. I have attached function that allows selecting multiple items:

var BrandView = Backbone.View.extend({
...some code...
    toggle_select: function() {
        this.model.selected = !this.model.selected;
        if(this.model.selected) $(this.el).addClass('selected');
        else $(this.el).removeClass('selected');
        return this;
    }
});

var AppView = Backbone.View.extend({
...some code...
    delete_selected: function() {
        _.each(Brands.selected(), function(model){ 
            model.delete_selected();
        });
        return false;
    },
});

Thing is, I want to know how many items are selected. In this setup selecting is NOT affecting the model and thus not firing any events. And from MVC concept I understand that views should not be directly talking to other views. So how can AppView know that something is being selected in BrandViews?

And more specifically, I AppView to know how many items were selected, so if more than 1 is selected, I show a menu for multiple selection.

7
BTW, $(this.el).toggleClass("selected"). Or even shorter to this.model.selected = $(this.el).toggleClass('selected").hasClass("selected");.Mark Rushakoff

7 Answers

74
votes

You might want to have a read of this discussion of Backbone pub/sub events:

http://lostechies.com/derickbailey/2011/07/19/references-routing-and-the-event-aggregator-coordinating-views-in-backbone-js/

I like to add it in as a global event mechanism:

Backbone.pubSub = _.extend({}, Backbone.Events);

Then in one view you can trigger an event:

Backbone.pubSub.trigger('my-event', payload);

And in another you can listen:

Backbone.pubSub.on('my-event', this.onMyEvent, this);
7
votes

I use what Addy Osmani calls the mediator pattern http://addyosmani.com/largescalejavascript/#mediatorpattern. The whole article is well worth a read.

Basically it is an event manager that allows you to subscribe to and publish events. So your AppView would subscript to an event, i.e. 'selected'. Then the BrandView would publish the 'selected' event.

The reason I like this is it allows you to send events between views, without the views being directly bound together.

For Example

var mediator = new Mediator(); //LOOK AT THE LINK FOR IMPLEMENTATION

var BrandView = Backbone.View.extend({
    toggle_select: function() {
        ...
        mediator.publish('selected', any, data, you, want);
        return this;
    }
});

var AppView = Backbone.View.extend({
    initialize: function() {
        mediator.subscribe('selected', this.delete_selected)
    },

    delete_selected: function(any, data, you, want) {
        ... do something ...
    },
});

This way your app view doesn't care if it is a BrandView or FooView that publishes the 'selected' event, only that the event occured. As a result, I find it a maintainable way to manage events between parts of you application, not just views.

If you read further about the 'Facade', you can create a nice permissions structure. This would allow you to say only an 'AppView' can subscribe to my 'selected' event. I find this helpful as it makes it very clear where the events are being used.

1
votes

Ignoring the problems with this that you already mention in your post, you can bind and trigger events to/from the global Backbone.Event object, which will allow anything to talk to anything else. Definitely not the best solution, and if you have views chatting with one another then you should consider refactoring that. But there ya go! Hope this helps.

1
votes

Here is my case with a similar need: Backbone listenTo seemed like a solution to redirect to login page for timed out or not authenticated requests.

I added event handler to my router and made it listen to the global event such as:

Backbone.Router.extend({
    onNotAuthenticated:function(errMsg){
        var redirectView = new LoginView();
        redirectView.displayMessage(errMsg);
        this.loadView(redirectView);
    },
    initialize:function(){
        this.listenTo(Backbone,'auth:not-authenticated',this.onNotAuthenticated);  
    },
    .....
});

and in my jquery ajax error handler:

$(document).ajaxError(
    function(event, jqxhr, settings, thrownError){
        .......
        if(httpErrorHeaderValue==="some-value"){
             Backbone.trigger("auth:not-authenticated",errMsg);
        }
    });     
1
votes

You can use Backbone object as the event bus.

This approach is somewhat cleaner but still relies on Global Backbone object though

var view1 = Backbone.View.extend({

  _onEvent : function(){
    Backbone.trigger('customEvent');
  }

});


var view2 = Backbone.View.extend({

  initialize : function(){
    Backbone.on('customEvent', this._onCustomEvent, this);
  },

  _onCustomEvent : function(){
    // react to document edit.
  }

});
0
votes

Use the same model objects. AppView could be initialized with a collection, and BrandView initialized with one model from that collection. When attributes of a branch object change, any other code that has a reference to that model can read it.

So lets so you have some brands that you fetch via a collection:

var brands = new Brands([]);
brands.fetch();

Now you make an AppView, and an array of BrandView's for each model.

var appView = new AppView({brands: brands});
var brandViews = brands.map(function(brand) {
  return new BrandView({brand: brand});
});

The appView and the brandViews now both have access to the same model objects, so when you change one:

brands.get(0).selected = true;

Then it changes when accessed by the views that reference it as well.

console.log(appView.brands.get(0).selected); // true
console.log(brandViews[0].brand.selected)    // true
0
votes

Same as John has suggested above, the Mediator Pattern works really good in this scenario, as Addy Osmani summing this issue up again in Backbone fundamentals.

Wound up using the Backbone.Mediator plugin which is simple and great, and makes my AMD View modules working together seamlessly =)