backbone.js events and el - javascript

Okay, so I've read several other questions regarding Backbone views and events not being fired, however I'm still not getting it sadly. I been messing with Backbone for about a day, so I'm sure I'm missing something basic. Here's a jsfiddle with what I'm working with:
http://jsfiddle.net/siyegen/e7sNN/3/
(function($) {
var GridView = Backbone.View.extend({
tagName: 'div',
className: 'grid-view',
initialize: function() {
_.bindAll(this, 'render', 'okay');
},
events: {
'click .grid-view': 'okay'
},
okay: function() {
alert('moo');
},
render: function() {
$(this.el).text('Some Cow');
return this;
}
});
var AppView = Backbone.View.extend({
el: $('body'),
initialize: function() {
_.bindAll(this, 'render', 'buildGrid');
this.render();
},
events: {
'click button#buildGrid': 'buildGrid'
},
render: function() {
$(this.el).append($('<div>').addClass('gridApp'));
$(this.el).append('<button id="buildGrid">Build</button>');
},
buildGrid: function() {
var gridView = new GridView();
this.$('.gridApp').html(gridView.render().el);
}
});
var appView = new AppView();
})(jQuery);
The okay event on the GridView does not fire, I'm assuming because div.grid-view does not exist when the event is first bound. How should I handle the binding and firing of an event that's built on a view dynamically? (Also, it's a short example, but feel free to yell at me if I'm doing anything else that I shouldn't)

Your problem is that the events on GridView:
events: {
'click .grid-view': 'okay'
}
say:
when you click on a descendent that matches '.grid-view', call okay
The events are bound with this snippet from backbone.js:
if (selector === '') {
this.$el.on(eventName, method);
} else {
this.$el.on(eventName, selector, method);
}
So the .grid-view element has to be contained within your GridView's this.el and your this.el is <div class="grid-view">. If you change your events to this:
events: {
'click': 'okay'
}
you'll hear your cows (or "hear them in your mind" after reading the alert depending on how crazy this problem has made you).
Fixed fiddle: http://jsfiddle.net/ambiguous/5dhDW/

Related

How can I "deep extend" or "modularly extend" a class in Backbone / Marionette?

In Marionette, we have a "master view" that we would like to extend.
var PaginatedDropdown = Marionette.CompositeView.extend({
template: template,
events: {
'click': function () { return 'hello';},
'keyup': function () { return 'goodbye'}
},
childViewOptions: {
tagName: 'li'
}
});
The ideal use case would be extending this view, or class, by more specific views that would implement most of the features, and modify some of the features, of the master class / view:
var MotorcycleColorChooserDropdown = PaginatedDropdown.extend({
events: {
'mouseenter': function () { return 'vroom'; };
}
});
The problem is we're not sure how to selectively modify things such as the events hash, or override certain childview options. Specifically:
If MotorcycleColorChooserDropdown has an events object, it will override all of the events the PaginatedDropdown is listening for. How do we mix and match? (allow having an events object on MotorcycleColorChooserDropdown that combines itself with PaginatedDropdown's events object?
Potentially unsolvable: What if we want all the functionality of the PaginatedDropdown click event, but we also want to add to it a little bit in MotorcycleColorChooserDropdown? Do we just have to manually stick all the functionality from the Parent into the Child class?
We have considered simply not doing extended views like this, or doing things like MotorcycleColorChooserDropdown = PaginatedDropdown.extend(); and then one at a time doing MotorcycleColorChooserDropdown.events = PaginatedDropdown.events.extend({...}); but that seems messy, ugly, and I'm sure there's a better way.
Here is what i've been doing
var PaginatedDropdown = Marionette.CompositeView.extend({
template: template,
events: {
'click': 'onClick',
'keyup': function () { return 'goodbye'}
},
onClick:function(e){
return 'hello'
},
childViewOptions: {
tagName: 'li'
}
});
var MotorcycleColorChooserDropdown = PaginatedDropdown.extend({
events: _.extend({
'click': 'onClick',
'mouseenter': function () { return 'vroom'; };
},PaginatedDropdown.prototype.events),
onClick:function(e){
PaginatedDropdown.prototype.onClick.call(this,e)
//code
}
});
For your first question, i just extend the child events with the parent events.
As for the second, i just call the parent method from the child, passing in the child context and the event object.
It is quite verbose, but also very explicit. Someone reading your code will know exactly what's going on.
You could:
var PaginatedDropdown = Marionette.CompositeView.extend({
template: template,
childViewOptions: {
tagName: 'li'
},
"events": function() {
'click': 'onClick',
'keyup': 'onKeyUp'
},
"onClick": function() {
return 'hello';
},
"onKeyUp": function() {
return 'hello';
},
});
var MotorcycleColorChooserDropdown = PaginatedDropdown.extend({
"events" : function() {
//Question:
//Basically extending the first events by using the marionette event function and extending it.
var parentEvents = PaginatedDropdown.prototype.events,
events = _.extend({}, parentEvents);
events['mouseenter'] = this.onMouseEnter;
//add all of the events of the child
return events;
}
"onMouseEnter": function() {
return 'vroom';
},
"onClick": function() {
//Question 2:
//Applying the parent's method
PaginatedDropdown.prototype.onClick.apply(this, arguments);
//and adding new code here
}
});

Why does scroll event not trigger when rendering a template in a Backbone view?

In this code sample, a Backbone view is bound to a pre-existing DOM element. The scroll event triggers as expected.
In this alternate sample, the Backbone view renders the HTML instead of using a pre-existing DOM element. The scroll event doesn't fire.
Why?
The primary difference is the second sample does this:
this.$el.html(template);
This works:
http://jsfiddle.net/hKWR9/1/
$(function(){
var MyView = Backbone.View.extend({
className: 'scrollbox',
events: {
'click': 'onClick',
'scroll': 'onScroll'
},
initialize: function() {
this.render();
},
render: function() {
var template = '<div class="filler"></div>';
$('body').append(this.$el);
this.$el.html(template);
},
onClick: function() {
console.log('click');
},
onScroll: function() {
console.log("scroll");
}
});
var App = new MyView();
}());
Your fiddle doesn't work, because you defined your el with classname .scrollbox, while it should have been scrollbox. There doesnt seem to be a benefit in creating another 'scrollbox' within this '.scrollbox'.

backbone.js this context is lost in event functions

I'm kind of new to backbone and have a question. It seems like when an event in my view is fired, I lose the context to "this". How can I preserve this or get the original "this" for the model. Here is an example:
var MyListView = MyDBView.extend({
initialize: function(options){
},
render: function() {
//stuff here. I can access this.options here
},
dialogResponseYes: function(e){
//try to get this.options and options is undefined as this has changed to another element (a button)
}
});
So, how do I get the original context of this?
if you are using events object to bind events to your view like here http://backbonejs.org/#View-delegateEvents everything should be ok, otherwise u can hardly bind them to your view using _.bindAll
initialize: function(options){
_.bindAll(this, "dialogResponseYes");
},
or
var MyListView = MyDBView.extend({
initialize: function(options){
},
events: {
'click div': 'dialogResponseYes' //example
},
render: function() {
},
dialogResponseYes: function(e){
}
});

Marionette's CompositeView event not firing

I'm having trouble using Marionette's CompositeView. I render my model in my CompositeView using a template and want to add a click event to it. Somehow I can't get the events to work using the events: { "click": "function" } handler on the CompositeView... What am I doing wrong?
var FactsMenuItem = Backbone.Marionette.ItemView.extend({
template: tmpl['factsmenuitem'],
initialize: function() {
console.log('factsmenuitem');
},
onRender: function() {
console.log('factsmenuitem');
}
});
var FactsMenuView = Backbone.Marionette.CompositeView.extend({
template: tmpl['factsmenu'],
itemView: FactsMenuItem,
itemViewContainer: ".subs",
events: {
'click': 'blaat'
},
blaat: function() {
console.log('this is not working');
},
initialize: function() {
this.model.get('pages').on('sync', function () {
this.collection = this.model.get('pages');
this.render();
}, this);
},
onRender: function() {
console.log('render factsmenu');
}
});
var FactsLayout = Backbone.Marionette.Layout.extend({
template: tmpl['facts'],
regions: {
pages: ".pages",
filter: ".filter",
data: ".data"
},
initialize: function(options) {
this.currentPage = {};
this.factsMenu = new FactsMenu();
this.factsView = new FactsMenuView({model: this.factsMenu});
},
onRender: function() {
this.pages.show(this.factsView);
}
});
Edit:
I removed some code that made the question unclear...
The problem lies that the events of the non-collectionview of the compositeview (the modelView??) are not fired. I think this has something to do with the way the FactsLayoutView instantiates the compositeview...
The problem was caused by the way the region was rendered. In my FactsLayout is used this code:
initialize: function(options) {
this.currentPage = {};
this.factsMenu = new FactsMenu();
this.factsView = new FactsMenuView({model: this.factsMenu});
},
onRender: function() {
this.pages.show(this.factsView);
}
Apparently you can't show a view on a onRender function... I had to change the way the FactsLayout is initialized:
var layout = new FactsLayout({
slug: slug
});
layout.render();
var factsMenu = new FactsMenu({ slug: slug });
var factsView = new FactsMenuView({model: factsMenu});
layout.pages.show(factsView);
Maybe I did not understand your question well but if you need to listen an event fired from an item view within your composite view you should do like the following.
Within the item view test method.
this.trigger("test");
Within the composite view initialize method.
this.on("itemview:test", function() { });
Note that when an event is fired from an item of a CollectionView (a CompositeView is a CollectionView), it is prepended by itemview prefix.
Hope it helps.
Edit: Reading you question another time, I think this is not the correct answer but, about your question, I guess the click in the composite view is captured by the item view. Could you explain better your goal?

Backbone.js View can't unbind events properly

I have some Backbone.js code that bind a click event to a button,
and I want to unbind it after clicked, the code sample as below:
var AppView = Backbone.View.extend({
el:$("#app-view"),
initialize:function(){
_.bindAll(this,"cancel");
},
events:{
"click .button":"cancel"
},
cancel:function(){
console.log("do something...");
this.$(".button").unbind("click");
}
});
var view = new AppView();
However the unbind is not working, I tried several different way and end up binding event in initialize function with jQuery but not in Backbone.events model.
Anyone know why the unbind is not working?
The reason it doesn't work is that Backbonejs doesn't bind the event on the DOM Element .button itself. It delegates the event like this:
$(this.el).delegate('.button', 'click', yourCallback);
(docs: http://api.jquery.com/delegate)
You have to undelegate the event like this:
$(this.el).undelegate('.button', 'click');
(docs: http://api.jquery.com/undelegate)
So your code should look like:
var AppView = Backbone.View.extend({
el:$("#app-view"),
initialize:function(){
_.bindAll(this,"cancel");
},
events:{
"click .button":"cancel"
},
cancel:function(){
console.log("do something...");
$(this.el).undelegate('.button', 'click');
}
});
var view = new AppView();
Another (maybe better) way to solve this is to create a state attribute like this.isCancelable now everytime the cancel function is called you check if this.isCancelable is set to true, if yes you proceed your action and set this.isCancelable to false.
Another button could reactivate the cancel button by setting this.isCancelable to true without binding/unbinding the click event.
You could solve this another way
var AppView = Backbone.View.extend({
el:$("#app-view"),
initialize:function(){
_.bindAll(this,"cancel");
},
events:{
"click .button":"do"
},
do:_.once(function(){
console.log("do something...");
})
});
var view = new AppView();
underscore.js once function ensures that the wrapped function
can only be called once.
There is an even easier way, assuming you want to undelegate all events:
this.undelegateEvents();
I like bradgonesurfing answer. However I came across a problem using the _.once approach when multiple instances of the View are created. Namely that _.once would restrict the function to be called only once for all objects of that type i.e. the restriction was at the class level rather than instance level.
I handled the problem this way:
App.Views.MyListItem = Backbone.View.extend({
events: {
'click a.delete' : 'onDelete'
},
initialize: function() {
_.bindAll(this);
this.deleteMe = _.once(this.triggerDelete);
},
// can only be called once
triggerDelete: function() {
console.log("triggerDelete");
// do stuff
},
onDelete:(function (e) {
e.preventDefault();
this.deleteMe();
})
});
Hopefully this will help someone
you can simply use object.off, the code below is work for me
initialize:function () {
_.bindAll(this, 'render', 'mouseover', 'mouseout', 'delete', 'dropout' , 'unbind_mouseover', 'bind_mouseover');
.......
},
events: {
'mouseover': 'mouseover',
'unbind_mouseover': 'unbind_mouseover',
'bind_mouseover': 'bind_mouseover',
.....
},
mouseover: function(){
$(this.el).addClass('hover');
this.$('.popout').show();
},
unbind_mouseover: function(){
console.log('unbind_mouseover');
$(this.el).off('mouseover');
},
bind_mouseover: function(){
........
},

Categories