Backbone.js: bind/unbind events across views - backbone.js

I am using the event aggregator pattern by Derick Bailey where he has illustrated the pattern in which an object manages the raising of events and the subscribers for those events.
It was all working fine where I was triggering events in one view and subscribing to them in other. The problem came when two or more views subscribe to an event and then at the time of discarding a view, one of the view unsubscribes from the event. This causes all the other views to be unsubscribed from the event as well.
Is there some workaround for this?
Update
Here is the a little bit of code that I'm using in my view:
var EventAggregator = _.extend({}, Backbone.Events);
new MyView({
collection: MyCollection,
eventagg: EventAggregator
});
MyView = Backbone.View.extend({
initialize: function() {
_.bindAll(this, 'render', 'close', 'actionFnc');
this.childviews = [];
this.options.eventagg.bind('evt:action', this.actionFnc);
this.render();
},
render: function() {
},
close: function() {
_(this.childViews).each(function(childview) {
childview.close();
});
$(this.el).empty();
this.options.eventagg.unbind('evt:action');
},
actionFnc: function() {
// do something over here
}
});

change the following line:
this.options.eventagg.unbind('evt:action');
to
this.options.eventagg.unbind('evt:action', this.actionFnc);

Related

Is there a way to make a Marionette View listen for events on itself via the events hash?

I'm having trouble figuring out how to quickly, and simply attach a listener for events triggered on a Backbone.Marionette view.
I can currently accomplish what I'm looking for by adding a listener via .on, but is there a quick way via the events or triggers hashes? Something like this seems like it should work but doesn't:
return Marionette.ItemView.extend({
triggers: {
"click .close": "menu:close"
},
events: {
"menu:close #": "close",
},
close: {
// do stuff
}
}
Update
There is actually a (simple) way to do exactly what you want.
// Itemview
var itemView = Marionette.ItemView.extend({
initialize: function() {
Marionette.bindEntityEvents(this, this, this.events);
},
template: "#item",
triggers: {
"click .btn": "menu:performAction"
},
events: {
"menu:performAction": "performAction"
},
performAction: function() {
console.log('test');
}
});
In short this binds your events attribute containing the hashes to the views events.
Fiddle: http://jsfiddle.net/8T68P/
Documentation: https://github.com/marionettejs/backbone.marionette/blob/master/docs/marionette.functions.md#marionettebindentityevents
Old answer
See this fiddle: http://jsfiddle.net/Cardiff/K5TTQ/
Listening to events like that won't work indeed. And if you happen to use the .on method from within your view. Please use listenTo. That will be cleaned up properly when the view is closed. Like this:
// Itemview
var itemView = Marionette.ItemView.extend({
initialize: function() {
var view = this;
view.listenTo(view, "menu:performAction", view.performActionFromListenTo);
},
template: "#item",
triggers: {
"click .btn": "menu:performAction"
},
performActionFromListenTo: function() {
console.log('test');
}
});

Bind a view to collections in backbone.js

I'm semi-new to backbone. I'm trying to bind a collection to a view so that when a new model is added to a collection, the view is updated. I think when you do this with models you can bind to the model's change event. But how do you do the same with collections?
App.Views.Hotels = Backbone.View.extend({
tagName: 'ul',
render: function() {
this.collection.each(this.addOne, this);
var floorplanView = new App.Views.Floorplans({collection:floorplanCollection});
$('.floorplans').html(floorplanView.render().el);
return this;
},
events: {'click': 'addfloorplan'},
addOne: function(hotel) {
var hotelView = new App.Views.Hotel ({model:hotel});
this.$el.append(hotelView.render().el);
},
addfloorplan: function() {
floorplanCollection.add({"name": "another floorplan"});
}
});
App.Collections.Floorplans = Backbone.Collection.extend({
model: App.Models.Floorplan,
initialize: function () {
this.bind( "add", function() {console.log("added");} );
}
});
The click event fires and adds to the collection. But how do I get it to update the view?
You can listen to the collection's add event, which fires when a new item is added to the collection. In modern versions of Backbone, the method listenTo is preferred to bind or on for listening to events. (Read de documentation for more info)
For example, in your case this should do the trick:
App.Views.Hotels = Backbone.View.extend({
initialize: function() {
this.listenTo(this.collection,'add', this.addOne);
},
//rest of code
Hope this helps!
Here is a nice tutorial I had followed long ago.
An Intro to Backbone.js: Part 3 – Binding a Collection to a View
It helps you define a DonutCollectionView that will, when given a collection of donuts, render an UpdatingDonutView for each donut.

Right way for wiring backbone views

I have a two views:
1 LeftView (maximized when RightView is minimized & vice versa)
2 RightView (containing)
- collection of
- RightItemView (rendering RightItemModel)
When RightView is maximized and the user clicks a RightItemView, I want to maximize LeftView and display something according to the data from the clicked RightItemView.
What's the proper way to wire them?
I would recommend using the Backbone.Events module:
http://backbonejs.org/#Events
Basically, this line is all it takes to create your event dispatcher:
var dispatcher = _.clone(Backbone.Events);
Then all of your views can trigger/listen for events using the global dispatcher.
So, in RightItemView you would do something like this in the click event:
dispatcher.trigger('rightItemClick', data); // data is whatever you need the LeftView to know
Then, in LeftView's initialize function, you can listen for the event and call your relevant function:
dispatcher.on('rightItemClick', this.maximizeAndDisplayData);
Assuming your LeftView would have a function like so:
maximizeAndDisplayData: function(data) {
// do whatever you need to here
// data is what you passed with the event
}
The solution #jordanj77 mentioned is definitely one of the correct ways to achieve your requirement. Just out of curiosity, I thought of another way to achieve the same effect. Instead of using a separate EventDispatcher to communicate between the two views, why shouldn't we use the underlying model as our EventDispatcher? Let's try to think in those lines.
To start with, add a new boolean attribute to the RightItem model called current and default it to false. Whenever, the user selects the RightItemView, set the model's current attribute to true. This will trigger a change:current event on the model.
var RightItem = Backbone.Model.extend({
defaults: {
current: false,
}
});
var RightItemView = Backbone.View.extend({
events: {
'click li': 'changeCurrent'
}
changeCurrent: function() {
this.model.set('current', true);
}
});
On the other side, the LeftView will be handed a Backbone.Collection of RightItem models during creation time. You would anyways have this instance to supply the RightView isn't it? In its initialize method, the LeftView will listen for change:current event. When the event occurs, LeftView will change the current attribute of the model it is currently displaying to false and start displaying the new model that triggered this event.
var LeftView = Backbone.View.extend({
initialize: function() {
this.collection.on('change:current', this.render, this);
},
render: function(model) {
// Avoid events triggered when resetting model to false
if(model.get('current') === true) {
// Reset the currently displayed model
if (this.model) {
this.model.set('current') = false;
}
// Set the currently selected model to the view
this.model = model;
// Display the view for the current model
}
}
});
var leftView = new LeftView({
// Use the collection that you may have given the RightView anyways
collection: rightItemCollection
});
This way, we get to use the underlying model as the means of communication between the Left and Right Views instead of using an EventDispatcher to broker for us.
The solution given by #Ganeshji inspired me to make a live example
I've created 2 views for this.
var RightView = Backbone.View.extend({
el: $('.right_view'),
template: _.template('<p>Right View</p>'),
renderTemplate: function () {
this.$el.html('');
this.$el.append(this.template());
this.$link = this.$el.append('Item to view').children('#left_view_max');
},
events: {
'click #left_view_max' : 'maxLeftView'
},
maxLeftView: function () {
//triggering the event for the leftView
lView.trigger('displayDataInLeftView', this.$link.attr('title'));
},
initialize: function (options) {
this.renderTemplate();
}
});
var LeftView = Backbone.View.extend({
el: $('.left_view'),
template: _.template('<p>Left View</p>'),
renderTemplate: function () {
this.$el.html('');
this.$el.append(this.template());
},
displayDataInLeftView: function (data) {
this.$el.append('<p>' + data + '</p>');
},
initialize: function (options) {
//set the trigger callback
this.on('displayDataInLeftView', this.displayDataInLeftView, this);
this.renderTemplate();
}
});
var lView = new LeftView();
var rView = new RightView();
Hope this helps.

Backbone custom event trigger not being recognised?

I'm learning Backbone.js for the first time and I'm having an issue trying to get a custom event from triggering (or from the View from recognising when it's been triggered)?
You can see my Collection code here: https://github.com/Integralist/Backbone-Playground/blob/master/Assets/Scripts/App/main.js#L72-86 which when initialized it triggers a custom collection:init event.
var Contacts = Backbone.Collection.extend({
model: Contact,
initialize: function(){
this.trigger('collection:init');
this.bind('add', this.model_added, this);
},
model_added: function(){
console.log('A new model has been created so trigger an event for the View to update the <select> menu');
}
});
But later on in my View where I'm listening for that event I can't get the function populate to fire: https://github.com/Integralist/Backbone-Playground/blob/master/Assets/Scripts/App/main.js#L90-107
var ContactsView = Backbone.View.extend({
initialize: function(){
console.log(contacts.models, 'get initial model data and populate the select menu?');
},
events: {
'collection:init': 'populate',
'change select': 'displaySelected'
},
populate: function(){
console.log('populate the <select> with initial Model data');
},
displaySelected: function (event) {
console.log('get model data and display selected user', event);
}
});
Any ideas what I'm doing wrong?
The events hash in a view is used to bind events from the DOM to your view, e.g. events raised by the elements in your rendered view. To listen to events raised by your collection, you will have to set them manually:
var ContactsView = Backbone.View.extend({
initialize: function(){
contacts.on("collection:init",this.populate,this);
}
...
});
Note that you are using a global contacts variable, I would advise to use Backbone mechanisms and pass your collection to the constructor, as you do with the el:
var ContactsView = Backbone.View.extend({
initialize: function(){
console.log(this.collection.models);
this.collection.on("collection:init",this.populate,this);
}
...
});
var contacts_view = new ContactsView({
el: $('#view-contacts'),
collection:contacts
});
As #mu said in the comments, as is, your event won't do anything since you trigger it in the initialize method of the collection, which is automatically called by the constructor of the collection therefore before you can bind anything in the view. See this Fiddle to visualize the call order : http://jsfiddle.net/yRuCN/
Trigger it elsewhere, or, if I read correctly your intent, you (probably) want to use the built in reset event:
var ContactsView = Backbone.View.extend({
initialize: function(){
this.collection.on("reset",this.populate,this);
}
...
});
See http://jsfiddle.net/yRuCN/1/ for an example with potential uses.

Event delegation failing to attach events in Backbone.js app

My Backbone.js app has a popup editor view that will be repeatedly closed and reopened as the user makes changes. I'm trying to figure out the cleanest way to implement this, and I'm stuck on an event delegation issue.
I believe the problem lies in the way I'm applying the template in my render method. I haven't had any issues with getting events to fire in other views, but those views differed in that they had a model. The view I'm having issues with is more of an application view that contains sub-views, so I'm not sure how to pass the view's context to the MyApp view.
Here's my code:
MyApp = Backbone.View.extend({
tagName: 'div',
template: _.template($('#app-template').html()),
initialize: function() {
_.bindAll(this);
this.render();
},
render: function() {
$('#container').html(this.template);
return this;
},
events: {
"click .save" : "onSaveClicked"
},
onSaveClicked: function () {
console.log("Save clicked.");
this.$el.remove();
}
});
$('#show').click(function () {
var myapp = new MyApp;
});
I've also posted it as a jsFiddle.
I stepped through the Backbone.js source, and it appears that render is called first, then events are assigned, which is what I'd expect. Everything looks OK from what I can tell, but onSaveClicked never fires when I click Save.
The desired functionality is that clicking Show displays the form and Save removes it. If there's a better way to do this that's more inline with Backbone's underlying philosophy I'm open to that as well. You'll notice that I'm nesting an unnamed div inside container, and that's because I wanted to maintain a consistent anchor point for my popup.
The events are bound to the view el, but you never append the el to the DOM. Try
MyApp = Backbone.View.extend({
tagName: 'div',
template: _.template($('#app-template').html()),
initialize: function() {
_.bindAll(this);
this.render();
},
render: function() {
this.$el.html(this.template);
return this;
},
events: {
"click .save" : "onSaveClicked"
},
onSaveClicked: function () {
console.log("Save clicked.");
this.$el.remove();
}
});
$('#show').click(function () {
var myapp = new MyApp;
$("#container").append(myapp.$el);
});
http://jsfiddle.net/WBPqk/18/
Note that in your Fiddle you bound the click event to .save where your template uses a done class.

Resources