8

I have a single page web app with multiple backbone.js views. The views must sometimes communicate with each other. Two examples:

  1. When there are two ways views presenting a collection in different ways simultaneously and a click on an item in one view must be relayed to the other view.
  2. When a user transitions to the next stage of the process and the first view passes data to the second.

To decouple the views as much as possible I currently use custom events to pass the data ($(document).trigger('customEvent', data)). Is there a better way to do this?

Johnny
  • 7,073
  • 9
  • 46
  • 72

4 Answers4

15

One widely used technique is extending the Backbone.Events -object to create your personal global events aggregator.

var vent = {}; // or App.vent depending how you want to do this
_.extend(vent, Backbone.Events);

Depending if you're using requirejs or something else, you might want to separate this into its own module or make it an attribute of your Application object. Now you can trigger and listen to events anywhere in your app.

// View1
vent.trigger('some_event', data1, data2, data3, ...);

// View2
vent.on('some_event', this.reaction_to_some_event);

This also allows you to use the event aggregator to communicate between models, collections, the router etc. Here is Martin Fowler's concept for the event aggregator (not in javascript). And here is a more backboney implementation and reflection on the subject more in the vein of Backbone.Marionette, but most of it is applicable to vanilla Backbone.

Hope this helped!

jakee
  • 18,486
  • 3
  • 37
  • 42
8

I agree with @jakee at first part

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

however, listening a global event with "on" may cause a memory leak and zombie view problem and that also causes multiple action handler calls etc.

Instead of "on", you should use "listenTo" in your view

 this.listenTo(vent, "someEvent", yourHandlerFunction);

thus, when you remove your view by view.remove(), this handler will be also removed, because handler is bound to your view.

When triggering your global event, just use

vent.trigger("someEvent",parameters);
omeralper
  • 9,804
  • 2
  • 19
  • 27
0

jakee's answer suggests a fine approach that I myself have used, but there is another interesting way, and that is to inject a reference to an object into each view instance, with the injected object in turn containing references to as many views as you want to aggregate.

In essence the view-aggregator is a sort of "App" object, and things beside views could be attached, e.g. collections. It does involve extending the view(s) and so might not be to everybody's taste, but on the other hand the extending serves as a simple example for doing so.

I used the code at http://arturadib.com/hello-backbonejs/docs/1.html as the basis for my ListView and then I got the following to work:

define(
    ['./listView'],

    function (ListView) {
        var APP = {
            VIEWS : {}
        }

        ListView.instantiator = ListView.extend({
            initialize : function() {
                this.app = APP;
                ListView.prototype.initialize.apply(this, arguments);
            }
        });

        APP.VIEWS.ListView = new ListView.instantiator();
        console.log(APP.VIEWS.ListView.app);
    }
);
Community
  • 1
  • 1
Dexygen
  • 12,287
  • 13
  • 80
  • 147
0

Views shouldn't communicate with each other. From the Backbone documentation:

A View is an atomic chunk of user interface. (https://backbonejs.org/#Model-View-separation)

Each View manages the rendering and user interaction within its own DOM element. If you're strict about not allowing views to reach outside of themselves, it helps keep your interface flexible — allowing views to be rendered in isolation in any place where they might be needed. (https://backbonejs.org/#View-rendering)

To keep the views atomic and self contained they should only communicate via a shared model (or models).

In example one from the question, the views could share a model with an attribute called 'clickedItem'. When an item is clicked in view1 it would set the 'clickedItem' attribute of the shared model and then view2 would be informed of this via backbone.

Similarly, in example two, you should pass the data to a shared model.