Related
With jQuery you can bind functions to an event triggered on a DOM object using .bind() or one of the event handler helper functions.
jQuery have to store this internally somehow and I wonder if is it possible given a DOM object, to find out which events have been bound to the object, and access those functions etc. The desired return result could look something like this:
{
click: [function1, function2],
change: [function3],
blur: [function4, function5, function6]
}
jQuery 1.7 has stopped exposing the events in the regular data() function. You can still get them like this:
var elem = $('#someid')[0];
var data = jQuery.hasData( elem ) && jQuery._data( elem );
console.log(data.events);
Please note, that this only works for Events which have been bound using jQuery. AFAIK you there is no way to see all the events which have been bound using the regular DOM functions like addEventListener.
You can see them in the webkit inspector though: In the Elements tab navigate to the desired DOM node, on the right side select the "Event Listeners" drop down.
Edit: the method below works only in jQuery < 1.7
You can find a lot of interesting tips and tricks in this article: Things you may not know about jQuery.
It seems that jQuery uses data to store event handlers:
You can access all event handlers
bound to an element (or any object)
through jQuery’s event storage:
// List bound events:
console.dir( jQuery('#elem').data('events') );
// Log ALL handlers for ALL events:
jQuery.each($('#elem').data('events'), function(i, event){
jQuery.each(event, function(i, handler){
console.log( handler['handler'].toString() );
});
});
// You can see the actual functions which will occur
// on certain events; great for debugging!
I've got this piece code in a view:
//dom events -----
,events:{
'click #language-switcher input[type="radio"]': function(e){
this.current_language = $(e.target).val();
}
,'click .create-gcontainer-button': function(){
this.collection.add(new Group());
}
}
,set_events:function(){
//model events -----
this.listenTo(this.collection,'add',function(group){
var group = new GroupView({ model: group });
this.group_views[group.cid] = group;
this.groups_container.append(group.el);
EventTools.trigger("group_view:create",{ lang:this.current_language });
});
this.listenTo(this.collection,'destroy',function(model){
console.log('removing model:', model);
});
//emitter events ---
EventTools.on('group_view:clear',this.refresh_groups, this);
}//set_events
Note: set_events gets called on initialization.
Well, I don't like defining events in 2 different places, but since the docs say that events defined from the 'events' prop are bound to the element (or children of it if a selector is passed), I guess I cannot use it for other types of events. Am I correct?
I also tried to define 'events' from inside my set_events function, but for some reason that leads to a memory leak or something similar (browser gets stuck).
So another more general question could be: on a Backbone view, is there a way to define all the events in one single place?
Within a View, there are two types of events you can listen for: DOM events and events triggered using the Event API. It is important to understand the differences in how views bind to these events and the context in which their callbacks are invoked.
OM events can be bound to using the View’s events property or using jQuery.on(). Within callbacks bound using the events property, this refers to the View object; whereas any callbacks bound directly using jQuery will have this set to the handling DOM element by jQuery. All DOM event callbacks are passed an event object by jQuery. See delegateEvents() in the Backbone documentation for additional details.
Event API events are bound as described in this section. If the event is bound using on() on the observed object, a context parameter can be passed as the third argument. If the event is bound using listenTo() then within the callback this refers to the listener. The arguments passed to Event API callbacks depends on the type of event. See the Catalog of Events in the Backbone documentation for details.
Yes you can define all the events in the view initialize method, see the below example
// Add your javascript here
var View = Backbone.View.extend({
el: '#todo',
// bind to DOM event using events property
initialize: function() {
// bind to DOM event using jQuery
this.events = {
'click [type="checkbox"]': 'clicked'
};
this.$el.click(this.jqueryClicked);
// bind to API event
this.on('apiEvent', this.callback);
},
// 'this' is view
clicked: function(event) {
console.log("events handler for " + this.el.outerHTML);
this.trigger('apiEvent', event.type);
},
// 'this' is handling DOM element
jqueryClicked: function(event) {
console.log("jQuery handler for " + this.outerHTML);
},
callback: function(eventType) {
console.log("event type was " + eventType);
}
});
you can see the demo here
for your code
set_events:function(){
//dom events -----
this.events={
'click #language-switcher input[type="radio"]': function(e){
this.current_language = $(e.target).val();
}
,'click .create-gcontainer-button': function(){
this.collection.add(new Group());
}
};
//model events -----
this.listenTo(this.collection,'add',function(group){
var group = new GroupView({ model: group });
this.group_views[group.cid] = group;
this.groups_container.append(group.el);
EventTools.trigger("group_view:create",{ lang:this.current_language });
});
this.listenTo(this.collection,'destroy',function(model){
console.log('removing model:', model);
});
//emitter events ---
EventTools.on('group_view:clear',this.refresh_groups, this);
}//set_events
As I commented above, for some strange reason, placing events inside initialize() or set_events() fails silently. I found out that doing one of the 2, backbone doesn't find the events. This backbone's view's function says undefined to console:
delegateEvents: function(events) {
events || (events = _.result(this, 'events'));
console.log(events); //outputs undefined
if (!events) return this;
this.undelegateEvents();
for (var key in events) {
var method = events[key];
if (!_.isFunction(method)) method = this[method];
if (!method) continue;
var match = key.match(delegateEventSplitter);
this.delegate(match[1], match[2], _.bind(method, this));
}
return this;
},
Though, if I place events like a regular view property, just as my code on the main question, it will correctly log the events.
Lets say I have a web app which has a page that may contain 4 script blocks - the script I write may be found in one of those blocks, but I do not know which one, that is handled by the controller.
I bind some onclick events to a button, but I find that they sometimes execute in an order I did not expect.
Is there a way to ensure order, or how have you handled this problem in the past?
If order is important you can create your own events and bind callbacks to fire when those events are triggered by other callbacks.
$('#mydiv').click(function(e) {
// maniplate #mydiv ...
$('#mydiv').trigger('mydiv-manipulated');
});
$('#mydiv').bind('mydiv-manipulated', function(e) {
// do more stuff now that #mydiv has been manipulated
return;
});
Something like that at least.
Dowski's method is good if all of your callbacks are always going to be present and you are happy with them being dependant on each other.
If you want the callbacks to be independent of each other, though, you could be to take advantage of bubbling and attach subsequent events as delegates to parent elements. The handlers on a parent elements will be triggered after the handlers on the element, continuing right up to the document. This is quite good as you can use event.stopPropagation(), event.preventDefault(), etc to skip handlers and cancel or un-cancel the action.
$( '#mybutton' ).click( function(e) {
// Do stuff first
} );
$( '#mybutton' ).click( function(e) {
// Do other stuff first
} );
$( document ).delegate( '#mybutton', 'click', function(e) {
// Do stuff last
} );
Or, if you don't like this, you could use Nick Leaches bindLast plugin to force an event to be bound last: https://github.com/nickyleach/jQuery.bindLast.
Or, if you are using jQuery 1.5, you could also potentially do something clever with the new Deferred object.
I had been trying for ages to generalize this kind of process, but in my case I was only concerned with the order of first event listener in the chain.
If it's of any use, here is my jQuery plugin that binds an event listener that is always triggered before any others:
** UPDATED inline with jQuery changes (thanks Toskan) **
(function($) {
$.fn.bindFirst = function(/*String*/ eventType, /*[Object])*/ eventData, /*Function*/ handler) {
var indexOfDot = eventType.indexOf(".");
var eventNameSpace = indexOfDot > 0 ? eventType.substring(indexOfDot) : "";
eventType = indexOfDot > 0 ? eventType.substring(0, indexOfDot) : eventType;
handler = handler == undefined ? eventData : handler;
eventData = typeof eventData == "function" ? {} : eventData;
return this.each(function() {
var $this = $(this);
var currentAttrListener = this["on" + eventType];
if (currentAttrListener) {
$this.bind(eventType, function(e) {
return currentAttrListener(e.originalEvent);
});
this["on" + eventType] = null;
}
$this.bind(eventType + eventNameSpace, eventData, handler);
var allEvents = $this.data("events") || $._data($this[0], "events");
var typeEvents = allEvents[eventType];
var newEvent = typeEvents.pop();
typeEvents.unshift(newEvent);
});
};
})(jQuery);
Things to note:
This hasn't been fully tested.
It relies on the internals of the jQuery framework not changing (only tested with 1.5.2).
It will not necessarily get triggered before event listeners that are bound in any way other than as an attribute of the source element or using jQuery bind() and other associated functions.
The order the bound callbacks are called in is managed by each jQuery object's event data. There aren't any functions (that I know of) that allow you to view and manipulate that data directly, you can only use bind() and unbind() (or any of the equivalent helper functions).
Dowski's method is best, you should modify the various bound callbacks to bind to an ordered sequence of custom events, with the "first" callback bound to the "real" event. That way, no matter in what order they are bound, the sequence will execute in the right way.
The only alternative I can see is something you really, really don't want to contemplate: if you know the binding syntax of the functions may have been bound before you, attempt to un-bind all of those functions and then re-bind them in the proper order yourself. That's just asking for trouble, because now you have duplicated code.
It would be cool if jQuery allowed you to simply change the order of the bound events in an object's event data, but without writing some code to hook into the jQuery core that doesn't seem possible. And there are probably implications of allowing this that I haven't thought of, so maybe it's an intentional omission.
Please note that in the jQuery universe this must be implemented differently as of version 1.8. The following release note is from the jQuery blog:
.data(“events”): jQuery stores its event-related data in a data object
named (wait for it) events on each element. This is an internal data
structure so in 1.8 this will be removed from the user data name space
so it won’t conflict with items of the same name. jQuery’s event data
can still be accessed via jQuery._data(element, "events")
We do have complete control of the order in which the handlers will execute in the jQuery universe. Ricoo points this out above. Doesn't look like his answer earned him a lot of love, but this technique is very handy. Consider, for example, any time you need to execute your own handler prior to some handler in a library widget, or you need to have the power to cancel the call to the widget's handler conditionally:
$("button").click(function(e){
if(bSomeConditional)
e.stopImmediatePropagation();//Don't execute the widget's handler
}).each(function () {
var aClickListeners = $._data(this, "events").click;
aClickListeners.reverse();
});
function bindFirst(owner, event, handler) {
owner.unbind(event, handler);
owner.bind(event, handler);
var events = owner.data('events')[event];
events.unshift(events.pop());
owner.data('events')[event] = events;
}
just bind handler normally and then run:
element.data('events').action.reverse();
so for example:
$('#mydiv').data('events').click.reverse();
You can try something like this:
/**
* Guarantee that a event handler allways be the last to execute
* #param owner The jquery object with any others events handlers $(selector)
* #param event The event descriptor like 'click'
* #param handler The event handler to be executed allways at the end.
**/
function bindAtTheEnd(owner,event,handler){
var aux=function(){owner.unbind(event,handler);owner.bind(event,handler);};
bindAtTheStart(owner,event,aux,true);
}
/**
* Bind a event handler at the start of all others events handlers.
* #param owner Jquery object with any others events handlers $(selector);
* #param event The event descriptor for example 'click';
* #param handler The event handler to bind at the start.
* #param one If the function only be executed once.
**/
function bindAtTheStart(owner,event,handler,one){
var eventos,index;
var handlers=new Array();
owner.unbind(event,handler);
eventos=owner.data("events")[event];
for(index=0;index<eventos.length;index+=1){
handlers[index]=eventos[index];
}
owner.unbind(event);
if(one){
owner.one(event,handler);
}
else{
owner.bind(event,handler);
}
for(index=0;index<handlers.length;index+=1){
owner.bind(event,ownerhandlers[index]);
}
}
I have same issue and found this topic. the above answers can solve those problem, but I don't think them are good plans.
let us think about the real world.
if we use those answers, we have to change our code. you have to change your code style. something like this:
original:
$('form').submit(handle);
hack:
bindAtTheStart($('form'),'submit',handle);
as time goes on, think about your project. the code is ugly and hard to read! anthoer reason is simple is always better. if you have 10 bindAtTheStart, it may no bugs. if you have 100 bindAtTheStart, are you really sure you can keep them in right order?
so if you have to bind same events multiple.I think the best way is control js-file or js-code load order. jquery can handle event data as queue. the order is first-in, first-out. you don't need change any code. just change load order.
Here's my shot at this, covering different versions of jQuery:
// Binds a jQuery event to elements at the start of the event chain for that type.
jQuery.extend({
_bindEventHandlerAtStart: function ($elements, eventType, handler) {
var _data;
$elements.bind(eventType, handler);
// This bound the event, naturally, at the end of the event chain. We
// need it at the start.
if (typeof jQuery._data === 'function') {
// Since jQuery 1.8.1, it seems, that the events object isn't
// available through the public API `.data` method.
// Using `$._data, where it exists, seems to work.
_data = true;
}
$elements.each(function (index, element) {
var events;
if (_data) {
events = jQuery._data(element, 'events')[eventType];
} else {
events = jQuery(element).data('events')[eventType];
}
events.unshift(events.pop());
if (_data) {
jQuery._data(element, 'events')[eventType] = events;
} else {
jQuery(element).data('events')[eventType] = events;
}
});
}
});
In some special cases, when you cannot change how the click events are bound (event bindings are made from others' codes), and you can change the HTML element, here is a possible solution (warning: this is not the recommended way to bind events, other developers may murder you for this):
<span onclick="yourEventHandler(event)">Button</span>
With this way of binding, your event hander will be added first, so it will be executed first.
JQuery 1.5 introduces promises, and here's the simplest implementation I've seen to control order of execution. Full documentation at http://api.jquery.com/jquery.when/
$.when( $('#myDiv').css('background-color', 'red') )
.then( alert('hi!') )
.then( myClickFunction( $('#myID') ) )
.then( myThingToRunAfterClick() );
I want to trigger a render event when my views are being rendered.
function Renderer() {
_.extend(this, Backbone.Events);
};
Renderer.prototype.render = function(view, model) {
this.trigger('render:before');
// Do some checks to see how
// we should render the view
// and then call render
this.trigger('render:after');
};
var renderer = new Renderer();
Now I can register for events on the Renderer, but I must use the full name. I.e. this works:
renderer.on('render:before', function() { console.log("before rendering"); });
renderer.on('render:after', function() { console.log("after rendering"); });
renderer.on('all', function() { console.log("All events from renderer"); });
But this does not:
renderer.on('render', function() { console.log("Any rendering events"); });
I expected the last one to be equivalent to registering on all events for the renderer.
Is there a way to make listening to render equivalent to listening for both render:before and render:after?
Namespacing event names by using the colon is just a convention:
If you have a large number of different events on a page, the
convention is to use colons to namespace them: "poll:start", or
"change:selection".
The source code of Events.trigger shows that the event handler to be called is searched for by the full name of the event, independently of whether it contains a colon or not:
var events = this._events[name];
...
if (events) triggerEvents(events, args);
You can:
define and trigger an 'all' event,
trigger multiple event handlers by calling trigger with a space-delimited list of event names, or
modify the source code of Events.trigger in backbone.js to add this feature.
Lets say I have a web app which has a page that may contain 4 script blocks - the script I write may be found in one of those blocks, but I do not know which one, that is handled by the controller.
I bind some onclick events to a button, but I find that they sometimes execute in an order I did not expect.
Is there a way to ensure order, or how have you handled this problem in the past?
If order is important you can create your own events and bind callbacks to fire when those events are triggered by other callbacks.
$('#mydiv').click(function(e) {
// maniplate #mydiv ...
$('#mydiv').trigger('mydiv-manipulated');
});
$('#mydiv').bind('mydiv-manipulated', function(e) {
// do more stuff now that #mydiv has been manipulated
return;
});
Something like that at least.
Dowski's method is good if all of your callbacks are always going to be present and you are happy with them being dependant on each other.
If you want the callbacks to be independent of each other, though, you could be to take advantage of bubbling and attach subsequent events as delegates to parent elements. The handlers on a parent elements will be triggered after the handlers on the element, continuing right up to the document. This is quite good as you can use event.stopPropagation(), event.preventDefault(), etc to skip handlers and cancel or un-cancel the action.
$( '#mybutton' ).click( function(e) {
// Do stuff first
} );
$( '#mybutton' ).click( function(e) {
// Do other stuff first
} );
$( document ).delegate( '#mybutton', 'click', function(e) {
// Do stuff last
} );
Or, if you don't like this, you could use Nick Leaches bindLast plugin to force an event to be bound last: https://github.com/nickyleach/jQuery.bindLast.
Or, if you are using jQuery 1.5, you could also potentially do something clever with the new Deferred object.
I had been trying for ages to generalize this kind of process, but in my case I was only concerned with the order of first event listener in the chain.
If it's of any use, here is my jQuery plugin that binds an event listener that is always triggered before any others:
** UPDATED inline with jQuery changes (thanks Toskan) **
(function($) {
$.fn.bindFirst = function(/*String*/ eventType, /*[Object])*/ eventData, /*Function*/ handler) {
var indexOfDot = eventType.indexOf(".");
var eventNameSpace = indexOfDot > 0 ? eventType.substring(indexOfDot) : "";
eventType = indexOfDot > 0 ? eventType.substring(0, indexOfDot) : eventType;
handler = handler == undefined ? eventData : handler;
eventData = typeof eventData == "function" ? {} : eventData;
return this.each(function() {
var $this = $(this);
var currentAttrListener = this["on" + eventType];
if (currentAttrListener) {
$this.bind(eventType, function(e) {
return currentAttrListener(e.originalEvent);
});
this["on" + eventType] = null;
}
$this.bind(eventType + eventNameSpace, eventData, handler);
var allEvents = $this.data("events") || $._data($this[0], "events");
var typeEvents = allEvents[eventType];
var newEvent = typeEvents.pop();
typeEvents.unshift(newEvent);
});
};
})(jQuery);
Things to note:
This hasn't been fully tested.
It relies on the internals of the jQuery framework not changing (only tested with 1.5.2).
It will not necessarily get triggered before event listeners that are bound in any way other than as an attribute of the source element or using jQuery bind() and other associated functions.
The order the bound callbacks are called in is managed by each jQuery object's event data. There aren't any functions (that I know of) that allow you to view and manipulate that data directly, you can only use bind() and unbind() (or any of the equivalent helper functions).
Dowski's method is best, you should modify the various bound callbacks to bind to an ordered sequence of custom events, with the "first" callback bound to the "real" event. That way, no matter in what order they are bound, the sequence will execute in the right way.
The only alternative I can see is something you really, really don't want to contemplate: if you know the binding syntax of the functions may have been bound before you, attempt to un-bind all of those functions and then re-bind them in the proper order yourself. That's just asking for trouble, because now you have duplicated code.
It would be cool if jQuery allowed you to simply change the order of the bound events in an object's event data, but without writing some code to hook into the jQuery core that doesn't seem possible. And there are probably implications of allowing this that I haven't thought of, so maybe it's an intentional omission.
Please note that in the jQuery universe this must be implemented differently as of version 1.8. The following release note is from the jQuery blog:
.data(“events”): jQuery stores its event-related data in a data object
named (wait for it) events on each element. This is an internal data
structure so in 1.8 this will be removed from the user data name space
so it won’t conflict with items of the same name. jQuery’s event data
can still be accessed via jQuery._data(element, "events")
We do have complete control of the order in which the handlers will execute in the jQuery universe. Ricoo points this out above. Doesn't look like his answer earned him a lot of love, but this technique is very handy. Consider, for example, any time you need to execute your own handler prior to some handler in a library widget, or you need to have the power to cancel the call to the widget's handler conditionally:
$("button").click(function(e){
if(bSomeConditional)
e.stopImmediatePropagation();//Don't execute the widget's handler
}).each(function () {
var aClickListeners = $._data(this, "events").click;
aClickListeners.reverse();
});
function bindFirst(owner, event, handler) {
owner.unbind(event, handler);
owner.bind(event, handler);
var events = owner.data('events')[event];
events.unshift(events.pop());
owner.data('events')[event] = events;
}
just bind handler normally and then run:
element.data('events').action.reverse();
so for example:
$('#mydiv').data('events').click.reverse();
You can try something like this:
/**
* Guarantee that a event handler allways be the last to execute
* #param owner The jquery object with any others events handlers $(selector)
* #param event The event descriptor like 'click'
* #param handler The event handler to be executed allways at the end.
**/
function bindAtTheEnd(owner,event,handler){
var aux=function(){owner.unbind(event,handler);owner.bind(event,handler);};
bindAtTheStart(owner,event,aux,true);
}
/**
* Bind a event handler at the start of all others events handlers.
* #param owner Jquery object with any others events handlers $(selector);
* #param event The event descriptor for example 'click';
* #param handler The event handler to bind at the start.
* #param one If the function only be executed once.
**/
function bindAtTheStart(owner,event,handler,one){
var eventos,index;
var handlers=new Array();
owner.unbind(event,handler);
eventos=owner.data("events")[event];
for(index=0;index<eventos.length;index+=1){
handlers[index]=eventos[index];
}
owner.unbind(event);
if(one){
owner.one(event,handler);
}
else{
owner.bind(event,handler);
}
for(index=0;index<handlers.length;index+=1){
owner.bind(event,ownerhandlers[index]);
}
}
I have same issue and found this topic. the above answers can solve those problem, but I don't think them are good plans.
let us think about the real world.
if we use those answers, we have to change our code. you have to change your code style. something like this:
original:
$('form').submit(handle);
hack:
bindAtTheStart($('form'),'submit',handle);
as time goes on, think about your project. the code is ugly and hard to read! anthoer reason is simple is always better. if you have 10 bindAtTheStart, it may no bugs. if you have 100 bindAtTheStart, are you really sure you can keep them in right order?
so if you have to bind same events multiple.I think the best way is control js-file or js-code load order. jquery can handle event data as queue. the order is first-in, first-out. you don't need change any code. just change load order.
Here's my shot at this, covering different versions of jQuery:
// Binds a jQuery event to elements at the start of the event chain for that type.
jQuery.extend({
_bindEventHandlerAtStart: function ($elements, eventType, handler) {
var _data;
$elements.bind(eventType, handler);
// This bound the event, naturally, at the end of the event chain. We
// need it at the start.
if (typeof jQuery._data === 'function') {
// Since jQuery 1.8.1, it seems, that the events object isn't
// available through the public API `.data` method.
// Using `$._data, where it exists, seems to work.
_data = true;
}
$elements.each(function (index, element) {
var events;
if (_data) {
events = jQuery._data(element, 'events')[eventType];
} else {
events = jQuery(element).data('events')[eventType];
}
events.unshift(events.pop());
if (_data) {
jQuery._data(element, 'events')[eventType] = events;
} else {
jQuery(element).data('events')[eventType] = events;
}
});
}
});
In some special cases, when you cannot change how the click events are bound (event bindings are made from others' codes), and you can change the HTML element, here is a possible solution (warning: this is not the recommended way to bind events, other developers may murder you for this):
<span onclick="yourEventHandler(event)">Button</span>
With this way of binding, your event hander will be added first, so it will be executed first.
JQuery 1.5 introduces promises, and here's the simplest implementation I've seen to control order of execution. Full documentation at http://api.jquery.com/jquery.when/
$.when( $('#myDiv').css('background-color', 'red') )
.then( alert('hi!') )
.then( myClickFunction( $('#myID') ) )
.then( myThingToRunAfterClick() );