Dealing with the DOM can be a very tedious process. Traversing the DOM can quickly clutter your code with nested loops. For years, developers have wanted a better way to query the DOM for nodes matching specified criteria. In HTML5 their wishes came true in the form of a new specification called the Selectors API. If you have worked with CSS before, then the term “selector” probably sounds familiar. If you are unfamiliar with CSS, selectors are pattern strings used to retrieve matching nodes from the DOM. The Selectors API is the same concept translated to JavaScript. In addition to removing excessive looping in your code, the Selectors API also runs faster because it executes natively in the browser.
One nice thing about the Selectors API is that it only introduces two new (extremely useful) methods. The two new methods are querySelector()
and querySelectorAll()
. Both methods take a single parameter – a string specifying the DOM selector(s). The querySelector()
method returns only the first element matched by the selector. If there is no match, it returns null
. If you need to select a list of all elements that match the selector string, then the querySelectorAll()
method is used. If there are no matches, then an empty list is returned. Both querySelector()
and querySelectorAll()
can be called on the document
object, as well as individual elements.
The following HTML tables show the employee rosters for two imaginary companies, “companyFoo” and “companyBar”. The rosters list each employee’s name and salary. A data-manager
custom data attribute is used to designate whether an employee is a manager or not. These tables will be used to illustrate example usages of the Selectors API.
<table id="companyFoo">
<tbody>
<tr>
<td class="employee" data-manager="false">Jack</td>
<td class="salary">$70,000</td>
</tr>
<tr>
<td class="employee" data-manager="true">Jill</td>
<td class="salary">$80,000</td>
</tr>
</tbody>
</table>
<table id="companyBar">
<tbody>
<tr>
<td class="employee" data-manager="false">Pete</td>
<td class="salary">$55,000</td>
</tr>
<tr>
<td class="employee" data-manager="true">Pam</td>
<td class="salary">$90,000</td>
</tr>
</tbody>
</table>
Selecting a single node
The following three statements will all return the “companyFoo” table element. The first statement uses the traditional getElementById()
method, while the second and third statements use the querySelector()
method. Notice the use of the #
character in the second statement. If you are accustomed to using getElementById()
it can be easy to forget the #
, which would cause the method to return null
. The third statement shows how querySelector()
is used to return only the first of the two table elements.
let node1 = document.getElementById('companyFoo');
let node2 = document.querySelector('#companyFoo');
let node3 = document.querySelector('table');
Selecting node lists
These next three statements all return node lists containing both of the table elements in the document. The first statement uses the getElementsByTagName()
DOM method. The second statement uses the querySelectorAll()
method to select all of the table elements in the document. The third statement selects the same table elements individually using multiple selectors.
let nodes1 = document.getElementsByTagName("table");
let nodes2 = document.querySelectorAll("table");
let nodes3 = document.querySelectorAll("#companyFoo, #companyBar");
You must also be aware that getElementsByTagName()
returns a live node list, while querySelectorAll()
returns a non-live node list – also referred to as a static node list. This means that after the list is created, it will not reflect any changes made to the DOM. If the DOM is updated, the static node list could become out of date. In that case another call to querySelectorAll()
is needed to refresh the list.
A real example
The previous examples don’t show the power of the Selectors API. What if you wanted to highlight all of companyFoo’s managers’ names in red font? What would the JavaScript look like? First, you would need to isolate the table for companyFoo using getElementById('companyFoo')
. Next, getElementsByClassName('employee')
, would be called to select all of the cells containing employee names. Finally, you would have to check that the custom data-manager
attribute value is present and set to true
. The resulting JavaScript would look something like this.
let table = document.getElementById('companyFoo');
let cells = table.getElementsByClassName('employee');
for (let i = 0; i < cells.length; i++) {
let cell = cells[i];
if (cell.dataset.manager === 'true') {
cell.style.color = '#FF0000';
}
}
The same code is rewritten in the following example using the Selectors API. Here, all of companyFoo’s managers can be found using a single query. Even for this simple example, the Selectors API proves to be more powerful than the older DOM access methods. As a side note, this simple example could have been implemented using a CSS rule.
let cells = document.querySelectorAll("#companyFoo > tbody > tr > td[data-manager='true']");
for (let i = 0; i < cells.length; i++) {
cells[i].style.color = '#FF0000';
}