In this article we explore the JQuery chaining internal mechanism to see how it works and how it can improve our web development.
I The “wrapped set”
If you are a regular user of JQuery, you have probably heard about the “wrapped set” .In the official documentation the wrapped set is mentionned as simply a jQuery object.
The wrapped set is simply a list of DOM elements(with their children) in the order in which they are defined in the current document that matches a selector or in the order in which they have been created on the fly with the $(html) function.
Consequently, by definition, a wrapped set is a result of:
- either a jQuery selector: $(‘.elementToBeSelected’)
- or a DOM element created on the fly with jQuery: $(‘<div><span>Test</span></div>’). In this case the wrapped set only contains this single element and its children (if any)
To be exact, the wrapped set is a wrapper jQuery object and does not only contains those elements. It also offers numerous handy jQuery functions. Below is an example:
<table border="0" cellspacing="1"> <thead> <tr> <th>Name</th><th>Sexe</th><th>Age</th> </tr> </thead> <tbody> <tr> <td>John</td><td>Male</td><td>35</td> </tr> <tr> <td>Sue</td><td>Female</td><td>23</td> </tr> <tr> <td>Sally</td><td>Female</td><td>28</td> </tr> <tr> <td>Peter</td><td>Male</td><td>25</td> </tr> </tbody> </table>
The selector $(‘td:nth-child(1)’) will give the following wrapped set:
Please notice that the first 4 properties of the jQuery(‘td:nth-child(1)’) wrapped set is the 4 HTMLTableDataCellElement fetched by the selector (from 0 to 3). The following properties are jQuery functions (add, addClass, after…)
B) jQuery function classes
Indeed, all jQuery functions can be split into 2 distincts classes:
- utility functions: functions that return a scalar value (string, number, void, …). These functions are of the form: jQuery.(args) or $.(args). Example:
- $.trim(value): returns a trimmed string
- $.each(container,callback): apply the passed called back function to the passed container. Not to be confused with $(selector).each(callback)
- jQuery.parseJSON(jsonString): return a JSON object from an input String representation of the same object
- wrapped set functions: functions that act on each element of the wrapped set and return a wrapped set. In this class we distinguish to sub-classes:
- unmodifying wrapped-set functions: functions that do not modify the current wrapped set. The function is applied to each DOM element in the set and the original wrapped set is returned. Example: .addClass(cssClass), .attr(attribute[,value]), .each(callback)…
- modifying wrapped-set functions: these functions modify the current set to return a subset of its DOM elements. All these functions take a selector as unique argument. Example: .find(selector), .closest(selector), .filter(selector), .has(selector) …
Let’s consider the following DOM portion:
<table border="0" cellspacing="1"> <tr> <td class="centerText">John</td> </tr> <tr> <td>Sue</td> </tr> <tr> <td>Sally</td> </tr> <tr> <td>Peter</td> </tr> </table>
jQuery(‘td’) will return a wrapped set containing the following DOM elements:
<td class="centerText">John</td> <td>Sue</td> <td>Sally</td> <td>Peter</td>
Now jQuery(‘td’).find(‘.centerText’) will filter the current set with all elements matching the selector. The new wrapped set is:
II Chaining mechanism
A) Internal set stack
All function chainings in jQuery are based on non-utility functions. You can’t do chaining with utility functions simply because they only return scalar values and not wrapped set. Please notice that using an utility function in the middle of a chain will result in stopping it (makes sense).
The idea behing the function chaining is to navigate back and forth between different wrapped sets. Internally jQuery maintains a stack to keep track of all sets it encounters.
Let’s consider the following DOM section:
<div id="containterDiv"> <div id="firstChild"> <span class="centerText">Test</span> </div> <div id="secondChild"> <span>Another Text</span> </div> </div>
and the following chain jQuery(‘#containterDiv’).find(‘div’).filter(‘#firstChild’).find(‘span’);
Below is an illustration of how jQuery keeps track of wrapped sets across time:
B) Inter-sets navigation
Until now, we only see methods to restrict or filter the current wrapped set. What if we want to come back to the previous set ?
For this, jQuery provides the magical .end() method. This method simply pop the current wrapped set off the stack and goes back to the previous set.
Now that we understand how the sets are stacked, it’s easier to navigate through DOM sections using jQuery methods chaining. Let’s illustrate it by a more complex example:
As you can see, the chaining feature is quite powerfull. You can navigate back and forth in the wrapped-set stack with the .end() function. It is even possible to chain multiple .end() successively to navigate many wrapped sets back. It can becomes quickly unreadable so do not abuse of this function too much.
There is a good practice, when using complex chaining, to indent properly your code to make it at least readable and understandable for future developers. The previous chain can be turned into:
Each indentation level corresponds to a wrapped set change.