Learning jQuery(Fourth Edition)
上QQ阅读APP看书,第一时间看更新

DOM traversal methods

The jQuery selectors that we have explored so far allow us to select a set of elements as we navigate across and down the DOM tree and filter the results. If this were the only way to select elements, our options would be somewhat limited. There are many occasions when selecting a parent or ancestor element is essential; that is where jQuery's DOM traversal methods come into play. With these methods at our disposal, we can go up, down, and all around the DOM tree with ease.

Some of the methods have a nearly identical counterpart among the selector expressions. For example, the line we first used to add the alt class, $('tr:even').addClass('alt'), could be rewritten with the .filter() method as follows:

$('tr').filter(':even').addClass('alt');

For the most part, however, the two ways of selecting elements complement each other. Also, the .filter() method in particular has enormous power because it can take a function as its argument. The function allows us to create complex tests for whether elements should be kept in the matched set. Let's suppose, for example, we want to add a class to all external links:

a.external {
  background: #fff url(images/external.png) no-repeat 100% 2px;
  padding-right: 16px;
}

jQuery has no selector for this sort of case. Without a filter function, we'd be forced to explicitly loop through each element, testing each one separately. With the following filter function, however, we can still rely on jQuery's implicit iteration and keep our code compact:

$('a').filter(function() {
  return this.hostname && this.hostname != location.hostname;
}).addClass('external');

Listing 2.9

The second line filters the set of <a> elements by two criteria:

  • They must have an href attribute with a domain name (this.hostname). We use this test to exclude mailto links, for instance.
  • The domain name that they link to (again, this.hostname) must not match (!=) the domain name of the current page (location.hostname).

More precisely, the .filter() method iterates through the matched set of elements, calling the function once for each and testing the return value. If the function returns false, the element is removed from the matched set. If it returns true, the element is kept.

With the .filter() method in place, the Henry V link is styled to indicate it is external:

In the next section, we'll take another look at our striped table example to see what else is possible with traversal methods.

Styling specific cells

Earlier, we added a highlight class to all cells containing the text Henry. To instead style the cell next to each cell containing Henry, we can begin with the selector that we have already written and simply call the .next() method on the result:

$(document).ready(function() {
  $('td:contains(Henry)').next().addClass('highlight');
});

Listing 2.10

The tables should now look like this:

The .next() method selects only the very next sibling element. To highlight all of the cells following the one containing Henry, we could use the .nextAll() method instead:

$(document).ready(function() {
  $('td:contains(Henry)').nextAll().addClass('highlight');
});

Listing 2.11

Since the cells containing Henry are in the first column of the table, this code causes the rest of the cells in these rows to be highlighted:

As we might expect, the .next() and .nextAll() methods have counterparts: .prev() and .prevAll(). Additionally, .siblings() selects all other elements at the same DOM level, regardless of whether they come before or after the previously selected element.

To include the original cell (the one that contains Henry) along with the cells that follow, we can add the .addBack() method:

$(document).ready(function() {
  $('td:contains(Henry)').nextAll().addBack()
  .addClass('highlight');
});

Listing 2.12

With this modification in place, all of the cells in the row get the styling offered by the highlight class:

There are a multitude of selector and traversal-method combinations by which we can select the same set of elements. Here, for example, is another way to select every cell in each row where at least one of the cells contains Henry:

$(document).ready(function() {
  $('td:contains(Henry)').parent().children()
  .addClass('highlight');
});

Listing 2.13

Rather than traversing across to sibling elements, we travel up one level in the DOM to the <tr> tag with .parent() and then select all of the row's cells with .children().

Chaining

The traversal method combinations that we have just explored illustrate jQuery's chaining capability. With jQuery, it is possible to select multiple sets of elements and do multiple things with them, all within a single line of code. This chaining not only helps keep jQuery code concise, but it also can improve a script's performance when the alternative is to respecify a selector.

Tip

How chaining works

Almost all jQuery methods return a jQuery object and so can have more jQuery methods applied to the result. We will explore the inner workings of chaining in Chapter 8, Developing Plugins.

It is also possible to break a single line of code into multiple lines for greater readability. For example, a single chained sequence of methods could be written in one line:

$('td:contains(Henry)').parent().find('td:eq(1)')
    .addClass('highlight').end().find('td:eq(2)')
                           .addClass('highlight');

Listing 2.14

This same sequence of methods could also be written in seven lines:

$('td:contains(Henry)') // Find every cell containing "Henry"
  .parent() // Select its parent
  .find('td:eq(1)') // Find the 2nd descendant cell
  .addClass('highlight') // Add the "highlight" class
  .end() // Return to the parent of the cell containing "Henry"
  .find('td:eq(2)') // Find the 3rd descendant cell
  .addClass('highlight'); // Add the "highlight" class

Listing 2.15

The DOM traversal in this example is contrived and not recommended. There are clearly simpler, more direct methods at our disposal. The point of the example is simply to demonstrate the tremendous flexibility that chaining affords us.

Chaining can be like speaking a whole paragraph's worth of words in a single breath—it gets the job done quickly, but it can be hard for someone else to understand. Breaking it up into multiple lines and adding judicious comments can save more time in the long run.