Filtering Selections

jQuery · Lesson 6 of 24 5 min read Updated

A selector gets you a starting set of elements. Often you then need a smaller part of it: only the products under ₹500, only the third row, every row except the header. jQuery has a family of filtering methods that take a set and return a smaller set, so you can keep chaining.

In this lesson you will learn those methods and their plain JavaScript equivalents, which mostly come from turning a NodeList into a real array and using array methods such as filter() and slice().

filter() and not()

.filter() keeps the elements that match. It accepts either a selector or a function. .not() is the opposite: it removes the elements that match.

JavaScript
$("li").filter(".in-stock");        // keep items with class in-stock
$("li").not(".in-stock");           // keep the others

// A function: return true to keep the element
$("li").filter(function (index, element) {
  return Number($(element).attr("data-price")) < 500;
});

As with .each(), the callback receives (index, element), and this is the element too.

In plain JavaScript, convert the NodeList to an array first. querySelectorAll() returns a NodeList, which has forEach() but no filter():

JavaScript
const items = [...document.querySelectorAll("li")];   // spread into a real array
const cheap = items.filter((li) => Number(li.dataset.price) < 500);
const notInStock = items.filter((li) => !li.matches(".in-stock"));

element.matches(selector) returns true if the element would be selected by that CSS selector, which makes it the plain way to filter by selector.

Try both side by side on a small shop:

HTMLTry it yourself
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>Filtering products</title>
  <style>
    li { padding: 0.2rem 0.5rem; }
    .pick { background: #dff0ff; }
  </style>
</head>
<body>
  <h2>Ananya's Book Corner, Lucknow</h2>
  <ul id="books">
    <li data-price="299" class="in-stock">Panchatantra Stories</li>
    <li data-price="650">Learn Python the Simple Way</li>
    <li data-price="180" class="in-stock">Hindi Grammar Workbook</li>
    <li data-price="899" class="in-stock">Atlas of India</li>
    <li data-price="450">Cricket Rules Explained</li>
  </ul>
  <button id="cheap">Under ₹500 (jQuery)</button>
  <button id="stock">In stock and under ₹500 (plain JS)</button>
  <p id="out"></p>

  <script src="https://cdn.jsdelivr.net/npm/jquery@4.0.0/dist/jquery.min.js"></script>
  <script>
    const $books = $("#books li");

    $("#cheap").on("click", function () {
      $books.removeClass("pick");
      const $cheap = $books
        .filter(function (index, element) {
          return Number($(element).attr("data-price")) < 500;
        })
        .addClass("pick");
      $("#out").text($cheap.length + " books under ₹500");
    });

    document.querySelector("#stock").addEventListener("click", () => {
      const books = [...document.querySelectorAll("#books li")];
      books.forEach((li) => li.classList.remove("pick"));
      const found = books.filter((li) => li.matches(".in-stock") && Number(li.dataset.price) < 500);
      found.forEach((li) => li.classList.add("pick"));
      document.querySelector("#out").textContent = `${found.length} books in stock under ₹500`;
    });
  </script>
</body>
</html>

Picking by position

These methods choose elements by their position in the set. Positions start at 0.

jQueryKeepsPlain JavaScript (on an array arr)
.first()the first elementarr[0] or arr.at(0)
.last()the last elementarr.at(-1)
.eq(2)the third elementarr[2]
.eq(-1)the last element (negative counts from the end)arr.at(-1)
.slice(1, 3)elements at index 1 and 2arr.slice(1, 3)
.even()indexes 0, 2, 4...arr.filter((el, i) => i % 2 === 0)
.odd()indexes 1, 3, 5...arr.filter((el, i) => i % 2 === 1)

.even() and .odd() were added in jQuery 3.5. Be careful with the names: because counting starts at 0, .even() gives the 1st, 3rd and 5th elements as a human would count them. CSS :nth-child(odd) counts from 1, so it matches those same elements.

HTMLTry it yourself
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>Position methods</title>
  <style>
    table { border-collapse: collapse; }
    td { border: 1px solid #ccc; padding: 0.3rem 0.6rem; }
    .stripe { background: #f2f6fa; }
    .first { font-weight: bold; }
    .last { color: #b3261e; }
  </style>
</head>
<body>
  <h2>Pune to Mumbai: stops</h2>
  <table>
    <tr><td>Pune Junction</td><td>06:00</td></tr>
    <tr><td>Shivajinagar</td><td>06:08</td></tr>
    <tr><td>Lonavala</td><td>07:05</td></tr>
    <tr><td>Karjat</td><td>07:45</td></tr>
    <tr><td>Mumbai CSMT</td><td>09:15</td></tr>
  </table>
  <p id="out"></p>

  <script src="https://cdn.jsdelivr.net/npm/jquery@4.0.0/dist/jquery.min.js"></script>
  <script>
    const $rows = $("tr");
    $rows.even().addClass("stripe");      // rows 1, 3 and 5 as people count them
    $rows.first().addClass("first");
    $rows.eq(-1).addClass("last");

    const middle = $rows.slice(1, -1).find("td:first-child").map(function () {
      return $(this).text();
    }).get();
    $("#out").text("Stops on the way: " + middle.join(", "));
  </script>
</body>
</html>

.slice(1, -1) keeps every row except the first and last. Then .map() turns each row into a value, and .get() converts the result into a normal JavaScript array so you can call join().

has() and is()

.has(selector) keeps elements that contain a matching descendant. .is(selector) does not filter at all: it returns true if at least one element in the set matches, which is handy inside if.

JavaScript
$("li").has("ul");                 // list items that contain a nested list
if ($("#agree").is(":checked")) { /* ... */ }
if ($(event.target).is("button.delete")) { /* ... */ }

// Plain JavaScript
[...document.querySelectorAll("li")].filter((li) => li.querySelector("ul"));
document.querySelectorAll("li:has(ul)");            // CSS :has() is native in modern browsers
if (document.querySelector("#agree").checked) { /* ... */ }
if (event.target.matches("button.delete")) { /* ... */ }

Going back with end()

Each filtering call creates a new set. .end() returns to the previous set in the chain, which lets you style two groups in one statement:

JavaScript
$("#books li")
  .filter(".in-stock").addClass("available")
  .end()                                   // back to all the books
  .not(".in-stock").addClass("unavailable");

Chains like this were very popular, but they are harder to read than two short statements. Use .end() sparingly and recognise it when you see it in old code.

Common mistakes

  • Calling array methods on a NodeList. querySelectorAll(...).filter is undefined. Spread it into an array first: [...list].
  • Expecting .is() to return a set. It returns true or false, so you cannot chain after it.
  • Off-by-one with .even(). It uses 0-based indexes, so it picks what people call the odd-numbered rows.
  • Forgetting .get() after .map(). Without it you have a jQuery object of strings, not an array.

Key points

  • .filter() keeps matches and .not() removes them; both take a selector or a function.
  • .first(), .last(), .eq() (negative allowed), .slice(), .even() and .odd() pick by position.
  • .has() keeps elements containing a match; .is() answers yes or no.
  • .map(...).get() turns a set into a plain array of values.
  • In plain JavaScript, spread the NodeList into an array and use filter, slice, at and matches().

Exercise

Make a list of six cities with a data-temp attribute holding today's temperature. Using jQuery, highlight the cities hotter than 35 degrees with .filter(), make the last city bold with .last(), and show the names of the first three cities in a paragraph using .slice(), .map() and .get().

Show solution
HTML
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>City temperatures</title>
  <style>.hot { background: #ffd8cc; } .bold { font-weight: bold; }</style>
</head>
<body>
  <ul id="cities">
    <li data-temp="41">Jaipur</li>
    <li data-temp="29">Kochi</li>
    <li data-temp="38">Lucknow</li>
    <li data-temp="31">Pune</li>
    <li data-temp="36">Rohtak</li>
    <li data-temp="27">Shillong</li>
  </ul>
  <p id="first-three"></p>
  <script src="https://cdn.jsdelivr.net/npm/jquery@4.0.0/dist/jquery.min.js"></script>
  <script>
    const $cities = $("#cities li");
    $cities.filter(function (i, el) {
      return Number($(el).attr("data-temp")) > 35;
    }).addClass("hot");
    $cities.last().addClass("bold");
    const names = $cities.slice(0, 3).map(function () {
      return $(this).text();
    }).get();
    $("#first-three").text("First three: " + names.join(", "));
  </script>
</body>
</html>

Jaipur, Lucknow and Rohtak are highlighted; .slice(0, 3) keeps indexes 0 to 2, and .get() turns the mapped set into an array for join().

Quick Quiz

1. What does $("li").eq(-1) return?

2. What is the plain JavaScript equivalent of $(el).is(".active")?

3. Why does document.querySelectorAll("li").filter(fn) throw an error?

4. Which elements does .even() keep from a set of five?

Learn jQuery with a mentor

Free lessons get you started. Our Full Stack Web Development training adds real projects and code reviews from working developers.

  • Project-based learning
  • Mentor code reviews
  • Free counselling call