At the very core of jQuery is its selector engine, allowing you to select elements within any document based on names, attributes, states, and more. Because of CSS’s popularity, it made sense to adopt its selector syntax to make it simple to select elements in jQuery. As well as supporting most of the selectors specified in the CSS 1–3 specifications, jQuery adds quite a few custom selectors that can be used to select elements based on special states and characteristics. Additionally, you can create your own custom selectors! This chapter discusses some of the more common problems encountered while selecting elements with jQuery.
Before the first recipe, let’s discuss a few basic principles.
The easiest way to target a specific element or a set of elements within a document is by using a CSS selector within the jQuery wrapper function, like so:
jQuery('#content p a'), // Select all anchor elements within all paragraph elements within #content
Now that we’ve selected the elements we’re after, we can run any
of jQuery’s methods on that collection. For example, adding a class of
selected
to all links is as simple
as:
jQuery('#content p a').addClass('selected'),
jQuery offers many DOM traversal methods to aid in the element selection process, such as next()
, prev()
, and parent()
. These and other methods accept a
selector expression as their only parameter, which filters the returned
results accordingly. So, you can use CSS selectors in a number of
places, not just within jQuery(...)
.
When constructing selectors, there’s one general rule for optimization: be only as specific as you need to be. It’s important to remember that the more complicated a selector is, the more time it will take jQuery to process the string. jQuery uses native DOM methods to retrieve the elements you’re after. The fact that you can use selectors is only a product of a nicely polished abstraction; there’s nothing wrong with this, but it is very important to understand the ramifications of what you’re writing. Here is a typical example of an unnecessarily complicated selector:
jQuery('body div#wrapper div#content'),
A higher degree of specificity does not necessarily mean it’s faster. The previous selector can be rewritten to this:
jQuery('#content'),
This has the same effect but manages to shave off the overhead of the previous version. Also note that sometimes you can further optimize by specifying a context for your selectors; this will be discussed later in the chapter (see Recipe 2.11).
Use the direct descendant combinator
(>
). This combinator expects two selector expressions, one
on either side. For example, if you want to select all anchor elements
that reside directly beneath list items, you could use this selector:
li > a
. This would select all
anchors that are children of a list item; in other words, all anchors
that exist directly beneath list items. Here’s an example:
<a href="/category">Category</a> <ul id="nav"> <li><a href="#anchor1">Anchor 1</a></li> <li><a href="#anchor2">Anchor 2</a></li> <li><span><a href="#anchor3">Anchor 3</a></span></li> </ul>
Now, to select only the anchors within each list item, you would call jQuery like so:
jQuery('#nav li > a'), // This selects two elements, as expected
The third anchor within the #nav
list is not selected because it’s not a
child of a list item; it’s a child of a <span>
element.
It’s important to distinguish between a child and a descendant. A descendant is any element existing within another element, whereas a child is a direct descendant; the analogy of children and parents helps massively since the DOM’s hierarchy is largely similar to that.
It’s worth noting that combinators like >
can be used without an expression on
the left side if a context is already specified:
jQuery('> p', '#content'), // Fundamentally the same as jQuery('#content > p')
Selecting children in a more programmatic environment should be
done using jQuery’s children()
method, to which you can pass a selector to filter the returned
elements. This would select all direct children of the #content
element:
jQuery('#content').children();
The preceding code is essentially the same as jQuery('#content > *')
with one important
difference; it’s faster. Instead of parsing your selector, jQuery
knows what you want immediately. The fact that it’s faster is not a
useful differential, though. Plus, in some situations, the speed
difference is marginal verging on irrelevant, depending on the browser
and what you’re trying to select. Using the children()
method is especially useful when
you’re dealing with jQuery objects stored under variables. For
example:
var anchors = jQuery('a'), // Getting all direct children of all anchor elements // can be achieved in three ways: // #1 anchors.children(); // #2 jQuery('> *', anchors); // #3 anchors.find('> *'),
In fact, there are even more ways of achieving it! In this
situation, the first method is the fastest. As stated earlier, you can
pass a selector expression to the children()
method to filter the
results:
jQuery('#content').children('p'),
Only paragraph elements that are direct children of #content
will be returned.
If you’re looking to select the adjacent sibling of a particular
element, then you can use the adjacent sibling
combinator (+
). Similar to the child (>
) combinator, the sibling combinator
expects a selector expression on each side. The righthand expression
is the subject of the selector, and the lefthand expression is the
sibling you want to match. Here’s some example HTML markup:
<div id="content"> <h1>Main title</h1> <h2>Section title</h2> <p>Some content...</p> <h2>Section title</h2> <p>More content...</p> </div>
If you want to select only <h2>
elements that immediately follow
<h1>
elements, you can use
the following selector:
jQuery('h1 + h2'), // Selects ALL H2 elements that are adjacent siblings of H1 elements
In this example, only one <h2>
element will be selected (the
first one). The second one is not selected because, while it is a
sibling, it is not an adjacent sibling of the
<h1>
element.
If, on the other hand, you want to select and filter all
siblings of an element, adjacent or not, then you can use jQuery’s
siblings()
method to target them, and you can pass an optional selector
expression to filter the selection:
jQuery('h1').siblings('h2,h3,p'), // Selects all H2, H3, and P elements that are siblings of H1 elements.
Sometimes you’ll want to target siblings dependent on their position relative to other elements; for example, here’s some typical HTML markup:
<ul> <li>First item</li> <li class="selected">Second item</li> <li>Third item</li> <li>Fourth item</li> <li>Fifth item</li> </ul>
To select all list items beyond the second (after li.selected
), you could use the following
method:
jQuery('li.selected').nextAll('li'),
The nextAll()
method, just like siblings()
, accepts a selector expression to
filter the selection before it’s returned. If you don’t pass a
selector, then nextAll()
will
return all siblings of the subject element that exist after the
subject element, although not before
it.
With the preceding example, you could also use another CSS
combinator to select all list items beyond the second. The
general sibling combinator (~
) was added in CSS3, so you probably haven’t been able to
use it in your actual style sheets yet, but fortunately you can use it
in jQuery without worrying about support, or lack thereof. It works in
exactly the same fashion as the adjacent sibling combinator (+) except
that it selects all siblings that follow, not
just the adjacent one. Using the previously specified markup, you
would select all list items after li.selected
with the following
selector:
jQuery('li.selected ~ li'),
The adjacent sibling combinator can be conceptually tricky to use because it doesn’t follow the top-down hierarchical approach of most other selector expressions. Still, it’s worth knowing about and is certainly a useful way of selecting what you want with minimal hassle.
The same functionality might be achieved without a selector, in the following way:
jQuery('h1').next('h2'),
The next()
method can make a nice alternative to the selector syntax,
especially in a programmatic
setting when you’re dealing with jQuery objects as variables, for
example:
var topHeaders = jQuery('h1'), topHeaders.next('h2').css('margin','0'),
Depending on what you want to do, you have the following filters at your disposal. These may look like CSS pseudoclasses, but in jQuery they’re called filters:
:first
Matches the first selected element
:last
Matches the last selected element
:even
Matches even elements (zero-indexed)
:odd
Matches odd elements (zero-indexed)
:eq(n
)
Matches a single element by its index (n)
:lt(n
)
Matches all elements with an index below n
:gt(n
)
Matches all elements with an index above n
Assuming the following HTML markup:
<ol> <li>First item</li> <li>Second item</li> <li>Third item</li> <li>Fourth item</li> </ol>
the first item in the list could be selected in a number of different ways:
jQuery('ol li:first'), jQuery('ol li:eq(0)'), jQuery('ol li:lt(1)'),
Notice that both the eq()
and lt()
filters accept a number; since it’s zero-indexed, the first
item is 0, the second is 1, etc.
A common requirement is to have alternating styles on table
rows; this can be achieved with the :even
and :odd
filters:
<table> <tr><td>0</td><td>even</td></tr> <tr><td>1</td><td>odd</td></tr> <tr><td>2</td><td>even</td></tr> <tr><td>3</td><td>odd</td></tr> <tr><td>4</td><td>even</td></tr> </table>
You can apply a different class dependent on the index of each table row:
jQuery('tr:even').addClass('even'),
You’d have to specify the corresponding class (even
) in your CSS style sheet:
table tr.even { background: #CCC; }
This code would produce the effect shown in Figure 2-1.
As mentioned, an element’s index is zero-based, so if an element is the first one, then its index is zero. Apart from that fact, using the preceding filters is very simple. Another thing to note is that these filters require a collection to match against; the index can be determined only if an initial collection is specified. So, this selector wouldn’t work:
jQuery(':even'),
Actually, this selector does work, but only because jQuery
does some corrective postprocessing of your selector behind the
scenes. If no initial collection is specified, then jQuery will
assume you meant all elements within the document. So, the selector
would actually work, since it’s effectively identical to this:
jQuery('*:even')
.
An initial collection is required on the lefthand side of the filter, i.e., something to apply the filter to. The collection can be within an already instantiated jQuery object, as shown here:
jQuery('ul li').filter(':first'),
The filter method is being run on an already instantiated jQuery object (containing the list items).
jQuery offers a convenient filter for this very purpose. The
:animated
filter will match only elements that are currently
animating:
jQuery('div:animated'),
This selector would select all <div>
elements currently animating.
Effectively, jQuery is selecting
all elements that have a nonempty animation queue.
This filter is especially useful when you need to apply a
blanket function to all elements that are not currently animated. For
example, to begin animating all <div>
elements that are not already
animating, it’s as simple as this:
jQuery('div:not(div:animated)').animate({height:100});
Sometimes you might want to check whether an element is
animating. This can be done with jQuery’s useful is()
method:
var myElem = jQuery('#elem'), if( myElem.is(':animated') ) { // Do something. }
There are normally only two things you would want to query in
this respect: the text contents and the element contents (other
elements). For the former, you can use the :contains()
filter:
<!-- HTML --> <span>Hello Bob!</span> // Select all SPANs with 'Bob' in: jQuery('span:contains("Bob")'),
Note that it’s case sensitive, so this selector wouldn’t match anything if we searched for bob (with a lowercase b). Also, quotes are not required in all situations, but it’s a good practice just in case you encounter a situation where they are required (e.g., when you want to use parentheses).
To test for nested elements, you can use the :has()
filter. You can pass any valid selector to this filter:
jQuery('div:has(p a)'),
This selector would match all
elements that
encapsulate <div>
<a>
elements
(anchors) within <p>
elements
(paragraphs).
The :contains()
filter might
not fit your requirements. You may need more control over what text to
allow and what to disallow. If you need that control, I suggest using
a regular expression and testing against the text of the element, like
so:
jQuery('p').filter(function(){ return /(^|s)(apple|orange|lemon)(s|$)/.test(jQuery(this).text()); });
This would select all paragraphs containing the word
apple, orange, or
lemon. To read more about jQuery’s filter()
method, have a look at Recipe 2.10.
For this, jQuery gives us the :not
filter, which you can use in the following way:
jQuery('div:not(#content)'), // Select all DIV elements except #content
This filter will remove any elements from the current collection that are matched by the passed selector. The selector can be as complex as you like; it doesn’t have to be a simple expression, e.g.:
jQuery('a:not(div.important a, a.nav)'), // Selects anchors that do not reside within 'div.important' or have the class 'nav'
Passing complex selectors to the :not
filter is possible only in jQuery
version 1.3 and beyond. In versions previous to that, only simple
selector expressions were acceptable.
In addition to the mentioned :not
filter, jQuery also supplies a method
with very similar functionality. This method accepts both selectors
and DOM collections/nodes. Here’s an example:
var $anchors = jQuery('a'), $anchors.click(function(){ $anchors.not(this).addClass('not-clicked'), });
According to this code, when an anchor is clicked, all anchors
apart from that one will have the class not-clicked
added. The this
keyword refers to the clicked element.
The not()
method also accepts
selectors:
$('#nav a').not('a.active'),
This code selects all anchors residing within #nav
that do not have a class of active
.
You can use either the :hidden
or :visible
filter as necessary:
jQuery('div:hidden'),
Here are some other examples of usage:
if (jQuery('#elem').is(':hidden')) { // Do something conditionally } jQuery('p:visible').hide(); // Hiding only elements that are currently visible
Since jQuery 1.3.2, these filters have dramatically changed.
Before 1.3.2 both filters would respond like you would expect for
the CSS visibility
property, but that is no longer taken into account. Instead,
jQuery tests for the height and width of the element in question
(relative to its offsetParent
).
If either of these dimensions is zero, then the element is
considered hidden; otherwise, it’s considered visible.
If you need more control, you can always use jQuery’s filter()
method, which allows you to test the element in any way you
want. For example, you may want to select all elements that are set to
display:none
but not those that are
set to visibility:hidden
. Using the
:hidden
filter won’t work because
it matches elements with either of those characteristics (< v1.3.2)
or doesn’t take either property into consideration at all (>= v1.3.2):
jQuery('*').filter(function(){ return jQuery(this).css('display') === 'none' && jQuery(this).css('visibility') !== 'hidden'; });
The preceding code should leave you with a collection of
elements that are set to display:none
but not visibility:hidden
. Note that, usually, such
a selection won’t be necessary—the :hidden
filter is perfectly suitable in most
situations.
Use an attribute selector to match specific attributes and corresponding values:
jQuery('a[href="http://google.com"]'),
The preceding selector would select all anchor elements with an
href
attribute equal to the value
specified (http://google.com
).
There are a number of ways you can make use of the attribute selector:
[attr]
Matches elements that have the specified attribute
[attr=val]
Matches elements that have the specified attribute with a certain value
[attr!=val]
Matches elements that don’t have the specified attribute or value
[attr^=val]
Matches elements with the specified attribute and that start with a certain value
[attr$=val]
Matches elements that have the specified attribute and that end with a certain value
[attr~=val]
Matches elements that contain the specified value with
spaces, on either side (i.e., car
matches car
but not cart
)
Prior to jQuery 1.2 you had to use XPath syntax (i.e., putting
an @
sign before an attribute
name). This is now deprecated.
You can also combine multiple attribute selectors:
// Select all elements with a TITLE and HREF: jQuery('*[title][href]'),
As always, for special requirements it may be more suitable to
use the filter()
method to more specifically outline what you’re looking
for:
jQuery('a').filter(function(){ return (new RegExp('http://(?!' + location.hostname + ')')).test(this.href); });
In this filter, a regular expression is being used to test the
href
attribute of each anchor. It
selects all external links within any page.
The attribute selector is especially useful for selecting elements based on slightly varying attributes. For example, if we had the following HTML:
<div id="content-sec-1">...</div> <div id="content-sec-2">...</div> <div id="content-sec-3">...</div> <div id="content-sec-4">...</div>
we could use the following selector to match all of the <div>
elements:
jQuery('div[id^="content-sec-"]'),
jQuery gives us a bunch of useful filters for this very purpose, as shown in Table 2-1.
jQuery selector syntax | Selects what? |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
So, as an example, if you needed to select all text inputs, you
would simply do this: jQuery(':text'),
There is also an :input
filter that selects all input
,
textarea
, button
, and select
elements.
Note that the :hidden
filter,
as discussed earlier, does not test for the type hidden
; it works by checking the computed
height of the element. This works with input elements of the type
hidden
because they, like other
hidden elements, have an offsetHeight
of
zero.
As with all selectors, you can mix and match as desired:
jQuery(':input:not(:hidden)'), // Selects all input elements except those that are hidden.
These filters can also be used with regular CSS syntax. For
example, selecting all text input elements plus all <textarea>
elements can be done in the
following way:
jQuery(':text, textarea'),
You need to select an element based not only on its relationship to other elements or simple attribute values but also on varying characteristics such as programmatic states not expressible as selector expressions.
If you’re looking for an element with very specific
characteristics, selector expressions may not be the best tool. Using
jQuery’s DOM filtering method (filter()
), you can select elements based on anything expressible
within a function.
The filter method in jQuery allows you to pass either a string (i.e., a selector expression) or a function. If you pass a function, then its return value will define whether certain elements are selected. The function you pass is run against every element in the current selection; every time the function returns false, the corresponding element is removed from the collection, and every time you return true, the corresponding element is not affected (i.e., it remains in the collection):
jQuery('*').filter(function(){ return !!jQuery(this).css('backgroundImage'), });
The preceding code selects all elements with a background image.
The initial collection is of all elements (*
); then the filter()
method is called with a function.
This function will return true when a backgroundImage
is specified for the element
in question. The !!
that you see is
a quick way of converting any type in JavaScript to its Boolean
expression. Things that evaluate to false include an empty string, the
number zero, the value undefined
,
the null type, and, of course, the false Boolean itself. If any of
these things are returned from querying the backgroundImage
, the function will return
false, thus removing any elements without background images from the
collection. Most of what I just said is not unique to jQuery; it’s
just JavaScript fundamentals.
In fact, the !!
is not
necessary because jQuery evaluates the return value into a Boolean
itself, but keeping it there is still a good idea; anyone looking at
your code can be absolutely sure of what you intended (it aids
readability).
Within the function you pass to filter()
, you can refer to the current
element via the this
keyword. To
make it into a jQuery object (so you can access and perform jQuery
methods), simply wrap it in the jQuery function:
this; // Regular element object jQuery(this); // jQuery object
Here are some other filtering examples to spark your imagination:
// Select all DIV elements with a width between 100px and 200px: jQuery('div').filter(function(){ var width = jQuery(this).width(); return width > 100 && width < 200; }); // Select all images with a common image extension: jQuery('img').filter(function(){ return /.(jpe?g|png|bmp|gif)(?.+)?$/.test(this.src); }); // Select all elements that have either 10 or 20 children: jQuery('*').filter(function(){ var children = jQuery(this).children().length; return children === 10 || children === 20; });
There will always be several different ways to do something; this is no less true when selecting elements with jQuery. The key differential is usually going to be speed; some ways are fast, others are slow. When you use a complicated selector, you should be thinking about how much processing jQuery has to do in the background. A longer and more complex selector will take longer to return results. jQuery’s native methods can sometimes be much faster than using a single selector, plus there’s the added benefit of readability. Compare these two techniques:
jQuery('div a:not([href^=http://]), p a:not([href^=http://])'), jQuery('div, p').find('a').not('[href^=http://]'),
The second technique is shorter and much more readable than the first. Testing in Firefox (v3) and Safari (v4) reveals that it’s also faster than the first technique.
You’ve heard of the context parameter but have yet to encounter a situation where it’s useful.
As well as passing a selector expression to jQuery()
or $()
, you can pass a second argument that
specifies the context. The context is where jQuery will search for the
elements matched by your selector expression.
The context parameter is probably one of the most underused of jQuery’s features. The way to use it is incredibly simple: pass a selector expression, a jQuery object, a DOM collection, or a DOM node to the context argument, and jQuery will search only for elements within that context.
Here’s an example: you want to select all input fields within a form before it’s submitted:
jQuery('form').bind('submit', function(){ var allInputs = jQuery('input', this); // Now you would do something with 'allInputs' });
Notice that this
was passed
as the second argument; within the handler just shown, this
refers to the form element. Since it’s
set as the context, jQuery will only return input
elements within that form. If we
didn’t include that second argument, then all of the document’s
input
elements would be
selected—not what we want.
As mentioned, you can also pass a regular selector as the context:
jQuery('p', '#content'),
The preceding code returns exactly the same collection as the following selector:
jQuery('#content p'),
Specifying a context can aid in readability and speed. It’s a useful feature to know about!
The default context used by jQuery is document
, i.e., the topmost item in the DOM
hierarchy. Only specify a context if it’s different from this default.
Using a context can be expressed in the following way:
jQuery( context ).find( selector );
In fact, this is exactly what jQuery does behind the scenes.
Considering this, if you already have a reference to the context, then you should pass that instead of a selector—there’s no point in making jQuery go through the selection process again.
You need a reusable filter to target specific elements based on their characteristics. You want something that is succinct and can be included within your selector expressions.
You can extend jQuery’s selector expressions under the jQuery.expr[':']
object; this is an alias
for Sizzle.selectors.filters
. Each
new filter expression is defined as a property of this object, like
so:
jQuery.expr[':'].newFilter = function(elem, index, match){ return true; // Return true/false like you would on the filter() method };
The function will be run on all elements in the current collection and needs to return true (to keep the element in the collection) or false (to remove the element from the collection). Three bits of information are passed to this function: the element in question, the index of this element among the entire collection, and a match array returned from a regular expression match that contains important information for the more complex expressions.
For example, you might want to target all elements that have a certain property. This filter matches all elements that are displayed inline:
jQuery.expr[':'].inline = function(elem) { return jQuery(elem).css('display') === 'inline'; };
Now that we have created a custom selector, we can use it in any selector expression:
// E.g. #1 jQuery('div a:inline').css('color', 'red'), // E.g. #2 jQuery('span').filter(':not(:inline)').css('color', 'blue')
jQuery’s custom selectors (:radio
, :hidden
, etc.) are created in this
way.
As mentioned, the third parameter passed to your filter function is an array returned from a regular expression match that jQuery performs on the selector string. This match is especially useful if you want to create a filter expression that accepts parameters. Let’s say that we want to create a selector that queries for data held by jQuery:
jQuery('span').data('something', 123); // We want to be able to do this: jQuery('*:data(something,123)'),
The purpose of the selector would be to select all elements that
have had data attached to them via jQuery’s data()
method—it specifically targets
elements with a datakey of something
, equal to the number 123.
The proposed filter (:data
) could be created as follows:
jQuery.expr[':'].data = function(elem, index, m) { // Remove ":data(" and the trailing ")" from // the match, as these parts aren't needed: m[0] = m[0].replace(/:data(|)$/g, ''), var regex = new RegExp('(['"]?)((?:\\\1|.)+?)\1(,|$)', 'g'), // Retrieve data key: key = regex.exec( m[0] )[2], // Retrieve data value to test against: val = regex.exec( m[0] ); if (val) { val = val[2]; } // If a value was passed then we test for it, otherwise // we test that the value evaluates to true: return val ? jQuery(elem).data(key) == val : !!jQuery(elem).data(key); };
The reason for such a complex regular expression is that we want to make it as flexible as possible. The new selector can be used in a number of different ways:
// As we originally mused (above): jQuery('div:data("something",123)'), // Check if 'something' is a "truthy" value jQuery('div:data(something)'), // With or without (inner) quotes: jQuery('div:data(something, "something else")'),
Now we have a totally new way of querying data held by jQuery on an element.
If you ever want to add more than one new selector at the same
time, it’s best to use jQuery’s extend()
method:
jQuery.extend(jQuery.expr[':'], { newFilter1 : function(elem, index, match){ // Return true or false. }, newFilter2 : function(elem, index, match){ // Return true or false. }, newFilter3 : function(elem, index, match){ // Return true or false. } });
3.22.216.254