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!)
Related
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
}
});
What is the best way to detect the moment after a Backbone View, extended from an other object or not, has been removed?
JsFiddle added :
http://jsfiddle.net/simmoniz/M5J8Q/1917/
How to make the line #32 working without altering the views...
<h2>The container</h2>
<div id="container"></div>
<script>
var SomeExtendedView = Marionette.ItemView.extend({
events: {
'click button.remove':'remove',
},
});
var JohnView = SomeExtendedView.extend({
template: _.template('<div><p>I\'m a <em>John view</em> <button class="remove">Remove me</button></p></div>'),
});
var DoeView = SomeExtendedView.extend({
template: _.template('<div><p>I\'m a <strong>Doe view</strong> <button class="remove">Remove me</button>'),
});
var SimpleView = Backbone.View.extend({
initialize: function(){
Backbone.View.prototype.initialize.apply(this, arguments);
this.$el.bind('click', _.bind(this.remove, this));
},
render: function(){
this.$el.html('<div><p>Simple view. <strong>Click on me to remove</strong></p></div>');
return this;
}
});
var container = {
el: $('#container'),
views: null,
add: function(view){
if(!this.views)this.views = [];
this.el.append(view.render().el);
view.$el.bind('remove', _.bind(this.onRemove, this));
},
onRemove : function(element){
console.log('Element ' + element + ' has been removed!');
}
}
container.add(new JohnView());
container.add(new DoeView());
container.add(new SimpleView());
</script>
View lifecycle management is one of the important things missing from the backbone core.
All non-trivial apps end up needing to solve this. You can either roll your own, or use something like marionette or Chaplin.
Basically, backbone doesn't have the concept of view destruction or dealocation. A point in time in which listeners should be unbound. This is the single greatest source of memory leaks in backbone apps.
I finally came up with a working solution. Since the element added is a Backbone view (simple or extended), it has remove function. This solution replaces the remove function with a new "remove" event that performs the same operation, but triggers a "remove" event juste before. Listeners can catch it now. It works great :
var ev = new $.Event('remove'),
orig = $.fn.remove;
view.remove = function() {
$(this).trigger(ev);
return orig.apply(this, arguments);
}
Then we can listen to the "remove" event like in my question
view.bind('remove', _.bind(this.onRemove, this));
Inside the view
events: {
"remove" : "some function",
},
I have a Backbone View with simple events:
Backbone.View.extend({
events: {
"change #id": "idChanged"
},
idChanged: function () {}
initialize: function () {
/* construct HTML */
$("#id").trigger("change");
}
});
However this does not fire the idChanged event. When I change #id with the browser it does fire. How can I trigger the Backbone View event?
a couple of things in your code.
1 I don't think you defined your events correctly.
It should be a hash, or a function that returns a hash, like so:
events: {
"change #id": "idChanged"
}
2 a few typos like "function" and missing comma
then, to make the events work, the defined #id element must be inside the view's el. If the element is outside of the view, it's not gonna work.
also, you cannot trigger that in initialize, because before that function is executed, the view is not fully initialized yet. :)
here's a working example:
http://jsfiddle.net/3KmzQ/
That's because the events hash will be bound to the view when it gets rendered, which happens after the initialize code gets run. Try calling the desired callback directly:
Backbone.View.extend({
events: function () {
"change #id": "idChanged"
},
idChanged: function () {}
initialize: function () {
/* construct HTML */
this.idChanged();
}
});
You used "extend".
Same code should apply to Backbone.view.Object( {....} )
Specify the object that you would like to fire events at.
Backbone.View.Ojbect(
{
events: function () {
"change #id": "idChanged"
},
idChanged: funciton () {}
initialize: function () {
/* construct HTML */
$("#id").trigger("change");
}
}
);
That is, try not to extend.
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) {}
})
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(){
........
},