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 quite limited (although, frankly,
the selector expressions are robust in their own right, especially when
compared to the regular DOM scripting options). 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:odd').addClass('alt');, could be rewritten with the .filter() method as follows:
$('tr').filter(':odd').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. 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');
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 and others of its ilk.
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, testing the return
value of the function against each one. If the function returns false, the element is removed from the matched set. If it returns true, the element is kept.
Now let's take a look at our striped table again 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 chain the next() method to it:&;&;
$(document).ready(function() {
$('td:contains(Henry)').next().addClass('highlight');
});
The table 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');
});
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 .andSelf() method:
$(document).ready(function() {
$('td:contains(Henry)').nextAll().andSelf().addClass('highlight');
});
T&;&;o be sure,
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');
});
Here, rather than traversing across to sibling elements, we travel up one level in the DOM to the <tr> with .parent() and then select all of the row's cells with .children().
Chaining
T&;&;he traversal-method combinations that we have just explored illustrate jQuery's chaining
capability. It is possible with jQuery 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
re-specify a selector.
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 as one
line …
$('td:contains(Henry)').parent().find('td:eq(1)')
.addClass('highlight').end().find('td:eq(2)')
.addClass('highlight');
… or as 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
A&;dmittedly, the DOM
traversal in this example is circuitous to the point of absurdity. We
certainly wouldn't recommend using it, as 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.
Accessing DOM elements
E&;very
selector expression and most jQuery methods return a jQuery object. This
is almost always what we want, because of the implicit iteration and
chaining capabilities that it affords.
Still, there may be points in our code when we need to access a DOM element
directly. For example, we may need to make a resulting set of elements
available to another JavaScript library. Or we might need to access an
element's tag name, which is available as a property of the DOM element. For these admittedly rare situations, jQuery provides the .get() method. To access the first DOM element referred to by a jQuery object, we would use .get(0). If the DOM element is needed within a loop, we would use .get(index). So, if we want to know the tag name of an element with id="my-element", we would write:
var myTag = $('#my-element').get(0).tagName;
For even greater convenience, jQuery provides a shorthand for .get(). Instead of writing the above line, we can use square brackets immediately following the selector:
var myTag = $('#my-element')[0].tagName;
It's
no accident that this syntax looks like an array of DOM elements; using
the square brackets is like peeling away the jQuery wrapper to get at
the node list, while including the index (in this case, 0) is like plucking out a DOM element itself.