Backbone.js View can't unbind events properly - javascript

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(){
........
},

Related

Backbone window keypress event?

I know that I can use the window.keypress event
$(window).keypress(function(e) {
alert('hello world');
});
But I was wondering if there was a way to use the backbone events to catch a keypress anywhere in the window?
I cannot do it on a view, because my page will contain multiple views.
This should do the trick:
var OverlordView = Backbone.View.extend({
events: {
"keypress": "alert"
},
alert: function() {
alert('hello world')
}
});
$(function() {
new OverlordView({el: $('body')[0]})
})
Demo: http://jsfiddle.net/3obw5k8j/
Backbone's event handlers only works in its own view. Doesnt affect window context. You need to setup inside initialize i assume.
var MyView = Backbone.View.extend({
initialize : function(){
$(window).on("keypress", this.windowKeyPress)
},
windowKeyPress : function(){
// handler.
}
});

Child object calls siblings' methods

I have the following structure:
var PopupView = Backbone.View.extend({
el:'#shade',
events:{
'click .popup-cancel': 'hide',
'click .popup-commit': 'commit',
},
show: function(){
...
},
hide: function(){
...
},
initialize: function(){
_.bindAll(this, 'show','hide','commit');
}
});
var Popup1 = PopupView.extend({
render: function(){
...
},
commit: function(){
console.log('1');
...
},
});
var Popup2 = PopupView.extend({
render: function(){
...
},
commit: function(){
console.log('2');
...
},
});
The problem is that when I click .popup-commit from one of the popups, it actually triggers the methods of both of them. I've tried moving the declaration of events and initialize() up into the child classes, but that doesn't work.
What's going on, and how can I fix it (so that the commit method of only the view I'm triggering it on gets fired)?
Your problem is right here:
el:'#shade'
in your PopupView definition. That means that every single instance of PopupView or its subclasses (except of course those that provide their own el) will be bound to the same DOM node and they will all be listening to events on on id="shade" element.
You need to give each view its own el. I'd recommend against ever setting el in a view definition like that. I think you'll have a better time if you let each view create (and destroy) its own el. If you do something like:
var PopupView = Backbone.View.extend({
className: 'whatever-css-class-you-need',
tagName: 'div', // or whatever you're using to hold your popups.
attributes: { /* whatever extra attributes you need on your el */ },
//...
});
then your views will each get their own el. See the Backbone.View documentation for more information on these properties.

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){
}
});

Disable specific event in a Backbone.js view

I am working on a nested backbone view, in which if you click on, it will create a new instance of the same view. I want to disable only a specific event, not all of them; in this case, the click. I tried using undelegateEvents(), but this will disable all the functions. Any ideas on how can this be done?
Here is a piece of the code I am working on:
var View = Backbone.View.extend({
events: {
"mousedown": "start",
"mouseup": "over"
},
start: function() {
var model = this.model;
var v = new View({
model: model,
});
v.undelegateEvents(); //I just want to disable mousedown
v.render();
},
over: function() {
/*
some code here
*/
},
render: function() {
/*
some code here
*/
}
});
The idea is to ban clicking in the second instantiated view while keeping the other events. The first one will have all of its events.
Thanks
You can specify the events you want to use when you call delegateEvents:
delegateEvents delegateEvents([events])
Uses jQuery's delegate function to provide declarative callbacks for DOM events within a view. If an events hash is not passed directly, uses this.events as the source.
So you could do something like this:
var v = new View({
model: model,
});
v.undelegateEvents();
var e = _.clone(v.events);
delete e.mousedown;
v.delegateEvents(e);
v.render();
You might want to push that logic into a method on View though:
detach_mousedown: function() {
this.undelegateEvents();
this.events = _.clone(this.events);
delete this.events.mousedown;
this.delegateEvents();
}
//...
v.detach_mousedown();
You need the this.events = _.clone(this.events) trickery to avoid accidentally altering the "class's" events (i.e. this.constructor.prototype.events) when you only want to change it for just one object. You could also have a flag for the View constructor that would do similar things inside its initialize:
initialize: function() {
if(this.options.no_mousedown)
this.detach_mousedown()
//...
}
Another option would be to have a base view without the mousedown handler and then extend that to a view that does have the mousedown handler:
var B = Backbone.View.extend({
events: {
"mouseup": "over"
},
//...
});
var V = B.extend({
events: {
"mousedown": "start",
"mouseup": "over"
},
start: function() { /* ... */ }
//...
});
You'd have to duplicate the B.events inside V or mess around with a manual extend on the events as _.extend won't merge the properties, it just replaces things wholesale.
Here is a simple example that shows how to delegate or undelegate events within a Backbone view
Backbone.View.extend({
el: $("#some_element"),
// delete or attach these as necessary
events: {
mousedown: "mouse_down",
mousemove: "mouse_move",
mouseup: "mouse_up",
},
// see call below
detach_event: function(e_name) {
delete this.events[e_name]
this.delegateEvents()
},
initialize: function() {
this.detach_event("mousemove")
},
mouse_down: function(e) {
this.events.mousemove = "mouse_move"
this.delegateEvents()
},
mouse_move: function(e) {},
mouse_up: function(e) {}
})

Using variable for selectors in events

for some reason I need to use a variable as the selector for events in backbone, but I can't figure how to do this :
app.views.Selfcare = Backbone.View.extend({
events: {
click window.parent.document .close : "closeWindow"
},
closeWindow: function() {
//code
}
});
I have to use a different scope and I can't do "click .close" : "closeWindow".
Thx for your help.
I had a look at Backbone.js's source code and found out that if your view's events is a function then the function is called and it's return value is used as the events object.
This means that your code can be changed like this:
app.views.Selfcare = Backbone.View.extend({
events: function() {
var _events = {
// all "standard" events can be here if you like
}
_events["events" + "with variables"] = "closeWindow";
return _events;
},
closeWindow: function() {
//code
}
});
THIS is the interesting part of the source code:
if (_.isFunction(events)) events = events.call(this);
Update:
Example is available on JSFiddle HERE**
I'm not sure that you'll be able to use a variable there. You could use the built-in Events methods (see documentation) to add a custom listener, then add an event listener to window.parent.document to trigger that custom event (use the Events.trigger method).
That said, it would be much easier to decouple this event from Backbone entirely (unless you don't want to do that), and go down the addEventListener route:
app.views.Selfcare = Backbone.View.extend({
initialize: function() {
_.bindAll(this, 'render', 'closeWindow');
if(this.options.clickTarget) {
this.options.clickTarget.addEventListener('click', this.closeWindow, false);
}
},
render: function() {
// Render to the DOM here
return this; // as per Backbone conventions
},
closeWindow: function() {
// Stuff here
}
});
// Usage:
var mySelfcare = new app.views.Selfcare({
clickTarget: window.parent.document
});
I think that should work, although I haven't tested it (and there may be one or two syntactical errors!)

Categories