These recipes will deal with edge case problems, advanced optimizations, and certain techniques to make your code cooler. These recipes are mostly for advanced developers who want to take their jQuery code one step further.
As in Chapter 8, I’ll refer to code as plugins, but that doesn’t mean it needs to be an actual plugin. If you don’t structure your code as jQuery plugins, then keep my naming convention in mind.
You are including jQuery dynamically into the page, by
either adding a <script>
element to the DOM or doing it some other way like Ajax.
Once jQuery is loaded, you expect everything to start working, but for some reason, no script starts.
You need to include an additional script to be executed after
jQuery is loaded. This script will simply call jQuery.ready()
. After you do this, everything will start working as
expected.
The jQuery.ready()
function
is called by jQuery’s core when the document is detected as ready.
Once called, all the document.ready
handlers are triggered
automatically.
You don’t need to worry about whether this function might
have been called already (for example, by the original detection),
triggering all the document.ready
handlers again.
jQuery.ready()
includes a
check for duplicated executions internally. Further calls will be
ignored.
The document.ready
detection is mostly based on events. Depending on the browser, a
certain event is bound, and it’s supposed to be triggered once the
document is ready.
In addition, the window.onload
event is bound for all
browsers as a fallback measure in case the other options
fail.
What was happening to you is that jQuery was finally loaded
into the page only after the window.onload
event; therefore, all the
event handlers remained bound, and none got triggered.
By calling jQuery.ready()
,
you’re “announcing” the document.ready
event manually, triggering
all the handlers and getting things back to normal.
Global event triggering implicates calling all the event handlers bound for a certain event, on all available elements.
It is performed by calling jQuery.trigger()
without passing any DOM element as context. It is
nearly the same as calling trigger()
on all the elements that have one
or more bindings to the corresponding event, something like
this:
jQuery('#a1,#a2,div.b5').trigger('someEvent'),
Triggering globally is obviously simpler because you don’t need to know all the elements that need to be triggered.
It’s quite useful for certain situations but can also be a slow process at times. Although it’s been optimized since jQuery 1.3, it still requires going through all the elements registered to jQuery’s event system. This can cause short (or not so short) hangs every time an event is triggered like this.
One possible solution is to have one or more global objects that will act as event listeners. These elements can be DOM elements or not. All global events will be bound and triggered on one of these elements.
Instead of doing something like this:
jQuery('#text1').bind('change-page', function(e, title){
jQuery(this).text( 'Page is ' + title );
});
jQuery('#text2').bind('change-page', function(e, title){
jQuery(this).text( 'At ' + title + ' Page' );
});
jQuery.trigger('change-page', 'Inbox'),
you’d do something like this:
jQuery.page = jQuery({}); // Just an empty object jQuery.page.bind('change', function(e, title){ jQuery('#text1').text( 'Page is ' + title ); }); jQuery.page.bind('change', function(e, title){ jQuery('#text2').text( 'At ' + title + ' Page' ); }); jQuery.page.trigger('change', 'Inbox'),
The syntax seems pretty much the same, but each call to trigger
won’t be iterating jQuery’s data
registry (aka jQuery.cache
).
Even if you decide to use a DOM element, the principle is the
same. DOM elements can be more appropriate at times. If, for example,
you’re creating a table-related plugin, then it’d make sense to use
each <table>
element as an
event listener.
The problem with DOM elements in many browsers is that they’re the main source of memory leaks. Memory leaks occur when there are certain amounts of RAM memory that cannot be freed by the JavaScript engine as the user leaves a page.
You should be much more careful about how you save data into the
objects when you use DOM elements. That’s why jQuery provides the
data()
method.
Still, I’d personally use regular JavaScript objects in most situations. You can add attributes and functions to them, and the likelihood (and magnitude) of memory leaks will be smaller.
As stated by the recipe title, this approach is faster. You
will be always triggering events on single objects, instead of the
n entries on jQuery.cache
.
The downside of this approach is that everyone needs to know
the event listener object (jQuery.page
in the example) in order to
bind or trigger one of its known events.
This can be negative if you’re aiming to keep your code encapsulated.[16]
The concept of encapsulation is highly enforced in object-oriented programming, where this is one of the things you should be very cautious about.
This is generally not such a great concern with jQuery programming, because it is not object oriented and most users don’t get too worried about code encapsulation. Still, it’s worth mentioning.
The listener objects mentioned don’t have to be simple dummy
objects with nothing but bind()
,
unbind()
, and trigger()
(as far as we’re
concerned).
These objects could actually have methods and attributes that would make them much more useful.
The only problem, though, is that if we do something like this:
jQuery.page = jQuery({ number:1 });
to access the number attribute, we would be forced to do this:
jQuery.page.number; // undefined jQuery.page[0].number; // 1
This is how jQuery works on HTML nodes and anything else.
But don’t give up on me yet! It’s easy to work around this. Let’s make a small plugin:
(function( $ ){ // These methods will be copied from jQuery.fn to our prototype var copiedMethods = 'bind unbind one trigger triggerHandler'.split(' '), // Empty constructor function Listener(){ }; $.each(copiedMethods, function(i,name){ Listener.prototype[name] = $.fn[name]; }); // Our "jQuery.fn.each" needs to be replaced Listener.prototype.each = function(fn) { fn.call(this); return this; }; $.listener = function( data ){ return $.extend(new Listener(), data); }; })( jQuery );
Now we can create objects that will have all the jQuery
methods we need that are related to events, but the scope of the
functions we pass to bind()
,
unbind()
, etc., will be the
object itself (jQuery.page
in our
example).
Note that our listener objects won’t have all jQuery methods but just the ones we copied. While you could add some more methods, most of them won’t work. That would require a more complex implementation; we’ll stick to this one, which satisfies our needs for events.
Now that we have this mini plugin, we can do this:
jQuery.page = jQuery.listener({ title: 'Start', changeTo: function( title ){ this.title = title; this.trigger('change'), } }); jQuery.page.changeTo('Inbox'),
Because you can now access the object from within the
handlers, using the this
, you
don’t need to pass certain values like the title as arguments to the
handler. Instead, you can simply use this.title
to access the value:
jQuery.page.bind('change', function(e){
jQuery('#text1').text( 'Page is ' + this.title );
});
Use jQuery.event.special
to do this. This feature requires an object with, at
least, a function that will be called each time your event is bound
for the time on each element and another function to clean up what you
did in the first place.
The syntax looks something like this:
jQuery.event.special.myEvent = { // Bind the custom event setup:function( data, namespaces ){ this; // The element being bound // return false to get the native binding, otherwise, // it will be skipped }, // Clean up teardown:function( namespaces ){ this; // The element being bound // Ditto about returning false } };
After you add your event behavior, you can do something like this:
jQuery('#some_element').bind('myEvent', {foo:'bar'}, function(){...});
After this, your setup()
function will be called.
As explained, your setup()
function will be called only when adding the first handler.
This is enough if the logic you’re encapsulating on this event doesn’t require some operations to be run each time a new binding is performed.
This option is provided by jQuery, but the approach has changed since jQuery 1.3.3.
If you’re using an older version, then you just need to use
jQuery.event.specialAll
instead of jQuery.event.special
. It will accept the
same kind of object, and your callbacks will receive the same
arguments. The only difference is that returning false won’t bring
any change.
As of jQuery 1.3.3, jQuery.event.specialAll
is gone. To
intercept all bindings for an event, you need to include an add()
(and optionally remove()
) function in your jQuery.event.special
namespace. The
functions will receive the handler that is about to be bound, and
can optionally return a new handler function to be used
instead.
Let’s make sure this is clear by writing a simple example; I’ll use the 1.3.3+ notation.
Let’s suppose you want to have an event triggered when an
element is selected (clicked) and it isn’t disabled. We’ll assume
that the item is disabled when it has the CSS class disabled
.
Here’s a way of doing that:
// Save these to make the code shorter // Don't do this within the global scope var event = jQuery.event; var $selected = event.special.selected = { setup:function( data ){ event.add(this, 'click', $selected.handler); return false; }, teardown:function(){ event.remove(this, 'click', $selected.handler); return false; }, handler:function(){ var $elem = jQuery(this); if( !$elem.hasClass('disabled') ) $elem.triggerHandler('selected'), } };
As you can see, we provide our own handler for selected
. Within the handler, we used
triggerHandler()
instead of
trigger()
because we don’t need
event bubbling, and there’s no default action to prevent, so we save
some needless processing.
jQuery.event.special
is a
great way of adding new behaviors without polluting the jQuery
namespace.
It doesn’t suit any situation, but it usually comes in handy
when you need a custom event that is based on another one (click
in our example). It’s also useful if
you have a plugin that simply binds events or simulates them; then
you can “mask” that plugin as a regular event.
jQuery’s core uses jQuery.event.special
to handle events
bound to the document.ready
event. Actually,
they’re stored as regular event handlers, but the first time you
bind to this event, you’re actually activating the (hacky) detection
code.
It is also used to transparently handle mouseenter
/mouseleave
events (those used by hover()
). All the DOM traversal operations
needed to achieve this are nicely hidden within the setup()
handlers.
There are also plugins that take advantage of jQuery.event.special
. Some of these are as
follows:
Checking these plugins can be a good start if you’re planning on adding new events to jQuery.
You need to allow other plugins (or just simple jQuery code) to chime in and modify certain variables before you perform the requested action.
Use events to notify other scripts about the action that is about to be carried out.
It is possible to get data that is gathered from the actioned event handlers.
If none is provided, you could then use some default option of your choice.
You’ll see how to do this according to what jQuery version you are using.
Since jQuery 1.3, with the addition of jQuery.Event
, this can be achieved in a nicer way. The old way
still works for triggerHandler()
but not for jQuery.trigger()
.
For the code we’ll see now, we will need to create a jQuery.Event
:
var e = jQuery.Event('updateName'),
Now, to call the handlers and retrieve the value, we’ll pass
the event object to trigger()
and then fetch the data
from the event object:
jQuery('#element').trigger(e);
alert( e.result ); // Charles
As I said at the beginning, this doesn’t work nicely when many handlers are bound and is, in general terms, a little unreliable and fragile.
So, how can we communicate event handlers and the function triggering the event?
The answer is, through the event object that we’re passing.
The jQuery.Event
object
passed to trigger()
will be the
same that is received by each handler as its first argument.
This means that we can do this:
jQuery('#name').bind('updateName', function(e){ e.name = this.value; }); var e = jQuery.Event('updateName'), jQuery('#name').trigger(e); alert( e.name );
This example doesn’t differ much from simply accessing
e.result
, but what about multiple
event handlers that operate on the same event object?
jQuery('#first').bind('update', function(e){ e.firstName = this.value; }); jQuery('#last').bind('update', function(e){ e.lastName = this.value; }); var e = jQuery.Event('update'), jQuery('#first, #last').trigger(e); alert( e.firstName ); alert( e.lastName );
We now have a way of allowing any number of event handlers to
provide needed information for a function to run. Needless to say,
you can call trigger()
several
times, passing the same event object.
As said before, it’d be wise to preset the event object with default values (if applicable). Your code shouldn’t rely on the fact that others did subscribe to a certain event.
If no default value can be used, then you can always abort the call or throw an error.
Older versions of jQuery only allowed the users to get a
single value, which would be returned when calling jQuery.trigger()
and/or triggerHandler()
.
It looked something like this:
jQuery('#element').bind('updateName', function(){
return 'Charles';
});
var name = jQuery('#element').triggerHandler('updateName'),
alert( name ); // Charles
This was OK until you had more than one event handler returning data. At that point, it was a matter of “who comes last” to decide which one’s data would be returned.
This is really a specialization of what we just saw. Event
objects, by design, have a method called preventDefault()
. This method is used on native events to abort common
actions like clicks on links, but it has no real use on custom
events.
We could take advantage of this and use this method to allow other scripts to prevent actions that are about to be performed.
I’ll now show you an example of an action. I’ll use the mini plugin introduced on Recipe 9.2, but that is certainly not a requirement to use this:
var remote = jQuery.listener({ request:function( url, callback ){ jQuery.ajax({ url:url, success:callback }); } }); // Do a request remote.request('contact.html', function( html ){ alert( html ); });
Now suppose we want to allow an external script to abort
certain requests when needed.
We need to modify remote.request
like this:
var remote = jQuery.listener({ request:function( url, callback ){ var e = jQuery.Event('beforeRequest'), e.url = url; this.trigger(e); if( !e.isDefaultPrevented() ) jQuery.ajax({ url:url, success:callback }); } });
e.isDefaultPrevented()
will
return whether e.preventDefault()
was ever called on this object.
Any external script can now do something like this:
remote.bind('beforeRequest', function(e){
if( e.url == 'contact.html' )
e.preventDefault();
});
Returning false (within the function) would have nearly the
same effect as calling e.preventDefault()
. It will also stop the
event propagation, which could be desirable.
Needless to say, in a situation like this, we could use what we learned before to allow the URL (or post data if added) to be modified by handlers.
You want your plugin to be controllable from the outside. One should be able to “tell” the plugin to do something at any time. There could be many instances (calls) of a plugin, but our action should only be executed in the context we provide.
One way to do this is indeed using events.
When a plugin is called, it binds functions on each matched element that once triggered, and it will perform the desired actions.
As a possible side solution, each time the plugin is called, instead of respecting the chaining, it could return an object that contains the bindings and allows external manipulation (use the plugin from Recipe 9.2).
This allows you to call the plugin many times on the same element without messing up the events.
We’ll now create a simple slideshow plugin. I’ll base this
plugin on an existing plugin of mine called jQuery.SerialScroll
.[19] I first thought of this approach when coding this
plugin, and, I must say, it worked pretty well.
We’ll name our plugin slideshow
. It will receive an <img>
element and an array of URLs,
and it will cycle the images. It will allow previous and next
movement, jumping to a certain image, and also autocycling.
Let’s start with the basics:
(function( $ ){ $.fn.slideshow = function(options){ return this.each(function(){ var $img = $(this), current = 0; // Add slideshow behavior... }); }; })( jQuery );
Now we’ll add a few local functions that will allow us to move to different images (URLs) in the collection:
function show( index ){ var total = options.images.length; while( index < 0 ) index += total; while( index >= total ) index −= total; current = index; $img.attr('src', options.images[index]); } function prev(){ show( current − 1 ); } function next(){ show( current + 1 ); }
We can now expose this functionality with events:
$img.bind('prev', prev).bind('next', next).bind('goto',function(e, index){ show( index ); });
What about autocycling? Let’s add a few more functions:
var auto = false, id; function start(){ stop(); auto = true; id = setTimeout(next, options.interval || 2000); } function stop(){ auto = false; clearTimeout(id); }
Here are the events:
$img.bind('start', start).bind('stop', stop);
We now need to add a few lines to show()
to keep autocycling if
needed:
function show( index ){ // Same as before... if( auto ) start(); }
And that’s it! We now have a full slideshow with prev
, next
, and autocycling.
In order to make the example clear, I made this plugin completely dependent on the outside manipulation.
Here’s a model implementation:
<ul> <li><img id="prev" src="prev.png" /></li> <li><img id="slideshow" /></li> <li><img id="next" src="next.png" /></li> </ul> ... (function( $ ){ var $image = $('#slideshow'), $image.slideshow({ images: ['1.jpg', '2.jpg', '3.jpg', '4.jpg'], interval: 3000 }); $('#prev').click(function(){ $image.trigger('prev'), }); $('#next').click(function(){ $image.trigger('next'), }); $image.trigger('goto', 0); // Initialize on 0 $image.trigger('start'), // We want auto cycling })( jQuery );
I used trigger()
because
it’s nice and short, but the truth is that it would be faster if
you used triggerHandler()
because trigger()
will generate
event bubbling (since jQuery 1.3) and you probably don’t need
it.
It could happen (although it’s strange) that the #slideshow
element could already have a
binding on an event by the name of prev
, next
, goto
, start
, or stop
.
If this is the case, check Recipe 8.4 for a solution.
Because I didn’t expose the bound functions, any other external code won’t be able to unbind them.
In most cases, you could simply unbind the whole events from the element, something like this:
jQuery('#slideshow').unbind('prev next goto start stop'), // Enumerate each event // or jQuery('#slideshow').unbind(); // Blindly remove them all
If you need a cautious unbinding or if you simply want to unbind all related events, check Recipe 8.3.
There are other existing techniques to allow external manipulation. I’ll compare some:
This pattern is used by jQuery UI (among others). It consists of executing actions when the plugin is passed a string as the first argument, for example:
jQuery('#image').slideshow('goto', 1);
This is a little shorter than using events, but the whole
approach requires you to save all the needed data (the current
index in our case) in a public way, so it can be retrieved
afterward. People who use this pattern also tend to use data()
to store the variables.
If you use events, you can simply use local variables because your event handlers have access to the plugin’s local scope.
This pattern is used by the validate
plugin from Jörn Zaefferer (and
others too).
Depending on how it is coded, the object’s methods could be able to access local variables. To do so, it must use closures,[20] which aren’t a nice thing to abuse. Also, you need to store this object somewhere (globally). This also requires you to do pseudo-object-oriented code (which you may like or not).
You could create some kind of hybrid between this approach
and the one I explained. Instead of binding the events (prev
, next
, etc.) to the DOM element, you
could create an object (using jQuery.listener
) and bind the events to
it; then it could be returned. As we saw in Recipe 9.2, this listener
object wouldn’t be restricted to events. It could have methods and
even data saved into its attributes.
You want to perform a certain action when a DOM element gets modified using jQuery. This could involve changing an attribute such as a CSS property, removing it from the document, etc.
Some browsers already support mutation events,[21] which would serve this need, but they’re not something you can use in a cross-browser fashion yet, and they aren’t integrated with jQuery.
Another thing you might ever need is to modify the arguments passed to jQuery methods before they’re executed. On the same principle, you could need to alter the data returned by a method after the executing of the function itself.
This is somehow related to aspect-oriented programming,[22] but here we won’t be nesting functions; instead, we’ll overload the desired method once and trigger events every time the method is called.
We’ll need one event to be triggered before the function is run to allow the arguments to be changed. We’ll also need an event after the function is run so we can retrieve the returned data and even change it if necessary.
Let’s see how to code this as a plugin. I’ll show you each step separately.
First, let’s create a function that replaces jQuery methods
with our own function. I’ll name it jQuery.broadcast()
; you can change its
name if you prefer something else:
(function($){ $.broadcast = function(name){ // Save the original method var old = $.fn[name]; $.fn[name] = function(){ // Broadcast }; }; })(jQuery);
name
needs to be the method
name we want to override, for example:
jQuery.broadcast('addClass'),
Now that we have put our own function as the jQuery method, let’s see how to trigger an event that will allow us to change the incoming arguments:
// Create an event object var e = $.Event('before-'+name); // Save the arguments into the object e.args = $.makeArray(arguments); // Trigger the event this.trigger(e);
Assuming you’re broadcasting addClass()
, we can now do this:
jQuery('body').bind('before-addClass',function(e){ e.args[0]; // The CSS class });
An event is now triggered, but we still have to call the old
addClass()
. We’ll save the
returned data into the event object as well so we can expose it
later when we trigger the other event.
e.ret = old.apply(this, e.args);
As you can see, we don’t pass the original arguments
array; instead, we use the one
we exposed in case it was modified in some way.
We now have the returned data saved in our event object. We can now trigger the final event, allowing external modification of the returned data.
We’ll reuse the same event object, but we’ll change the event’s name.
e.type = 'after-'+name; this.trigger(e);
All what’s left now is to return the resulting data and
continue with the normal execution. We’ll give out what we saved on
e.ret
that could have been
modified by an event handler:
return e.ret;
This is the completed code we’ve developed:
(function($){ $.broadcast = function(name){ var old = $.fn[name]; $.fn[name] = function(){ var e = $.Event('before-'+name); e.args = $.makeArray(arguments); this.trigger(e); e.ret = old.apply(this, e.args); e.type = 'after-'+name; this.trigger(e); return e.ret; }; }; })(jQuery);
I tried to keep the example short to illustrate the point. There are a couple of things you can do to improve it; here are a few ideas:
Use triggerHandler()
instead of trigger()
: if you don’t need the events to bubble, you
could simply use triggerHandler()
. This will make the
whole process faster; note that triggerHandler()
only triggers the
event on the first element of the collection.
Run the process on each element separately: in the
previous example, trigger()
is called on the whole collection at once. That will be OK for
most cases but can yield unexpected results when used on
collections with multiple elements.
You could wrap what we put inside the function with a call
to map()
. That should make the code work once per
element.
The downside is that it will be slightly slower and will
also generate an (unexpected) stack entry (pushStack()
) due to the call to
map()
.
Allow external code to prevent normal execution: if you’re
using jQuery 1.3 or higher, you could take advantage of the
methods for jQuery.Event
.
You can “ask” the event object whether someone called its
preventDefault()
method using
e.isDefaultPrevented()
.
If this returns true
,
then you don’t call the original function.
Avoid multiple overloads of the same jQuery method: this one is pretty simple; just create an internal object literal where you keep track of which methods were overloaded. Then you just ignore repeated calls.
Integrate this with jQuery.event.special
: this one will save you from calling jQuery.broadcast()
for each method you want to overload.
Instead, you add an entry to jQuery.event.special
for each method,
and you internally call jQuery.broadcast()
when someone binds
an event. This should be combined with the check for duplicated
calls.
You have objects with methods and attributes, and you want to pass those methods (functions) as event handlers. The problem is that once you do this, the method will “lose the reference” to the object, and you have no way of referencing the object within the event handlers.
This used to be quite complicated to achieve. It required you to
generate closures that would encapsulate the object and then pass them
to bind()
.
Since jQuery 1.3.3, a new parameter has been added to bind()
. It allows you to specify an object
as the scope or this
of the event
handler without using function closures.
This makes the required code both shorter and faster. You can now pass the object’s method as the function and the object itself as the scope.
You will surely wonder this sooner or later. I said before
that when you pass a scope object to bind()
, the this
of the event handler will be
overridden. This means we can’t retrieve the node as we always
do...but the node is not lost.
When you pass a scope object to the bind() method, events will
be delivered with the this
of the
event handler set to the scope object. You can still determine the
element being delivered to the event by using the event.currentTarget
property, which
contains a reference to the DOM element.
It’s usually not needed because using the this
is shorter, but in situations like
this, it’s the only way around.
I’ll create a small example that should illustrate how to use the scope parameter and also show you a situation where it is useful.
For the example, we’ll need two objects. Each will have a method that we want to bind as an event handler.
These are the objects:
function Person(name){ this.name = name; this.married = false; } jQuery.extend( Person.prototype, { whatIsYourName: function(){ alert(this.name); }, updateMarriedState: function(e){ var checkbox = e.currentTarget; this.married = checkbox.checked; } }); var peter = new Person('Peter'), var susan = new Person('Susan'),
Let’s suppose we have some sort of form and it has two
checkboxes (#c1
and #c2
). Each will manipulate the
married state of one of our previous
objects.
jQuery('#c1').bind('change', peter.updateMarriedState, peter); jQuery('#c2').bind('change', susan.updateMarriedState, susan);
Thanks to the scope attribute, we don’t need to create new functions for each binding; we can use the objects’ methods instead.
The methods don’t even need to be attached to the objects in the first place. You could do something like this:
function updatePersonMarriedState(e){ var checkbox = e.currentTarget; this.married = checkbox.checked; } jQuery('#c1').bind('change', updatePersonMarriedState, peter); jQuery('#c2').bind('change', updatePersonMarriedState, susan);
As you can see, you’re not really forced to put those
functions into the objects’ prototype, and it could actually make
more sense to keep them separated. Why should a method belonging
to Person
know about checkboxes
and the node? It’s probably nicer to keep all the specific DOM
manipulation apart from the data.
In some cases, the object’s method won’t need to know about the node or the event object at all. When this happens, we can bind a method directly, and we won’t be mixing DOM and data at all.
If we had to have make two buttons (#b1
and #b2
) to display the name of one person
when clicked, then it’d be as simple as this:
jQuery('#b1').bind('click', peter.whatIsYourName, peter); jQuery('#b2').bind('click', susan.whatIsYourName, susan);
It’s worth mentioning that both methods are actually the same:
peter.whatIsYourName == susan.whatIsYourName; // true
The function is created only once and saved into Person.prototype
.
3.145.202.61