Custom jQuery Filters Can Select Elements When Used Alone
It is not necessary to provide an actual element in conjunction with a filter, such as $('div:hidden')
. It is possible to simply pass the filter alone, anywhere a selector expression is expected.
Some examples:
// Selects all hidden elements $(':hidden'); // Selects all div elements, then selects only even elements $('div').filter(':even');
Grokking the :Hidden and :Visible Filter
The custom jQuery selector filters :hidden
and :visible
do not take into account the CSS visibility property as one might expect. The way jQuery determines if an element is hidden or visible is if the element consumes any space in the document. To be exact, an element is visible if its browser-reported offsetWidth
or offsetHeight
is greater than 0. That way, an element that might have a CSS display
value of block
contained in an element with a display
value of none
would accurately report that it is not visible.
Examine the code carefully and make sure you understand why the value returned is true
even though the <div>
being selected has an inline style of display:block
.
<!DOCTYPE html> <html lang="en"> <body> <div id="parentDiv" style="display: none;"> <div id="childDiv" style="display: block;"></div> </div> <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script> <script> (function($){ // Returns true because the parent div is hidden, so the // encapsulated div reports zero offsetWidth and offsetHeight alert($('#childDiv').is(':hidden')); })(jQuery); </script> </body> </html>
Using the Is() Method to Return a Boolean Value
It is often necessary to determine if the selected set of elements does, in fact, contain a specific element. Using the is()
method, we can check the current set against an expression/filter. The check will return true
if the set contains at least one element that is selected by the given expression/filter. If it does not contain the element, a false
value is returned. Examine the following code:
<!DOCTYPE html> <html lang="en"> <body> <div id="i0">jQuery</div> <div id="i1">jQuery</div> <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script> <script> (function($){ // Returns true alert($('div').is('#i1')); // Returns false. Wrapper set contains no <div> with id="i2" alert($('div').is('#i2')); // Returns false. Wrapper set contains no hidden <div> alert($('div').is(':hidden')); })(jQuery); </script> </body> </html>
It should be apparent that the second alert()
will return a value of false because our wrapper set did not contain a <div>
that had an id
attribute value of i2
. The is()
method is quite handy for determining if the wrapper set contains a specific element.
Notes: As of jQuery 1.3, the is()
method supports all expressions. Previously, complex expressions such as those containing hierarchy selectors (such as +
, ~
, and >
) always returned true
.
Filter is used by other internal jQuery functions. Therefore, all rules that apply there, apply here, as well.
Some developers use is('.class')
to determine if an element has a specific class. Don't forget that jQuery already has a method for doing this called hasClass('class')
, which can be used on elements that contain more than one class value. But truth be told, hasClass()
is just a convenient wrapper for the is()
method.
You Can Pass jQuery More Than One Selector Expression
You can provide the jQuery function's first parameter several expressions separated by a comma: $('expression, expression, expression')
. In other words, you are not limited to selecting elements using only a single expression. For example, in the example below, I am passing the jQuery function three expressions separated by a comma.
<!DOCTYPE html> <html lang="en"> <body> <div>jQuery </div> <p>is the </p> <ul> <li>best!</li> </ul> <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script> <script> (function ($) { // Alerts jQuery is the best! alert($('div, p, ul li').text()); // Inefficient way. Alerts jQuery is the best! alert($('div').text() + $('p').text() + $('ul li').text()); })(jQuery); </script> </body> </html>
Each of these expressions selects DOM elements that are all added to the wrapper set. We can then operate on these elements using jQuery methods. Keep in mind that all the selected elements will be placed in the same wrapper set. An inefficient way to do this would be to call the jQuery function three times, once for each expression.
Checking Wrapper Set .length to Determine Selection
It is possible to determine if your expression has selected anything by checking if the wrapper set has a length. You can do so by using the array property length
. If the length
property does not return 0, then you know at least one element matches the expression you passed to the jQuery function. For example, in the code below we check the page for an element with an id
of "notHere." Guess what? It is not there!
<!DOCTYPE html> <html lang="en"> <body> <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script> <script> (function($){ // Alerts "0" alert($('#notHere').length); })(jQuery); </script> </body> </html>
Notes: If it is not obvious, the length property can also report the number of elements in the wrapper set - stated another way, how many elements were selected by the expression passed to the jQuery function.
Creating Custom Filters for Selecting Elements
The capabilities of the jQuery selector engine can be extended by creating your own custom filters. In theory, all you are doing here is building upon the custom selectors that are already part of jQuery. For example, say we would like to select all elements on a Web page that are absolutely positioned. Since jQuery does not already have a custom :positionAbsolute
filter, we can create our own.
<!DOCTYPE html> <html lang="en"> <body> <div style="position: absolute">absolute</div> <span style="position: absolute">absolute</span> <div>static</div> <div style="position: absolute">absolute</div> <div>static</div> <span style="position: absolute">absolute</span> <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script> <script> (function ($) { // Define custom filter by extending $.expr[':'] $.expr[':'].positionAbsolute = function (element) { return $(element).css('position') === 'absolute'; }; // How many elements in the page are absolutely positioned? alert($(':positionAbsolute').length); // Alerts "4" // How many div elements are absolutely positioned? alert($('div:positionAbsolute').length); // Alerts "2" })(jQuery); </script> </body> </html>
The most important thing to grasp here is that you are not limited to the default selectors provided by jQuery. You can create your own. However, before you spend the time creating your own version of a selector, you might just simply try the filter()
method with a specified filtering function. For example, I could have avoided writing the :positionAbsolute
selector by simply filtering the <div>
elements in my prior example with a function I pass to the filter()
method.
// Remove <div> elements from the wrapper // set that are not absolutely positioned $('div').filter(function () { return $(this).css('position') === 'absolute'; }); // or // Remove all elements from the wrapper // set that are not absolutely positioned $('*').filter(function () { return $(this).css('position') === 'absolute'; });
Notes: For additional information about creating your own selectors I suggest the following read: http://www.bennadel.com/blog/1457-How-To-Build-A-Custom-jQuery-Selector.htm
Differences Between Filtering By Numeric Order vs. DOM Relationships
jQuery provides filters for filtering a wrapper set by an element's numerical context within the set.
These filters are:
:first
:last
:even
:odd
:eq(index)
:gt(index)
:lt(index)
Notes: Filters that filter the wrapper set itself do so by filtering elements in the set at a starting point of 0, or index of 0. For example :eq(0)
and :first
access the first element in the set - $('div:eq(0)')
- which is at a 0 index. This is in contrast to the :nth-child
filter that is one-indexed. Meaning, for example, :nth-child(1)
will return the first child element, but trying to use :nth-child(0)
will not work. Using :nth-child(0)
will always select nothing.
Using :first
will select the first element in the set while :last
will select the last element in the set. Remember that they filter the set based on the relationship (numerical hierarchy starting at 0) within the set, but not the elements' relationships in the context of the DOM. Given this knowledge, it should be obvious why the filters :first
, :last
, and :eq(index)
will always return a single element.
If it is not obvious, allow me to explain further. The reason that :first
can only return a single element is because there can only be one element in a set that is considered first when there is only one set. This should be fairly logical. Examine the code below to see this concept in action.
<!DOCTYPE html> <html lang="en"> <body> <ul> <li>1</li> <li>2</li> <li>3</li> <li>4</li> <li>5</li> </ul> <ul> <li>6</li> <li>7</li> <li>8</li> <li>9</li> <li>10</li> </ul> <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script> <script> (function ($) { // Remember that text() combines the contents of all // elements in the wrapper set into a single string. alert('there are ' + $('li').length + ' elements in the set'); // Get me the first element in the set alert($('li:first').text()); // Alerts "1" // Get me the last element in the set alert($('li:last').text()); // Alerts "10" // Get me the 6th element in the set, 0 based index alert($('li:eq(5)').text()); // Alerts "6" })(jQuery); </script> </body> </html>
With a clear understanding of manipulating the set itself, we can augment our understanding of selecting elements by using filters that select elements that have unique relationships with other elements within the actual DOM. jQuery provides several selectors to do this. Some of these selectors are custom, while some are well known CSS expressions for selecting DOM elements.
ancestor descendant
parent > child
prev + next
prev ~ siblings
:nth-child(selector)
:first-child
:last-child
:only-child
:empty
:has(selector)
:parent
Usage of these selector filters will select elements based on their relationship within the DOM as pertaining to other elements in the DOM. To demonstrate this concept, let's look at some code.
<!DOCTYPE html> <html lang="en"> <body> <ul> <li>1</li> <li>2</li> <li>3</li> <li>4</li> <li>5</li> </ul> <ul> <li>1</li> <li>2</li> <li>3</li> <li>4</li> <li>5</li> </ul> <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script> <script> (function ($) { // Remember that text() combines the contents of all // elements in the wrapper set into a single string. alert($('li:nth-child(2)').text()); // Alerts "22" alert($('li:nth-child(odd)').text()); // Alerts "135135" alert($('li:nth-child(even)').text()); // Alerts "2424" alert($('li:nth-child(2n)').text()); // Alerts "2424" })(jQuery); </script> </body> </html>
If you are surprised by the fact that $('li:nth-child(odd)').text()
returns the value 135135, you are not yet grokking relationship filters. The statement, $('li:nth-child(odd)')
said verbally would be "find all <li>
elements in the Web page that are children, and then filter them by odd children." Well, it just so happens that there are two structures in the page that have a grouping of siblings made up of <li>
s. My point is this: The wrapper set is made up of elements based on a filter that takes into account an element's relationship to other elements in the DOM. These relationships can be found in multiple locations.
The concept to take away is that not all filters are created equally. Make sure you understand which ones filter based on DOM relationships-e.g. :only-child
-and which ones filter by the elements' position-e.g. :eq()
-in the wrapped set.
Selecting Elements By Id When the Value Contains Meta-Characters
jQuery selectors use a set of meta-characters (e.g. # ~ [] = >
) that when used as a literal part of a name (e.g. id="#foo[bar]"
) should be escaped. It is possible to escape characters by placing two backslashes before the character. Examine the code below to see how using two backslashes in the selection expression allows us to select an element with an id attribute value of #foo[bar]
.
<!DOCTYPE html> <html lang="en"> <body> <div id="#foo[bar]">jQuery</div> <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script> <script> (function ($) { // Alerts "jQuery" alert($('#\\#foo\\[bar\\]').text()); })(jQuery); </script> </body> </html>
Here is the complete list of characters that need to be escaped when used as a literal part of a name.
#
;
&
,
.
+
*
~
'
:
"
!
^
$
[
]
(
)
=
>
|
/
Stacking Selector Filters
It is possible to stack selector filters-e.g. a[title="jQuery"][href^="http://"]
. The obvious example of this is selecting an element that has specific attributes with specific attribute values. For example, the jQuery code below will only select <a>
elements in the HTML page that:
- Contain an
href
attribute with a starting value of "http://" - Have a
title
attribute with a value of "jQuery"
Only one <a>
is being selected.
<!DOCTYPE html> <html lang="en"> <body> <a title="jQuery">jQuery.com</a> <a href="http://www.jquery.com" title="jQuery" class="foo">jQuery.com 1</a> <a href="">jQuery.com</a> <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script> <script> (function ($) { // Alerts "1" alert($('a[title="jQuery"][href^="http://"]').length); })(jQuery); </script> </body> </html>
Notice in the code how we have stacked two filters to accomplish this selection.
Other selector filters can be stacked besides just attribute filters. For example:
// Select the last <div> contained in the // wrapper set that contains the text "jQuery" $('div:last:contains("jQuery")') // Get all check boxes that are both visible and selected $(':checkbox:visible:checked')
The concept to take away is that selector filters can be stacked and used in combination.
Notes: You can also nest and stack filters - e.g. $('p').filter(':not(:first):not(:last)')
Nesting Selector Filters
Selector filters can be nested. This enables you to wield filters in a very concise and powerful manner. Below, I give an example of how you can nest filters to perform complex filtering.
<!DOCTYPE html> <html lang="en"> <body> <div>javascript</div> <div><span class="jQuery">jQuery</span></div> <div>javascript</div> <div><span class="jQuery">jQuery</span></div> <div>javascript</div> <div><span class="jQuery">jQuery</span></div> <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script> <script> (function ($) { // Select all div's, remove all div's that have a child element with class="jQuery" alert($('div:not(:has(.jQuery))').text()); // Alerts combined text of all div's // Select all div's, remove all div's that are odd in the set (count starts at 0) alert($('div:not(:odd)').text()); // Alerts combined text of all div's })(jQuery); </script> </body> </html>
The concept to take away is that selector filters can be nested.
Notes: You can also nest and stack filters - e.g. $('p').filter(':not(:first):not(:last)')
Grokking the :nth-child() Filter
The :nth-child()
filter has many uses. For example, say you only want to select every third <li>
element contained within a <ul>
element. It is possible with the :nth-child()
filter. Examine the following code to get a better understanding of how to use the :nth-child()
filter.
<!DOCTYPE html> <html lang="en"> <body> <ul> <li>1</li> <li>2</li> <li>3</li> <li>4</li> <li>5</li> <li>6</li> <li>7</li> <li>8</li> <li>9</li> <li>10</li> </ul> <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script> <script> (function ($) { // Remember that text() combines the contents of all // elements in the wrapper set into a single string. // By index alert($('li:nth-child(1)').text()); // Alerts "1" // By even alert($('li:nth-child(even)').text()); // Alerts "246810" // By odd alert($('li:nth-child(odd)').text()); // Alerts "13579" // By equation alert($('li:nth-child(3n)').text()); // Alerts "369" // Remember this filter uses a 1 index alert($('li:nth-child(0)').text()); // Alerts nothing. There is no 0 index. })(jQuery); </script> </body> </html>
Selecting Elements By Searching Attribute Values Using Regular Expressions
When the jQuery attribute filters used to select elements are not robust enough, try using regular expressions. James Padolsey has written a nice extension to the filter selectors that will allow us to create custom regular expressions for filtering. I have provided a code example here, but make sure you also check out the article on http://james.padolsey.com for all the details.
<!DOCTYPE html> <html lang="en"> <body> <div id="123"></div> <div id="oneTwoThree"></div> <div id="0"></div> <div id="zero"><div> <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script> <script> (function ($) { //James Padolsey filter extension jQuery.expr[':'].regex = function (elem, index, match) { var matchParams = match[3].split(','), validLabels = /^(data|css):/, attr = { method: matchParams[0].match(validLabels) ? matchParams[0].split(':')[0] : 'attr', property: matchParams.shift().replace(validLabels, '') }, regexFlags = 'ig', regex = new RegExp(matchParams.join('').replace(/^\s+|\s+$/g, ''), regexFlags); return regex.test(jQuery(elem)[attr.method](attr.property)); } // Select div's where the id attribute contains numbers alert($('div:regex(id,[0-9])').length); // Alerts "2" // Select div's where the id attribute contains the string "Two" alert($('div:regex(id, Two)').length); // Alerts "1" })(jQuery); </script> </div></div></body> </html>
Difference Between Selecting Direct Children vs. All Descendants
Direct children elements only can be selected by using the combiner >
or by way of the children()
traversing method. All descendants can be selected by using the *
CSS expression. Make sure you clearly understand the difference between the two. The example below demonstrates the differences.
<!DOCTYPE html> <html lang="en"> <body> <div> <p><strong><span>text</span></strong></p> <p><strong><span>text</span></strong></p> </div> <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script> <script> (function ($) { // Each statement alerts "2" because there are // two direct child <p> elements inside of <div> alert($('div').children().length); // or alert($('>*', 'div').length); alert($('div').find('>*').length); // Each statement alerts 6 because the <div> contains // 6 descendants, not including the text node. alert($('div').find('*').length); // or alert($('*', 'div').length); })(jQuery); </script> </body> </html>
Selecting Direct Child Elements When a Context Is Already Set
It is possible to use the combiner >
without a context to select direct child elements when a context has already been provided. Examine the code below.
<!DOCTYPE html> <html lang="en"> <body> <ul id="firstUL"> <li>text</li> <li> <ul id="secondUL"> <li>text</li> <li>text</li> </ul> </li> <li>text</li> </ul> <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script> <script> (function ($) { // Select only the direct <li> children. Alerts "3". alert($('ul:first').find('> li').length); // or alert($('> li', 'ul:first').length); } )(jQuery); </script> </body> </html>
Basically, '> element'
can be used as an expression when a context has already been determined.
Comments