Where to use event aggregator in backbone marionette? - backbone.js

I want to implement a custom vent event aggregator instance with requirejs as explainer here
Looking at examples here and in the docs, I've seent that calls to vent.on and vent.trigger are mainly used in views. My pattern would then be:
define(['marionette', 'vent'], function (Marionette, vent) {
return Marionette.ItemView.extend({
initialize: function () {
//bind
this.listenTo(vent, 'mycustomevent', this.myMethod);
//trigger
vent.trigger('viewinit', ...);
}
});
});
Is this pattern correct (views are responsible for managing aggregator events) or should i use it on Models and Collections?

The event aggregator really is just a pub/sub system for communication.
Regarding "what should go where", I'd suggest the following in most cases:
views trigger events (according to what the user has clicked, e.g.)
controllers listen for and react to events (deleting a model, e.g.)
Of course, there are many ways to use the event aggregator, but when dealing with views, the above method fits most use cases.
Using the event aggregator is also useful to manage routing events and remove duplication (see section "Implementing routing" here: http://samples.leanpub.com/marionette-gentle-introduction-sample.pdf )

Related

Backbone best practices where to fetch models/collections

I've inherited a codebase that follows the format of: a router sets a controller, the controller fetches the collection/model needed, then the controller set/passes the view the collection/model.
The current View I'm working on loads a collection, and now I need to build in a feature where I fetch a single model after the view has rendered, based on an id clicked (note the model is from a different collection).
I only want to load the model when/if they click a button. So my question is, can I setup the model/fetch in the View, or should I be doing that in the controller? Is there a backbone best practice when adopting a controller/view setup like this?
I primarily ask because it seems easier for me to add this new feature right in the View. But is that a bad practice? I thought so, so I started down the path of triggering an event in the View for the controller to the fetch the model, and then somehow pass that back to the View (but I'm not sure really how to even do that)...it seems like a lot of unnecessary hoop jumping?
Its OK to fetch collection via views. As 'plain' backbone does not Controller, View in charge of it responsibilities.
But imho fetch collections via controller is better practice, its easier to scale and support and test.
The only difficulty is to set communication between Controller and View context event. One of the approach is trigger Message Bus event on context event like
events: {
'click .some': function() {
this.trigger('someHappend', { some: data })
}
}
and listen to this event in Controller
this.on(someViewInstance, 'someHappend', function() {
// do something in controller
})
If you already inherited code with structure you described you'd better follow it. Also you might be interested in MarionetteJS as significant improvement. Also highly recommend you to checkout BackboneRails, screencasts not free but very usefull, especially in large scale app maintain

How to make one view aware of another's changes?

Suppose you are making a music library app.
You have one view with a list on genres and another that shows the contents of the selected genre. When the user clicks a genre on the list, the contents in the other view should be updated accordingly.
What is the best way to do this so that there are minimal dependencies?
I have not found any other place to listen to the mouse clicks than the view that draws the individual genre. I can send an event from there, but what is the optimal way of getting that event to update the other view which draws the genre contents? Who should listen to that event, the genre content view or its collection?
EDIT: I instantiate both views from the router of the app and I did manage to get this to work by making the views aware of each other, but that's not optimal of course.
You could make a simple model to hold the application state, you don't need anything fancy, just a bag of data that implements the usual Backbone event methods:
var AppState = Backbone.Model.extend({});
var app_state = new AppState();
Then the genre list view would listen for click events (as you already have) and set the current genre on the app-state model when someone changes it:
var Genres = Backbone.View.extend({
//...
choose: function(ev) {
// This would be the click handler for the genre,
// `.html()` is just for demonstration purposes, you'd
// probably use a data attribute in real life.
app_state.set({genre: $(ev.target).html() });
},
});
The view for the individual genre would listen for "change:genre" events on the app-state model and react as the genre changes:
var Genre = Backbone.View.extend({
initialize: function() {
_.bindAll(this, 'change_genre');
app_state.on('change:genre', this.change_genre);
},
//...
change_genre: function() {
// Refill the genre display...
}
});
Demo: http://jsfiddle.net/ambiguous/mwBKm/1/
You can make models for any data you want and models are a convenient way of working with data events in Backbone. As an added bonus, this approach makes it fairly easy to persist your application's state: just add the usual Backbone persistence support to AppState and away you go.
If you only need a simple event bus to push non-data events around, you can use Backbone's Events methods to build a simple event aggregator:
app.events = _.extend({}, Backbone.Events);
Then, assuming you have a global app namespace, you can say things like this:
app.events.on('some-event', some_function);
and
app.events.trigger('some-event', arg1, arg2, ...);
The best way I have seen is in the method proposed by Derick Bailey. In short you can create an event aggregator, which provides a centralized object for raising events to which different views can subscribe. The beauty of this solution is that it is very simple to implement as it makes use of Backbone's existing event system.

Backbone's preferred way of triggering navigate on link clicks?

Is there a preferred way to trigger Router.navigate in Backbone when a user clicks a link?
For instance a template might have a link Log Out. Is the preferred approach really to use a custom class and attach a click handler to that class in the view? This seems to generate tons of duplicate code so I'm looking for a better way.
The way to handle this kind of thing is to extend the Backbone objects ( in this case the View). You'll notice in the Backbone docs that this is encouraged, and necessary given its minimalist code base. I'd recommend checking out Marionette (on Github) and the site Backbone patterns for good ways to extend the Backbone core.
For example, maybe you extend View with a method that wires up the navigation handlers as you like:
Backbone.View.prototype.wireupNavs = function() {
var that = this;
this.$el.find("a[role=nav]").each(function() {
var target = $(this).attr('href');
that.bind("click", router.navigate(target);
});
}
Then any a tag you want as a Backbone nav element, you'd just decorate with the role="nav" attribute; and call this.wireupNavs() in the initializer function of the appropriate views.

Backbone.js nested views: reference or events?

What is more efficient, using events to communicate between nested Views, or keeping a reference around to call a method on. The following example shows two Views. The outer View responds to a click event, and could then use either an event, or method call to get the InnerView to respond appropriately.
InnerView = Backbone.View.extend({
initialize: function() {
this.model.bind('doSomethingEvent', this.doSomething);
},
doSomething: function() {
// This could have been called from event/trigger
// or from direction method invocation using reference.
}
});
OuterView = Backbone.View.extend({
events = {
'click' : 'handleOutViewClick'
},
render: function() {
// Create InnerView to render some model
var innerView = new InnerView({model:this.model });
$(this.el).append(innerView.render().el);
// Could store a reference to the View?
this.viewRef = innerView;
},
handleOutViewClick: function(e) {
// Should this function use a reference to the InnerView instance:
this.viewRef.doSomething();
// Or should it trigger an event on this.model that
// the InnerView is bound to?
this.someCollection.trigger('doSomethingEvent');
}
});
Probably a single method call is going to be more efficient than an event dispatch, which will involve at least 2 method calls. But I don't think you need to be concerned about which is more "efficient" here technically. Unless this is happening many times a second, you can concern yourself only with what makes for cleaner, more correct code. I think the cleanest pattern depends on the details being communicated. Here's my preference:
If it is a natural fit for outerview to manipulate models and collections, and have innerview respond via the normal backbone model/collection events, that is the cleanest.
If what's happening isn't really about the models, consider a "View Model" pattern where you model the interesting bits of state of the view as if it was a back end model, even though you have no intention of ever having that model interact with the server. Then bind your two views to events coming off the view model and have them coordinate by altering a common "view model" instance. This is a pattern I use for complicated views with lots of interdependent state not directly associated with the underlying models from the server.
If what's happening doesn't really change the models/collections and is more of a view-specific thing, a direct method dispatch will be more straightforward but also more tightly coupled. It's up to your judgement to decide when the loose coupling afforded by event dispatch merits the extra complexity and harder-to-follow control flow.

Backbone boilerplate Events

What is the best way to bind events to a Backbone boilerplate application? I've been trying to bind my events directly to the models associated with my views, in my views, but it doesn't seem to be working. I see within 'namespace.js', that there is an app key that extends Backbone.Events like so:
// Keep active application instances namespaced under an app object.
app: _.extend({}, Backbone.Events)
I don't fully understand how to use it...
I was able to get things working without the boilerplate, but it does provide some very cool functionality, so I'd love to be able to use it. Thanks!
ADDED
the code I was using was with the underscore bind method like so:
this.module.bind('change', this.render);
But then, I realized that 'this.model' is returning undefined, and so this doesn't work. I really am not sure how the boilerplate wants me to reference my model from the view.
I'm not sure if it is a typo that you copied from your code or a typo you only entered here, but I believe this.module (which IS undefined) should be this.model, which you also must be sure to pass in when you instantiate your view, of course, as so:
myView = new BBView({model: myModel});
then you can say this.model.bind('change', this.render); or this.model.on('change', this.render); for the most recent version of Backbone
I frequently bind my views to change events on my models in this way.
As for extending Backbone.Events, the way I have used it is to create an independent "event aggregator" that will help connect events between different views on your page. Let's say for example you have some action on one view that needs to set off an action on another view. In this case, you can pass your event aggregator object as an option to each of your views when you instantiate them, so that they can both trigger events or bind to events on a common object (i.e. your event aggregator).
whatsUp = _.extend({}, Backbone.Events) // the aggregator
myFirstView = new FirstBBView ({whatsUp: whatsUp});
(the aggregator shows up as this.options.whatsUp inside the view)
mySecondView = new SecondBBView2 ({whatsUp: whatsUp});
inside FirstBBView:
this.options.whatsUp.bind('specialEvent', function(arg1,arg2) {
// do stuff
});
inside SecondBBView, when something important happens:
this.options.whatsUp.trigger('specialEvent', {arg1: 'some data', arg2: 'more data'});
For a great explanation, see this great article by Derick Bailey

Resources