Often, when thinking and talking about jQuery, the main concepts that come to mind are DOM and style manipulation and behavior (events). Yet there are also a number of “core” features and utility functions tucked away for the developer’s benefit. This chapter is focused on exposing, disclosing, and explaining these not-so-common utility methods of jQuery.
You need to attach a special click
handler to all anchor tags that have just a hash for the current
page, and you don’t want to risk it breaking because of browser
support issues.
(function($) { $(document).ready(function() { $('a') .filter(function() { var href = $(this).attr('href'); // Normalize the URL if ( !jQuery.support.hrefNormalized ) { var loc = window.location; href = href.replace( loc.protocol + '//' + loc.host + loc.pathname, ''); } // This anchor tag is of the form <a href="#hash"> return ( href.substr(0, 1) == '#' ); }) .click(function() { // Special click handler code }); }); })(jQuery);
The jQuery.support
object
was added in version 1.3 and contains Boolean flags to
help write code using browser feature detection. In our example,
Internet Explorer (IE) has a different behavior in how it handles the href
attribute. IE will return the full URL
instead of the exact href
attribute. Using the hrefNormalized
attribute, we have future-proofed our solution in the event that a
later version of IE changes this behavior. Otherwise, we would have
needed a conditional that contained specific browser versions. While
it may be tempting, it is best to avoid this approach because it
requires future maintenance as new versions of browsers are released.
Another reason to avoid targeting specific browsers is that it is
possible for clients to intentionally or unintentionally report an
incorrect user agent string. In addition to the hrefNormalized
attribute, a number of
additional attributes exist:
boxModel
True if the browser renders according to the W3C CSS box model specification
cssFloat
True if
style.cssFloat
is used to get the current CSS float valuehrefNormalized
True if the browser leaves intact the results from
getAttribute('href')
htmlSerialize
True if the browser properly serializes link elements with the
innerHTML
attributeleadingWhitespace
True if the browser preserves leading whitespace when
innerHTML
is usednoCloneEvent
True if the browser does not clone event handlers when elements are cloned
objectAll
True if
getElementsByTagName('*')
on an element returns all descendant elementsopacity
scriptEval
True if using
appendChild
for a<script>
tag will execute the scriptstyle
True if
getAttribute('style')
is able to return the inline style specified by an elementtbody
True if the browser allows
<table>
elements without a<tbody>
element
(function($) { $(document).ready(function() { var months = [ 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']; $.each(months, function(index, value) { $('#months').append('<li>' + value + '</li>'); }); var days = { Sunday: 0, Monday: 1, Tuesday: 2, Wednesday: 3, Thursday: 4, Friday: 5, Saturday: 6 }; $.each(days, function(key, value) { $('#days').append('<li>' + key + ' (' + value + ')</li>'); }); }); })(jQuery);
In this recipe, we iterate over both an array and an object
using $.each()
, which provides an elegant interface to the common task
of iteration. The first argument to the $.each()
method is the array or object to
iterate over, with the second argument being the callback method that
is executed for each element. (Note that this is slightly different
from the jQuery collection method $('div').each()
, whose first argument is the
callback function.)
When the callback function defined by the developer is executed,
the this
variable is set to the
value of the element currently being iterated. Thus, the previous
recipe could be rewritten as follows:
(function($) { $(document).ready(function() { var months = [ 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']; $.each(months, function() { $('#months').append('<li>' + this + '</li>'); }); var days = { Sunday: 0, Monday: 1, Tuesday: 2, Wednesday: 3, Thursday: 4, Friday: 5, Saturday: 6 }; $.each(days, function(key) { $('#days').append('<li>' + key + ' (' + this + ')</li>'); }); }); })(jQuery);
(function($) { $(document).ready(function() { var months = [ 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']; months = $.grep(months, function(value, i) { return ( value.indexOf('J') == 0 ); }); $('#months').html( '<li>' + months.join('</li><li>') + '</li>' ); }); })(jQuery);
This recipe uses the $.grep()
method to filter the months
array so that it only includes entries that begin with the capital
letter J
. The $.grep
method returns the filtered array.
The callback method defined by the developer takes two arguments and
is expected to return a Boolean value of true
to keep an element or false
to have it removed. The first argument
specified is the value of the array element (in this case, the month),
and the second argument passed in is the incremental value of the
number of times the $.grep()
method
has looped. So, for example, if you want to remove every other month,
you could test whether ( i % 2 ) ==
0
, which returns the remainder of i / 2. (The %
is the modulus operator, which returns the
remainder of a division operation. So, when i
= 4
, i
divided by
2
has a remainder of 0
.)
(function($) { $(document).ready(function() { var months = [ 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']; months = $.grep(months, function(value, i) { return ( i % 2 ) == 0; }); $('#months').html( '<li>' + months.join('</li><li>') + '</li>' ); }); })(jQuery);
(function($) { $(document).ready(function() { var months = [ 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']; months = $.map(months, function(value, i) { return value.substr(0, 3); }); $('#months').html( '<li>' + months.join('</li><li>') + '</li>' ); }); })(jQuery);
In this recipe, $.map()
is iterating over the months
array and returns the abbreviation
(first three characters). The $.map()
method takes an array and a callback
method as arguments and iterates over each array element executing the
callback as defined by the developer. The array entry will be updated
with the return value of the callback.
You have two jQuery DOM collections that need to have duplicate elements removed:
(function($) { $(document).ready(function() { var animals = $('li.animals').get(); var horses = $('li.horses').get(); $('#animals') .append( $(animals).clone() ) .append( $(horses).clone() ); }); })(jQuery);
(function($) { $(document).ready(function() { var animals = $('li.animals').get(); var horses = $('li.horses').get(); var tmp = $.merge( animals, horses ); tmp = $.unique( tmp ); $('#animals').append( $(tmp).clone() ); }); })(jQuery);
jQuery’s $.unique()
function
will remove duplicate DOM elements from an array or
collection. In the previous recipe, we combine the animals
and horses
arrays using $.merge()
. jQuery makes use of $.unique()
throughout most of its core and
internal functions such as .find()
and .add()
. Thus, the most common
use case for this method is when operating on an array of elements not
constructed with jQuery.
You have written a plugin and need to test whether one of the settings is a valid callback function.
(function($) { $.fn.myPlugin = function(settings) { return this.each(function() { settings = $.extend({ onShow: null }, settings); $(this).show(); if ( $.isFunction( settings.onShow ) ) { settings.onShow.call(this); } }); }; $(document).ready(function() { $('div').myPlugin({ onShow: function() { alert('My callback!'); } }); }); })(jQuery);
While the JavaScript language provides the typeof
operator, inconsistent results and edge cases across web browsers
need to be taken into account. jQuery provides the .isFunction()
method to ease the developer’s job. Worth pointing out is that
since version 1.3, this method works for user-defined functions and
returns inconsistent results with built-in language functions such as
this:
jQuery.isFunction( document.getElementById );
which returns false in versions of Internet Explorer.
You have an input form and need to remove the whitespace that a user may have entered at either the beginning or end of a string.
<input type="text" name="first_name" class="cleanup" /> <input type="text" name="last_name" class="cleanup" /> (function($) { $(document).ready(function() { $('input.cleanup').blur(function() { var value = $.trim( $(this).val() ); $(this).val( value ); }); }); })(jQuery);
Upon the user blurring a field, the value as entered by the
user—$(this).val()
—is retrieved and
passed through the $.trim()
method
that strips all whitespace characters (space, tab, and
newline characters) from the beginning and end of the string. The
trimmed string is then set as the value of the input field
again.
var node = document.getElementById('myId'); node.onclick = function() { // Click handler }; node.myObject = { label: document.getElementById('myLabel') };
you have metadata associated with a DOM element for easy reference. Because of flawed garbage collection implementations of some web browsers, the preceding code can cause memory leaks.
Properties added to an object or DOM node at runtime (called
expandos) exhibit a number of issues because of
flawed garbage collection implementations in some web browsers. jQuery
provides developers with an intuitive and elegant method called
.data()
that aids developers in avoiding memory leak issues
altogether:
$('#myId').data('myObject', { label: $('#myLabel')[0] }); var myObject = $('#myId').data('myObject'); myObject.label;
In this recipe, we use the .data()
method, which manages access to our
data and provides a clean separation of data and markup.
One of the other benefits of using the data()
method is that it implicitly triggers
getData
and setData
events on the target element. So, given the following
HTML:
<div id="time" class="updateTime"></div>
we can separate our concerns (model and view) by attaching a
handler for the setData
event, which receives three
arguments (the event
object, data
key
, and data value
):
// Listen for new data $(document).bind('setData', function(evt, key, value) { if ( key == 'clock' ) { $('.updateTime').html( value ); } });
The setData
event is then
triggered every time we call .data()
on the document element:
// Update the 'time' data on any element with the class 'updateTime' setInterval(function() { $(document).data('clock', (new Date()).toString() ); }, 1000);
So, in the previous recipe, every 1 second (1,000 milliseconds)
we update the clock
data property
on the document
object, which
triggers the setData
event bound to
the document
,
which in turn updates our display of the current time.
You have developed a plugin and need to provide default options allowing end users to overwrite them.
(function($) { $.fn.myPlugin = function(options) { options = $.extend({ message: 'Hello world', css: { color: 'red' } }, options); return this.each(function() { $(this).css(options.css).html(options.message); }); }; })(jQuery);
In this recipe, we use the $.extend()
method provided by jQuery. $.extend()
will return a reference to the
first object passed in with the latter objects overwriting any
properties they define. The following code demonstrates how this works
in practice:
var obj = { hello: 'world' }; obj = $.extend(obj, { hello: 'big world' }, { foo: 'bar' }); alert( obj.hello ); // Alerts 'big world' alert( obj.foo ); // Alerts 'bar';
This allows for myPlugin()
in
our recipe to accept an options
object that will overwrite our default settings. The following code
shows how an end user would overwrite the default CSS color
setting:
$('div').myPlugin({ css: { color: 'blue' } });
One special case of the $.extend()
method is that when given a
single object, it will extend the base jQuery object. Thus, we could
define our plugin as follows to extend the jQuery core:
$.fn.extend({ myPlugin: function() { options = $.extend({ message: 'Hello world', css: { color: 'red' } }, options); return this.each(function() { $(this).css(options.css).html(options.message); }); } });
$.extend()
also provides a
facility for a deep (or recursive) copy. This is accomplished by
passing in Boolean true
as the
first parameter. Here is an example of how a deep copy would
work:
var obj1 = { foo: { bar: '123', baz: '456' }, hello: 'world' }; var obj2 = { foo: { car: '789' } }; var obj3 = $.extend( obj1, obj2 );
Without passing in true
,
obj3
would be as follows:
{ foo: { car: '789 }, hello: 'world' }
If we specify a deep copy, obj3
would be as follows after recursively
copying all properties:
var obj3 = $.extend( true, obj1, obj2 ); // obj3 { foo: { bar: '123', baz: '456', car: '789 }, hello: 'world' }
Get jQuery Cookbook now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.