Events are the main method of communication between a user and a website or web application. Most of our JavaScript/jQuery coding will be run in response to a variety of user and browser events.
By user events, I mean basically keyboard and mouse interaction like
click
, mousedown
, keypress
, etc. Browser
events are mainly DOM events like document.ready
, window.onload
, and many other events
related to DOM elements.
When coding Ajax applications, we also have custom jQuery
Ajax events that are dispatched during the process of an Ajax
request, that is, ajaxSend
, ajaxComplete
, ajaxError
, and some more.
jQuery’s API is very consistent, especially when it comes to events. Attaching a handler to any kind of event is done using the same code structure:
jQuery(listener
).bind('eventName'
,handlerFunction
);
This syntax also applies to a fourth category that I haven’t mentioned yet. jQuery’s event system can be used for event-driven programming[1] in which you can create your own custom events that can be bound and triggered as regular ones.
jQuery also provides a shortcut method for most common browser and Ajax events. A model call using a shortcut would look like this:
jQuery(listener
).eventName
(handlerFunction
);
When using bind()
, eventName
will be a string wrapped in either
single or double quotes. When
using the shortcut, you simply put the event’s name as the jQuery
method’s name.
Here’s an example of binding a click handler, with and without the shortcut:
// Using bind() jQuery('div'
).bind('click',
function(e){...}
); // Using the shortcut jQuery('div'
).click
(function(e){...}
);
During this chapter, I’ll use the shortcuts when available, just because they’re shorter and easier to read, in my opinion. Both work equally, and there’s no advantage to using the shortcut other than clarity and brevity; it’s simply a matter of taste.
I’ll assume that you already read Chapter 1, where the document.ready
event is explained in detail
(Recipe 1.2). If you have
any doubt about its use, do consult that recipe.
I also want to clarify that when I use the term plugin, for most cases I mean “plugins, widgets, or simply blocks of code.” Most jQuery users tend to organize their code into plugin-like structures, usually adding names to jQuery’s namespace.
Finally, jQuery’s event module was highly modified in 1.3. I will always mention when something needs to be done differently, according to what jQuery version would be used.
In many common situations, one needs to bind the same handler function to more than one event (on the same element, that is). You could always do something like this:
jQuery('div'
).click
(function(e){alert('event'),
}
) .keydown
(function(e){alert('event'),
}
);
That is not such a problem if the function is short, but for longer blocks of code, repeating them over and over won’t be that trivial and is definitely not the best approach.
There’s more than a single solution to this simple but recurrent problem.
One way to solve it without repeating yourself too much would be as follows:
function handler(e){ alert('event'), } jQuery('div'
).click
(handler
) .keydown
(handler
);
Defining a function once and then referring to it multiple times is not a bad approach, but there’s an even simpler one provided by jQuery.
bind()
accepts a list of events separated by spaces. That
means you can solve the previous problem like this:
jQuery('div'
).bind
'click keydown'
,function(e){
alert('event'),
});
You can also apply this behavior to unbind()
and one()
.
To unbind a certain function, you need to have a reference to
it, so even if you are using the multievent feature, you still need to
keep a reference to the handler. If you don’t pass the function to
unbind()
, then any other event
handler bound to that event will be removed as well:
function handler(e){ alert('event'), } jQuery('div'
).bind('click
keydown', handler); // ... jQuery('div'
).unbind('click
keydown', handler);
You’ve come into a situation where you have many bindings, and the handler functions look pretty similar. It doesn’t matter whether these bindings are applied to different element/event combinations. The thing is, you don’t want to repeat yourself over and over (who does?).
Here’s an example:
jQuery('#button1').click(function(e){ jQuery('div.panel').hide(); jQuery('#panel1').show(); jQuery('#desc').text('You clicked the red button'), }); jQuery('#button2').click(function(e){ jQuery('div.panel').hide(); jQuery('#panel2').show(); jQuery('#desc').text('You clicked the blue button'), }); jQuery('#button3').click(function(e){ jQuery('div.panel').hide(); jQuery('#panel3').show(); jQuery('#desc').text('You clicked the green button'), });
As you can see, the only differences noticed on each handler are the color and the panel to show. The amount of code would grow as you add more buttons or each time the handler functions get larger.
bind()
accepts an optional
data
argument to be bound together
with each specific handler function. The data values will be
accessible from within this function by accessing
[2] where event
.data
is the
event object argument provided by jQuery.event
Note that this value can be anything...an array, a string, a number, or an object literal.
It’s a common approach to pass an object literal, even if you are just passing one value, to make the code more readable. This way, the name you give this single attribute within the object will make your code a little more self-explanatory.
event
.data
is used to provide precomputed values
to a function, which means the values you will be passing to bind()
need to be already available at
binding time. To handle more “dynamic” values, there’s another way
that we’ll learn about in Recipe 8.5.
The solution to the previous problem could look something like this:
function buttonClicked(e){ jQuery('div.panel').hide(); jQuery('#panel'+e.data.panel).show(); jQuery('#desc').text('You clicked the '+e.data.color+' button'), } jQuery('#button1').bind('click',{panel:1, color:'red'}, buttonClicked); jQuery('#button2').bind('click',{panel:2, color:'blue'}, buttonClicked); jQuery('#button3').bind('click',{panel:3, color:'green'}, buttonClicked);
Of course, you could make this even shorter by using a loop. This approach is called a macro by some coders, and it’s a very common approach for jQuery code.
These macros will surely reduce the code length and can sometimes improve code readability. Some other times, they’ll just make your code completely unreadable, so use them with caution.
Here’s how you could do it:
jQuery.each(['red','blue','green'], function(num, color){ num++; // it's 0-index based jQuery('#button'+num).bind('click',function(e){ jQuery('div.panel').hide(); jQuery('#panel'+num).show(); jQuery('#desc').text('You clicked the '+color+' button'), }); })
As you can see, I haven’t used the data argument because we don’t really need it. The code is now somewhat shorter, but not that much, and it’s not more readable.
The conclusion is that both approaches can be used on this kind of situation. Depending on the problem, one could be better (shorter, more readable, easier to maintain) than the other.
So, you’ve made a plugin-like block of code that binds many event handlers to certain DOM elements.
Later, you want to clean them all up in order to dispose the plugin completely.
This could get a little lengthy if you added many handlers. Maybe you don’t even have access to the bound handlers because they belong to another local scope.
You can’t unbind every handler for a certain event (or any existing event), because you could be deleting other handlers that you didn’t take into account.
Use a unique namespace for each plugin you make. Any handler bound within this plugin must be added with this namespace.
Later, when cleaning up, you just need to “unbind the whole namespace,” and all the related event handlers will go away with one single line of code.
To add a namespace to an event type, you simply add a . followed by the namespace name.
Since jQuery 1.3, you can add more than one (namespace) per event.
This is how you would bind the click
and mousedown
functions with a
namespace:
jQuery.fn.myPlugin = function(){ return this .bind('click.myPlugin', function(){ // [code] }) .bind('mousedown.myPlugin', function(){ // [code] }); };
You need to trigger an event on a certain element (or many). This element belongs to one or more plugins so it may have event handlers bound to this event.
The problem is that this event is a common one, like click
or mousedown
. Simply triggering the event could
run other event handlers that you didn’t expect.
On the same principle as the previous recipe, namespaces can be used for triggering as well. When binding, you need to make sure you add a unique namespace to each set of handlers.
This can also be used for the opposite situation; if you need to trigger any event except those with a namespace, you can use the ! operator. An example of this will be shown in the discussion.
Now, say you want to programmatically trigger the click event bound by the plugin myPlugin. You could simply trigger the click event, but that would be a bad approach, because any other handler bound to the same event would get fired as well.
This is how to do this properly:
jQuery.fn.runMyPlugin = function(){
return this.trigger('click.myPlugin'),
};
On the contrary, maybe you need to trigger a click (or any other event), but the target element belongs to one or more plugins. Triggering an event could run undesired event handlers, and that would cause problems that will be pretty hard to debug.
So, assuming all the plugins did use a namespace, this is how to trigger a click safely:
jQuery('div.panels').trigger('click!'),
You want to pass certain values to an event handler, but they’re not known at “binding time,” and they would change with each call to the handler.
There are two ways of solving this problem:
Both approaches work, and neither is clearly better than the other. The second approach was a little awkward to use before jQuery 1.3. Since this version, it has become pretty straightforward and less problematic. I’ll explain each option in detail in the “Discussion” section.
Passing data to the handler, instead of making the function grab it from somewhere (global variables, jQuery namespace, etc.), makes the code easier to maintain because you keep handler functions simple and agnostic from the environment.
This also allows you to reuse the same handler for many situations.
trigger()
can receive one
or more values that will be passed on to the triggered handlers.
These values can be of any type and any amount. When you have more than one, you need to wrap them with an array:
jQuery('form').trigger('submit', ['John','Doe', 28, {gender:'M'}]);
The bound function for the preceding case would be something like this:
jQuery('form').bind('submit', function(e, name, surname, age, extra){
// Do something with these arguments
});
This approach is simple and easy to read. The problem is, it looks pretty bad when you need to receive many arguments; I personally wouldn’t go beyond four to five.
It’s also kind of misleading if the reader is used to the
common function(e){ }
kind of
function.
You start to wonder, where do these other arguments come from ?
Used within a programmatic event:
jQuery('#slideshow').bind('add-image', function(e, src){ var $img = jQuery('<img />').attr('src', src); jQuery(this).append($img); }); jQuery('#slideshow').trigger('add-image', 'img/dogs4.jpg');
Used within a real event:
jQuery('#button').bind('click', function(e, submit){ if( submit ) // Do something else // Do something else }); jQuery('#button').trigger('click', true);
If you choose to pass a custom event object instead, each value you pass has to be accessed as an attribute on the event object received by the handler.
This means that, no matter how many data you’re passing, the handler will always have only a single argument, the event object.
This is already an advantage over the first approach, because it makes the function declaration less verbose.
As mentioned, this approach is much nicer to use since jQuery 1.3. Here’s how you’d code the first example with a custom object:
jQuery('form').bind('submit', function(e){ // Do something with e.name, e.surname, etc. }); jQuery('form').trigger({ type:'submit', name:'John', surname:'Doe', age: 28, gender:'M' });
Passing an object literal is actually a shortcut to creating
an instance of jQuery.Event
.[3] This is the alternative way:
var e = jQuery.Event('submit'), // the new
operator can be omitted
e.name = 'John';
e.surname = 'Doe';
e.age = 28;
e.gender = 'M';
jQuery('form').trigger(e);
You can, of course, use jQuery.extend
instead of setting one
attribute at a time.
You do need to create an event object yourself if you plan on
retrieving data from this object after the call to trigger()
. That’s, by the way, a cool
technique to pass information from the handler to the caller (we’ll
get into this in the next chapter).
Using event.data
is useful
for static values that are accessible at the time when the function
was bound. When the data you need to pass must be evaluated later
(or each time), event.data
won’t
do for you.
You need to gain access to a certain DOM element as soon as possible.
Using document.ready
isn’t
fast enough; you really want to control this element before the page
finishes rendering.
Issues like this are especially noticeable on large pages, where
the document.ready
event takes
longer to be reached.
This is a very common and generic problem that can be solved in many different ways.
There’s one approach that works for all of them, but it requires polling the DOM so it adds overhead to the page-rendering process (definitely undesirable!).
These are some of the usual problems where one could rely on polling:
Hide an element right away, before it is rendered (or another style operation)
Bind event handlers to an element ASAP so that it quickly becomes functional
Any other situation
We’ll discuss what’s the better approach for each situation in the “Discussion” section.
So, your problem is directly related to styling, you want to apply a conditional styling to an element, and this condition needs to be evaluated by JavaScript.
The right way to go about this is adding a specific CSS class
to an element that is quickly accessible, like the <html>
element, and then style the
element accordingly.
Do something like this:
<!DOCTYPE html> <html> <head> <style type="text/css"> html.no-message #message{ display:none; } </style> <script src="assets/jquery-latest.js"></script> <script type="text/javascript"> // Bad jQuery(document).ready(function($){ $('#message').hide(); }); // Correct jQuery('html').addClass('no-message'), // or... document.documentElement.className = 'no-message'; </script> </head> <body> <p id="message">I should not be visible</p> <!-- Many more html elements --> </body> </html>
Very often we have this large page with interactive elements, like buttons and links.
You don’t want those elements to just hang in there, without any functionality attached while the page loads.
Luckily, there’s a great concept called event delegation that can save the day. Event delegation is easy to implement with one of several jQuery plugins, and since jQuery 1.3, a plugin is no longer needed, because it has been added to the core jQuery file.
You can now bind event handlers to elements that still don’t
exist by using the method live()
.[4] That way, you don’t need to worry about waiting for
the element to be ready in order to bind the events.
To read more about event delegation, check Recipe 8.10.
Your problem isn’t about styling or about events. Then you, my friend, fall into the worst group.
But don’t panic! There’s a better solution than polling if you’re concerned about performance. I’ll explain it at last.
Polling can be implemented with a simple interval
(setInterval
) that checks for
an element, and once found, a certain function is run, and the
interval needs to be cleared.
There are two plugins that can aid you with this. One is LiveQuery
,[5] which has an option to register a function to be run
for each newly found element that matches a selector. This
approach is pretty slow but supports the whole set of
selectors.
There’s another plugin called ElementReady
[6] that will also handle this situation properly.
It lets you register pairs of id
/function
, and it will poll the DOM. Once
an id
is found, the function
will be called, and the
id
is removed from the
queue.
This plugin implements, probably, the fastest approach to
detect elements, that is, using document.getElementById
. This plugin is
pretty fast but only supports id
s.
The whole document-ready concept means “after the html is
parsed.” This means the browser reached the body’s closing tag,
</body>
.
In other words, instead of using document.ready
, you could simply put
your scripts right before </body>
.
You can apply the same principle to other parts of the DOM:
you can add a <script>
right after the element you want to access, and you can know, for
certain, that it will be already accessible from it.
Here’s an example:
<!DOCTYPE html> <html> <head> <script src="assets/jquery-latest.js"></script> </head> <body> <p>The time is <span id="time"> </span></p> <script type="text/javascript"> jQuery('#time').text( new Date().toString() ); </script> <!-- Many more html elements --> </body> </html>
As you can see, no polling was needed in this case. This is a feasible solution if you don’t need to use it a lot or you’ll be adding tons of scripts to the page.
You have several handlers bound to the same element/event combination.
You want to, from within a handler, prevent the rest from being
called, something like what event.stopPropagation()
[7] does. The problem is that event.stopPropagation()
only works for
elements that are below the current element in the DOM
hierarchy.
Since jQuery 1.3, event objects passed to handlers have a new
method called stopImmediatePropagation()
.[8] This method will do just that, and no subsequent event
handler will be notified of the current event. It will also stop the
event’s propagation, just like stopPropagation()
does.
This method has been taken from ECMAScript’s DOM level 3 events specification.[9]
If you want to consult the event object, to know whether this
method has been called, you can do so by calling event.isImmediatePropagationStopped()
,[10] which will return either true
or false
.
stopImmediatePropagation()
can cancel
the actual submit binding(s) if a certain situation is met:
jQuery('form')
.submit(function(e){
e.preventDefault(); // Don't submit for real
if( jQuery('#field').val() == '' )
e.stopImmediatePropagation();
})
.submit(function(e){
// Only executed if the function above
// didn't call e.stopImmediatePropagation
});
It can also be useful for disabling elements or blocking containers temporarily:
(function($){
function checkEnabled(e){
if( jQuery(this).is('.disabled') ){
e.stopImmediatePropagation(); // Stop all handlers
e.preventDefault();
}
};
jQuery.fn.buttonize = function(){
return this.css('cursor','pointer')
.bind('click mousedown mouseup',checkEnabled};
};
})(jQuery);
While this new feature could be a lifesaver in some situations, you must be aware that basing your logic on this behavior isn’t all that safe. When you rely on this feature, you assume that the handlers will be executed in the order you expect and that no other handlers will get in the way.
While events bound with jQuery are executed in the same order
they’re added, it’s not something the API strongly supports, meaning
it could fail in some browsers or some special situations. It’s also
possible that bindings from different plugins could collide because one could call stopImmediatePropagation()
and the other
wouldn’t get executed. This could cause unexpected problems that
could take a long time to debug.
The conclusion is, don’t be afraid to use stopImmediatePropagation()
if it really
suits your problem, but do use it with caution and double-check all
the event handlers involved.
You should rather think twice before using it in these situations:
The listener is a “popular” DOM element that is also used by other plugins.
The event is a common one like click
or ready
. There’s a greater chance of
collisions.
On the other hand, it should be pretty safe to use it in these situations:
The listener is a DOM element that is dynamically created and used merely by one plugin.
The event is a custom event like change-color
or addUser
.
You intentionally want to stop any bound handler (like in the second example).
Your code is relying on the event.target
[11] property of an event object, most likely in combination
with event delegation, where one single event handler is bound to a
container and it manages a variable number of descendant
elements.
In some cases, you don’t seem to be getting the expected
behavior. event.target
is sometimes
pointing to an element that is inside the one you expected.
The event.target
property
refers to the element that got the event, that is, the specific
element.
This means that if, for example, you have an image inside a link
and you click the link, the event.target
will be the image, not the
link.
So, how should you work around this? If you’re not working with
event delegation, then using this
(scope of the function) or event.currentTarget
(since jQuery 1.3)
should do. It will always point to the element that has the event
handler bound.
If you’re indeed using event delegation, then you’ll need to find the parent element you were expecting.
Since jQuery 1.3, you can use closest()
.[12] As specified in its documentation, it will return the
closest element, beginning with the current element and going up
through the parents, that matches a certain selector.
If you’re using an older version of jQuery, you can simulate
closest()
with something like
this:
jQuery.fn.closest = function( selector ){ return this.map(function(){ var $parent = jQuery(this).parents(); return jQuery(this).add($parents).filter( selector )[0]; }); }
This could be improved a little for performance, but this simple version should do for general purposes.
Here’s a small example of a very common situation using closest()
:
jQuery('table').click(function(e){ var $tr = jQuery(e.target).closest('tr'), // Do something with the table row });
event.target
is one of the
event object’s properties normalized by jQuery’s event system
(event.srcElement
on IE).
So, how come an event is triggered on this target element and your event handler is called even when bound to an ancestor ? The answer is Event bubbling.[13]
Most standard DOM events do bubble.[14] This means that, after the event was triggered on the target, it will go up to its parent node and trigger the same event (with all its handlers).
This process will continue until either it reaches the document
or event.stopPropagation()
is called within an event
handler.
Thanks to event bubbling, you don’t need to always bind event handlers to specific elements; instead, you can bind to a common container once and handle them all from there. This is the principle of event delegation.
We all have fallen for this at least once. You set up something like this:
jQuery('#something').hover( function(){ // add some cool animation for jQuery(this) }, function(){ // Revert the cool animation to its initial state } );
For example, you could be enlarging an element each time the mouse rolls over it and then shrinking it to its initial size once the mouse rolls out.
All goes well until you quickly move the mouse over and out of the element and...what?!
The jQuery('#something')
element suddenly gets resized back and forth many times until it
finally stops.
The solution is indeed simple, too simple, but the problem is so recurrent that I really consider this solution a useful one.
What you need to do in order to avoid this nasty effect is simply kill all existing animations on the element before you create a new one.
To do so, you have to use jQuery’s stop()
method. It will (as the name says) stop the current
animation and, optionally, remove the following ones as well.
I’ll show you an example of animating the opacity
CSS property, but it works the
same for any other property:
jQuery('#something').hover( function(){ jQuery(this).stop().animate({opacity:1}, 1000); }, function(){ jQuery(this).stop().animate({opacity:0.8}, 1000); } );
This also works for custom jQuery animations, like slideUp()
, slideDown()
, fadeIn()
,
etc.
This is the former example using the fade methods:
jQuery('#something').hover( function(){ jQuery(this).stop().fadeTo( 1, 1000 ); }, function(){ jQuery(this).stop().fadeTo( 0.8, 1000 ); } );
There’s still another related problem that could arise in a situation like this:
jQuery('#something').hover( function(){ jQuery(this).stop() .fadeTo( 1, 1000 ) .animate( {height:500}, 1000 ); }, function(){ jQuery(this).stop() .fadeTo( 0.8, 1000 ) .animate( {height:200}, 1000 ); } );
If you try this code and move the mouse quickly, the element will get animated crazily again, but only its height (not the opacity) will animate.
The reason is simple; jQuery animations get queued by default. This means that if you add several animations, they’ll get executed in sequence.
stop()
by default only
stops (and removes) the current animation. In our last example, only
the opacity animation will be removed each time, leaving the height
animation in the queue, ready to run.
To work around this, you have to either call stop()
one more time or pass true
as the first argument. This will make
stop()
clean all the queued
animations as well. Our hover code should look like this:
jQuery('#something').hover( function(){ jQuery(this).stop(true) .fadeTo( 1, 1000 ) .animate( {height:500}, 1000 ); }, function(){ jQuery(this).stop(true) .fadeTo( 0.8, 1000 ) .animate( {height:200}, 1000 ); } );
You’ve bound one or more event handlers, and they suddenly stop working.
It happens after new elements are added dynamically by an Ajax
request or simple jQuery operations (append()
, wrap()
, etc.).
This problem is incredibly common, and we’ve all fallen for this at least once.
I’ll explain the theory behind it in the “Discussion” section. If you feel you need to understand this well before heading to the solutions, check Why do event handlers get lost ? first.
There are two possible solutions for this recurring problem, each with its own pros and cons:
This approach requires you to call bind()
again and again, every time new
elements are added.
It’s pretty easy to implement and doesn’t require any plugin or new method.
You can simply have all the bindings in a function and call it again after each update.
It relies on event bubbling.[15] This is fast and light but requires a little understanding and can be (just) a little tricky at times.
Since jQuery 1.3, there’s built-in support for event
delegation. Using it is as simple as using the new live()
method instead of bind()
.
JavaScript, as opposed to CSS, isn’t a declarative language. You don’t describe behaviors, and they get “automagically” applied.
JavaScript, like most other programming languages, is imperative. The developer specifies a sequence of actions to perform, and they get applied as the line of code is reached.
When you add a piece of code like this:
function handler(){ alert('got clicked'), } jQuery('.clickable').bind('click', handler);
this is what you’re “doing”:
Look for all elements with a CSS class “clickable” and save it to the collection.
Bind the “handler” function to the click event of each element in the collection.
If JavaScript/jQuery were interpreted declaratively, the previous code would mean the following:
Each time an element with CSS class clickable
is clicked, run the function
handler
.
However, because JavaScript/jQuery is interpreted
imperatively, the only elements that will get bound are those that
match the selector at the time it is run. If you add new elements
with a clickable
class or you
remove the class from an element, the behaviors won’t be added or
removed for those elements.
Event delegation consists of binding once, at the start, and passively listening for events to be triggered. It relies on the fact that many events in the browser bubble up.
As an example, after you click a <div>
, its parent node receives the
click event as well, and then it passes to the parent’s parent and
so on, until it reaches the document
element.
Rebinding is simple: you just re-add the event handlers. It leads to new problems, such as adding event handlers to elements that were already bound. Some add CSS classes to work around this problem (marking those bound with a certain class).
All this requires CPU cycles every time the elements are updated and requires more and more event handlers to be created.
One way to work around both problems mentioned is to use named functions as event handlers. If you always use the same function, then you’ve solved the duplication problem, and the overhead is smaller.
Still, rebinding can lead to higher and higher amounts of RAM taken as time passes by.
Event delegation just requires an initial binding and there’s no need to deal with rebinding at all. This is quite a relief for the developer and makes the code shorter and clearer. The RAM problem mentioned before doesn’t apply to event delegation. The content of the page might change, but the active event handlers are always the same.
Event delegation has a catch, though. In order for it to
work, the code that handles it (live()
, a plugin or your own code) must
take the element that got the event (event.target
) and go through its
ancestors to see which ones have event handlers to trigger along
with some more processing. This means that, while event delegation
requires less binding, it requires more processing each time an
event is triggered.
Also, event delegation cannot be used with events that don’t
bubble, such as focus
and
blur
. For these events, there’s
a workaround that works cross-browser, using the focusin
and
focusout
events in some
browsers.
Event delegation seems like a nicer approach, but it requires extra processing.
My advice on this matter is to use live bindings just when you really need them. These are two common situations:
You have a list of DOM elements that changes dynamically.
Event delegation can work faster when you bind one live binding instead of, say, 100 from the regular ones. This is faster at the start and takes less memory.
If there’s no reason to use live()
, then just go for bind()
. If you then need to make it live,
switching should be just a matter of seconds.
18.118.152.58