Introducing filters

Filters are selectors that usually work with other types of selectors to reduce a set of matched elements. You can recognize them easily because they always start with a colon (:). Just as you’ve seen for the attributes, if another selector isn’t specified, the use of the Universal selector is implicitly assumed. One of the peculiarities of these selectors is that some of them accept an argument passed inside the parentheses; for example, p:nth-child(2). In the next sections, we’ll discuss all the available filters in jQuery broken down into different categories.

Position filters

Sometimes you’ll need to select elements by their position on the page. You might want to select the first or last link on the page or from the third paragraph. jQuery supports mechanisms for achieving these specific selections.

For example, consider

a:first

This format of selector matches the first <a> on the page. Now, let’s say you want to retrieve links starting from the third one on the page. To achieve this goal, you can write

a:gt(1)

This selector is really interesting because it gives us the chance to discuss a few points. First, we’re using a selector called Greater than (gt) because there isn’t one called Greater than or equal. Also, unlike the selectors you’ve seen so far, it accepts an argument (1 in this case) that specifies the index from which to start. Why do you pass 1 if you want to start from the third element? Shouldn’t it be 2? The answer comes from our programming background where indexes usually start at 0. The first element has index 0, the second has index 1, and so on.

These selectors specific to jQuery provide surprisingly elegant solutions to sometimes tough problems. See table 2.4 for a list of these Position filters (which the jQuery documentation collocates inside the basic filters category).

Table 2.4. The Position filters supported by jQuery
SelectorDescriptionIn CSS?
:firstSelects the first match within the context. li a:first returns the first anchor that’s a descendant of a list item.
:lastSelects the last match within the context. li a:last returns the last anchor that’s a descendant of a list item.
:evenSelects even elements within the context. li:even returns every even-indexed list item.
:oddSelects odd elements within the context. li:odd returns every odd-indexed list item.
:eq(n)Selects the nth matching element.
:gt(n)Selects elements after the nth matching element (the nth element is excluded).
:lt(n)Selects elements before the nth matching element (the nth element is excluded).

As we noted, the first index in a set of elements is always 0. For this reason, the :even selector will counterintuitively retrieve the odd-positioned elements because of their even indexes. For example, :even will collect the first, third, and so on elements of a set because they have even indexes (0, 2, and so on). The takeaway lesson is :even and :odd are related to the index of the elements within the set, not their position.

Another fact to highlight is that you can also pass to :eq():gt(), and :lt() a negative index. In this case the elements are filtered counting backward from the last element. If you write p:gt(-2), you’re collecting only the last paragraph in the page. Considering that the last paragraph has index -1, the penultimate has index -2, and so on, basically you’re asking for all the paragraphs that come after the penultimate.

In some situations you don’t want to select only the first or last element in the whole page but each first or last element relative to a given parent in the page. Let’s discover how.

Child filters

We said that jQuery embraces the CSS selectors and specifications. Thus, it shouldn’t be surprising that you can use the child pseudo-classes introduced in CSS3. They allow you to select elements based on their position inside a parent element. Where the latter is omitted, the Universal selector is assumed. Let’s say you want to retrieve elements based on their position inside a given element. For example,

ul li:last-child

selects the last child of parent elements. In this example, the last <li> child of each <ul> element is matched.

You may also need to select elements of a type only if they’re the fourth child of a given parent. For example,

div p:nth-child(4)

retrieves all <p>s inside a <div> that are the fourth child of their parent element.

The :nth-child() pseudo-class is different from :eq() although they’re often confused. Using the former, all the children of a containing element are counted, regardless of their type. Using the latter, only the elements corresponding to the selector attached to the pseudo-class are counted, regardless of how many siblings they have before them. Another important difference is that :nth-child() is derived from the CSS specifications; therefore it assumes the index starts from 1 instead of 0.

Another use case we can think of is “retrieve all the second elements having class description inside a <div>.” This request is accomplished using the selector

div .description:nth-of-type(2)

As you’re going through this section you should realize that the available selectors are many and powerful. Table 2.5 shows all the Child filters described so far and many more. Please note that when a selector allows for more syntaxes, like :nth-child(), a check mark in the In CSS? column of table 2.5 means that all the syntaxes are supported.

Table 2.5. The Child filters of jQuery
SelectorDescriptionIn CSS?
:first-childMatches the first child element within the context
:last-childMatches the last child element within the context
:first-of-typeMatches the first child element of the given type
:last-of-typeMatches the last child element of the given type
:nth-child(n) :nth-child(even|odd) :nth-child(Xn+Y)Matches the nth child element, even or odd child elements, or nth child element computed by the supplied formula within the context based on the given parameter
:nth-last-child(n) :nth-last-child(even|odd) :nth-last-child(Xn+Y)Matches the nth child element, even or odd child elements, or nth child element computed by the supplied formula within the context, counting from the last to the first element, based on the given parameter
:nth-of-type(n) :nth-of-type(even|odd) :nth-of-type(Xn+Y)Matches the nth child element, even or odd child elements, or nth child element of their parent in relation to siblings with the same element name
:nth-last-of-type(n) :nth-last-of-type(even|odd) :nth-last-of-type(Xn+Y)Matches the nth child element, even or odd child elements, or nth child element of their parent in relation to siblings with the same element name, counting from the last to the first element
:only-childMatches the elements that have no siblings
:only-of-typeMatches the elements that have no siblings of the same type

As table 2.5 points out, :nth-child():nth-last-child():nth-last-of-type(), and :nth-of-type() accept different types of parameters. The parameter can be an index, the word “even,” the word “odd,” or an equation. The latter is a formula where you can have an unknown variable as n. If you want to target the element at any position that’s a multiple of 3 (for example 3, 6, 9, and so on), you have to write 3n. If you need to select all the elements at a position that’s a multiple of 3 plus 1 (like 1, 4, 7, and so on), you have to write 3n+1.

Because things are becoming more complicated, it’s best to see some examples. Consider the following table from the lab’s sample DOM. It contains a list of programming languages and some basic information about them:

<table id="languages">
  <thead>
    <tr>
      <th>Language</th>
      <th>Type</th>
      <th>Invented</th>
    </tr>

  </thead>
  <tbody>
    <tr>
      <td>Java</td>
      <td>Static</td>
      <td>1995</td>
    </tr>
    <tr>
      <td>Ruby</td>
      <td>Dynamic</td>
      <td>1993</td>
    </tr>
    <tr>
      <td>Smalltalk</td>
      <td>Dynamic</td>
      <td>1972</td>
    </tr>
    <tr>
      <td>C++</td>
      <td>Static</td>
      <td>1983</td>
    </tr>
  </tbody>
</table>

Let’s say that you wanted to get all of the table cells that contain the names of programming languages. Because they’re all the first cells in their rows, you could use

#languages td:first-child

You could also use

#languages td:nth-child(1)

but the first syntax would be considered pithier and more elegant.

To grab the language type cells, you’d change the selector to use :nth-child(2), and for the year they were invented, you’d use :nth-child(3) or :last-child. If you wanted the absolute last table cell (the one containing the text 1983), you’d use the :last pseudo-class seen in the previous section, resulting in td:last.

To test your ability, you can imagine another situation. Let’s say that you want to retrieve the name of the languages and their year of creation using :nth-child(). Basically, what you’re asking here is to take for each table row (<tr>) the first and the third columns (<td>). The first and easier solution is to pass odd as the argument to the filter, resulting in

#languages td:nth-child(odd)

Just to have some more fun, let’s make the previous selection harder, assuming that you want to perform the same selection passing a formula to the :nth-child() filter. Recalling that the index for :nth-child() starts at 1, you can turn the previous selector into

#languages td:nth-child(2n+1)

This last example should reinforce in you the idea that jQuery puts great power in your hands.

Before we move on, head back over to the Selectors Lab and try selecting entries two and four from the list. Then try to find three different ways to select the cell containing the text 1972 in the table. Also try to get a feel for the difference between the :nth-child() type of filters and the absolute position selectors.

Even though the CSS selectors we’ve examined so far are incredibly powerful, we’ll discuss ways of squeezing even more power out of jQuery’s selectors that are specifically designed to target form elements or their status.

Form filters

The CSS selectors that you’ve seen so far give you a great deal of power and flexibility to match the desired DOM elements, but there are even more selectors that give you greater ability to filter the selections.

As an example, you might want to match all check boxes that are in a checked state. You might be tempted to try something along these lines:

$('input[type="checkbox"][checked]');

But trying to match by attribute will check only the initial state of the control as specified in the HTML markup. What you really want to check is the real-time state of the controls. CSS offers a pseudo-class, :checked, that matches elements that are in a checked state. For example, whereas the input[type="checkbox"] selector selects all input elements that are check boxes, the input[type="checkbox"]:checked selector narrows the search to only input elements that are check boxes and are currently checked. When rewriting your previous statement to select all the check boxes that are currently checked using the filter, you can write

$('input[type="checkbox"]:checked');

jQuery also provides a handful of powerful custom filter selectors, not specified by CSS, that make identifying target elements even easier. For example, the custom :checkbox selector identifies all check box elements. Combining these custom selectors can be powerful and shrink your selectors even more. Consider rewriting once again our example using filters only:

$('input:checkbox:checked');

As we discussed earlier, jQuery supports the CSS filter selectors and also defines a number of custom selectors. They’re described in table 2.6.

Table 2.6. The CSS and custom jQuery filter selectors
SelectorDescriptionIn CSS?
:buttonSelects only button elements (input[type=submit], input[type=reset], input[type=button], or button) 
:checkboxSelects only check box elements (input[type=checkbox])
:checkedSelects check boxes or radio elements in the checked state or options of select elements that are in a selected state
:disabledSelects only elements in the disabled state
:enabledSelects only elements in the enabled state
:fileSelects only file input elements (input[type=file])
:focusSelects elements that have the focus at the time the selector is run
:imageSelects only image input elements (input[type=image])
:inputSelects only form elements (input, select, textarea, button)
:passwordSelects only password elements (input[type=password])
:radioSelects only radio elements (input[type=radio])
:resetSelects only reset buttons (input[type=reset] or button[type=reset])
:selectedSelects only option elements that are in the selected state
:submitSelects only submit buttons (button[type=submit] or input[type=submit])
:textSelects only text elements (input[type=text]) or input without a type specified (because type=text is the default)

These CSS and custom jQuery filter selectors can be combined, too. For example, if you want to select only enabled and checked check boxes, you could use

$('input:checkbox:checked:enabled');

Try out as many of these filters as you like in the Selectors Lab Page until you feel that you have a good grasp of their operation.

These filters are an immensely useful addition to the set of selectors at your disposal, but did you think, even for one moment, that the selectors ended here? No way!

Content filters

Another of the categories that you can find in the jQuery documentation is the one containing Content filters. As the name suggests, these filters are designed to select elements based on their content. For example, you can choose elements if they contain a given word or if the content is completely empty. Note that by content we mean not only raw text but also child elements.

As you saw earlier, CSS defines a useful selector for selecting elements that are descendants of specific parents. For example, this selector

div span

will select all span elements that are descendants of div elements.

But what if you wanted the opposite? What if you wanted to select all <div>s that contained span elements? That’s the job of the :has() filter. Consider this selector

div:has(span)

which selects the div ancestor elements as opposed to the span descendant elements.

This can be a powerful mechanism when you get to the point where you want to select elements that represent complex constructs. For example, let’s say that you want to find which table row contains a particular image element that can be uniquely identified using its src attribute. You might use a selector such as this

$('tr:has(img[src="puppy.png"])');

which would return any table row element containing the identified image anywhere in its descendant hierarchy.

A complete list of the Content filters is shown in table 2.7.

Table 2.7. The Content filters supported by jQuery
SelectorDescriptionIn CSS?
:contains(text)Selects only elements containing the specified text (the text of the children and the descendants is also evaluated).
:emptySelects only elements that have no children (including text nodes).
:has(selector)Selects only elements that contain at least one element that matches the specified selector.
:parentSelects only elements that have at least one child node (either an element or text).

If you’re starting to feel overwhelmed by all these selectors and filters, we suggest that you take a small break, because you aren’t finished yet!

Other filters

You’ve seen an incredible number of selectors and filters (special selectors) that you probably didn’t even know existed. Your journey into the world of selectors hasn’t ended, and in this section we’ll discuss the remaining ones. A couple of them, :visible and :hidden, are categorized in the library’s documentation under Visibility filters, but for brevity we decided to include them here.

If you want to negate a selector—let’s say to match any input element that’s not a check box—you can use the :not() filter. For example, to select non–check box input elements, you could use

input:not(:checkbox)

But be careful! It’s easy to go astray and get some unexpected results!

Let’s say that you wanted to select all images except those whose src attribute contained the text dog. You might quickly come up with the following selector:

$(':not(img[src*="dog"])');

But if you used this selector, you’d find that not only did you get all the image elements that don’t reference dog in their src, but in general, every element in the DOM that isn’t an image element with such src attribute’s value!

Whoops! Remember that when a base selector is omitted, it defaults to the Universal selector. Your errant selector actually reads as “fetch all elements that aren’t images that reference ‘dog’ in their src attributes.” What you intended was “fetch all image elements that don’t reference ‘dog’ in their src attributes,” which would be expressed like this:

$('img:not([src*="dog"])');

Again, use the lab page to conduct experiments until you’re comfortable with how to use the :not() filter to invert selections.

When working with jQuery it’s common to use its methods to hide one or more elements on a page. To retrieve these elements you can use the :hidden filter. An element is considered hidden not only if it has

display: none;

applied but also if it doesn’t occupy space. For example, a hidden element is also one that has its width and height set to zero. Using this selector

input:hidden

you’re targeting all the input elements of the page that are hidden.

jQuery 3: Feature changed

jQuery 3 slightly modifies the meaning of the :visible (and therefore of :hidden) filter. Starting from jQuery 3, elements will be considered :visible if they have any layout boxes, including those of zero width and/or height. For example, br elements and inline elements with no content will now be selected by the :visible filter.

When creating web pages, you often use foreign words. If you write correct, semantic HTML, you’ll find yourself tagging those words using the em element, adding the lang attribute to specify the language. Let’s say that you have a page about pizza; you could have markup like the following:

<p>The first pizza was called <em lang="it">Margherita</em>, and it was
     created in <em lang="it">Napoli</em> (Italy).</p>

You can select all those foreign words of this example using the :lang() filter in this way:

var $foreignWords = $('em:lang(it)');

A complete list of the remaining filters is shown in table 2.8.

Table 2.8. The remaining filters supported by jQuery
SelectorDescriptionIn CSS?
:animatedSelects only elements that are currently under animated control
:headerSelects only elements that are headers: <h1> through <h6>
:hiddenSelects only elements that are hidden
:lang(language)Selects elements in a specified language
:not(selector)Negates the specified selector
:rootSelects the element that’s the root of the document
:targetSelects the target element indicated by the fragment identifier of the document’s URI
:visibleSelects only elements that are visible

Although jQuery offers an incredible number of selectors, it doesn’t cover all the possible use cases, and the development team behind the library knows it. For this reason, they gave you the option to create your own filters. Let’s look at how you can do this.

How to create custom filters

In the previous sections, you learned all the selectors and filters supported by jQuery. Regardless of their number, you may deal with use cases not covered. You may also find yourself doing the same selection and then the same filtering on the retrieved set over and over again, using loops and selection constructs. In situations like these you can create a shortcut to collect nodes of the DOM or, to better phrase it, you can create a custom filter (also referred as a custom selector or custom pseudo-selector).

In jQuery there are two ways to create a custom filter. The first is simpler to write but its use is discouraged because it has been replaced, starting from jQuery 1.8, by the second one. In this app we’ll describe the newer method only, but if you want to take a look at the old way, we’ve prepared a JS Bin just for you. The example is also available in the file lesson-2/custom.filter.old.html of the source provided with this app. Keep in mind that when using the new approach, you’re developing a custom filter that won’t work in versions of jQuery prior to 1.8. However, this shouldn’t be a problem in many cases as this version is obsolete.

To explain the new way to create a custom filter, we’ll start with an example. Pretend you’re developing a tech game where you have a list of levels to complete with a certain grade of difficulty, the number of points the user can earn, and a list of technologies to employ to complete it. Your hypothetical list could resemble this:

<ul class="levels">
  <li data-level="1" data-points="1" data-technologies="javascript node grunt">Level 1</li>
  <li data-level="2" data-points="10" data-technologies="php composer">Level 2</li>
  <li data-level="3" data-points="100" data-technologies="jquery requirejs">Level 3</li>
  <li data-level="4" data-points="1000" data-technologies="javascript jquery backbone">Level 4</li>
</ul>

Now imagine you often need to retrieve levels (data-level) higher than 2 but only if they allow you to earn more than 100 points (data-points) and have jQuery in the list of the technologies to employ (data-technologies). Using the knowledge you’ve acquired so far, you know how to search li elements having the word jquery inside the attribute data-technologies (li[data-technologies~="jquery"]). But how do you perform a number comparison using selectors? The truth is you can’t. To accomplish this task, you must loop over your initial selection and then retain only the elements you need, as shown here:

Instead of repeating these lines every time, you can create a custom filter:

As you can see, a filter is nothing but a function added to a property called :, which belongs to jQuery’s expr attribute. That’s no mistake, dear reader. It’s a property called “colon.” The latter is a property containing jQuery’s native filters, and you can use it to add your own.

You call your custom filter requiredLevel, and instead of passing the function directly, you use a jQuery utility (actually it belongs to the underlying Sizzle selectors engine) called createPseudo() .

To the createPseudo() function, you pass an anonymous function where you declare a parameter called filterParam. The name of the latter, standing for “filter parameter,” is arbitrary and you can choose a different one if you prefer. This parameter represents an optional parameter you can pass to the filter, just like filters such as :eq() and :nth-child(), that you won’t use for the moment. Inside this anonymous function, you create another anonymous function that will be returned and that’s responsible for performing the filtering. To this inner function, jQuery passes the elements to be processed one at a time (element parameter), the DOMElement or DOMDocument from which selection will occur (context parameter), and a Boolean that specifies if you’re working on an XML document or not (isXML parameter) . Inside the innermost function, you write the code to test whether the element should be kept or not . In your case, you test whether the level is higher than 2 and the points the user can earn are more than 100.

In the previous example, we introduced an argument called filterParam that you can use to pass a parameter to your custom filter. Due to the fixed nature of our requirements, we didn’t use it. Let’s have some fun seeing how it can help you.

Imagine you want to retrieve levels based on the offered number of points—something like “select all the levels with a number of points higher than X.” That big X is a good opportunity to use a parameter to pass to your pseudo-selector. Based on this requirement, you can create a new filter:

There are a few differences compared to the previous example. You use the create-Pseudo() function as before, but you call the filter pointsHigherThan. Before declaring the second function, you need to save the argument in a variable called points  so it’ll be available in its closure (if you don’t know what a closure is, read the section on closures in the appendix). At this point, you can use the given argument through the use of the stored variable .

Let’s put this new filter into action. If you want to retrieve all the levels that allow you to earn more than 50 points, you can write

var $elements = $('.levels li:pointsHigherThan(50)');

obtaining the last two list items.

Both the custom filters presented in this section are available in the file lesson-2/custom.filter.html and also as a JS Bin.

So far, you’ve used half the power of the jQuery() function used to select elements because you used just one of the two parameters you can pass. It’s time to fix this.


Posted

in

by

Tags:

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *