Chapter 2. Selecting Elements with jQuery

James Padolsey

Introduction

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).

2.1. Selecting Child Elements Only

Problem

You need to select one or more direct children of a particular element.

Solution

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.

Discussion

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.

2.2. Selecting Specific Siblings

Problem

You need to select only a specific set of siblings of a particular element.

Solution

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'),

Discussion

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'),

2.3. Selecting Elements by Index Order

Problem

You need to select elements based on their order among other elements.

Solution

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.

Table with even rows darkened
Figure 2-1. Table with even rows darkened

Discussion

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'),

Warning

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).

2.4. Selecting Elements That Are Currently Animating

Problem

You need to select elements based on whether they’re animating.

Solution

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.

Discussion

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.
}

2.5. Selecting Elements Based on What They Contain

Problem

You need to select an element based on what it contains.

Solution

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 <div> elements that encapsulate <a> elements (anchors) within <p> elements (paragraphs).

Discussion

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.

2.6. Selecting Elements by What They Don’t Match

Problem

You need to select a number of elements that don’t match a specific selector.

Solution

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'

Warning

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.

Discussion

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.

2.7. Selecting Elements Based on Their Visibility

Problem

You need to select an element based on whether it’s visible.

Solution

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

Discussion

Warning

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.

2.8. Selecting Elements Based on Attributes

Problem

You need to select elements based on attributes and those attributes’ values.

Solution

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)

Warning

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]'),

Discussion

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-"]'),

2.9. Selecting Form Elements by Type

Problem

You need to select form elements based on their types (hidden, text, checkbox, etc.).

Solution

jQuery gives us a bunch of useful filters for this very purpose, as shown in Table 2-1.

Table 2-1. jQuery form filters

jQuery selector syntax

Selects what?

:text

<input type="text" />

:password

<input type="password" />

:radio

<input type="radio" />

:checkbox

<input type="checkbox" />

:submit

<input type="submit" />

:image

<input type="image" />

:reset

<input type="reset" />

:button

<input type="button" />

:file

<input type="file" />

:hidden

<input type="hidden" />

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.

Discussion

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'),

2.10. Selecting an Element with Specific Characteristics

Problem

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.

Solution

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;
});

Discussion

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.

2.11. Using the Context Parameter

Problem

You’ve heard of the context parameter but have yet to encounter a situation where it’s useful.

Solution

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!

Discussion

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.

2.12. Creating a Custom Filter Selector

Problem

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.

Solution

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.

Discussion

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.
    }
});
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.22.216.254