Create Custom CSS Selectors in jQuery

By Rob Gravelle

WEBINAR: On-demand webcast

How to Boost Database Development Productivity on Linux, Docker, and Kubernetes with Microsoft SQL Server 2017 REGISTER >

jQuery's powerful selectors are considered to be one of its best features. Not only do they closely resemble natural CSS, but jQuery beefs them up with a bunch of their own pseudo-class selectors. The icing on the cake is that jQuery's pseudo-class selectors are fully extendable. In today's tutorial we'll learn how to assign functions to the expr[':'] object to create our own custom selectors.

How jQuery's Pseudo-class Selectors Work

A CSS pseudo-class is a colon-prefixed keyword added to a selector that specifies a special state of the selected element(s). For example, ":hover" matches an element such as a link or button when the user hovers over it. These can be used with other information or on their own - i.e "button:hover" vs. ":hover". jQuery's own pseudo-class selectors include :button, :checkbox, :eq(), :even, :file, :first, :gt(), :has(), :header, :hidden, :image, :input, :last, :lt(), :odd, :parent, :password, :radio, :reset, :selected, :submit, :text, and :visible.

Here's some code that selects all <button> elements and <input> elements of "type='button'" and hides them:

$(":button").hide();

Note that extension selectors cannot take advantage of the performance boost offered by the native DOM querySelectorAll() method. Therefore jQuery's documentation recommends that you first select some elements using a pure CSS selector and then pass the pseudo-class selector to the jQuery filter() function to achieve optimal performance. With that in mind, here's some code that sets the background color of all odd-indexed table rows to blue:

$('tr').filter(':odd').css('background-color', 'blue');

Defining Custom Selectors

jQuery implements pseudo-class selectors as properties of the expr[':'] object, which is an alias for Sizzle.selectors.filters. It uses the following syntax:

$.expr[':'].selectorName = function(elem, [index], [match]) { /* ... */ }

This above syntax specifies selectorName as an attribute of expr[':']. The anonymous function assigned to this property is invoked for each element in the current collection with the element. It behaves as a filter, returning true to keep the element in the collection or false to remove it from the collection. Note that the element's index in the collection and a match array as arguments are both optional.

Here's a selector that targets all elements that can receive focus, i.e. links, buttons, form elements (excluding type='hidden'), having a tab index:

$.expr[':'].focusable = function (el) {
    return $(el).is('a, button, :input[type!=hidden], [tabindex]');
}

The focusable() function above cleverly delegates to one of jQuery's own selectors: is() checks the current matched set of elements against a selector, element, or jQuery object and returns true if at least one of these elements matches the given arguments.

In fact, employing more generic selectors within our own is a pattern that should be considered a best practice in custom selector creation.

Now you can use your selector just like you would jQuery's built-in selectors:

//turn all focusable fields red!
$(':focusable').css('color', 'red');

Defining Multiple Selectors at Once

If you'd like to define all of your custom selectors in one fell swoop, you can use the jQuery.extend() function. Just pass all of your functions in an object literal like so:

jQuery.extend(jQuery.expr[':'], {
    focusable: function (el) {
        return $(el).is('a, button, :input[type!=hidden], [tabindex]');
    },
    notopborder: function(el) {
        return $(el).css("border-top-style") == "none";
    }
});

Using the Match Parameter

The match parameter is actually an array that carries meta information about our selector. Here is what it would contain:

$('a:test(argument)');
//match would carry the following info:
[
    ':test(argument)', // full selector
    'test',            // only selector
    '',                // quotes used
    'argument'         // parameters
]

//for:
$('a:test("arg1, arg2")');
//match would carry the following info:
[
    ':test('arg1, arg2')', // full selector
    'test',                // only selector
    '"',                   // quotes used
    ['arg1, arg2']         // parameters
]

As an example, let's define a custom selector that targets elements based on their border type. It would be passed to the function within the third element of the match parameter.

We could then do something to the matched elements such as change their border styles:

//match would carry the following info:
/*    
[
   "borderstyle", 
   "borderstyle", 
   "", 
   "solid"
]
*/
borderstyle: function(elem, index, match) {
    return $(elem).css("border-top-style") == match[3];
}

$('#btnChangeBorder').click(function() {
  $(":borderstyle('solid')").css("border-style", "dashed");
});

Here's a demo of today's examples.

Conclusion

The ability to extend the expr[':'] object to create our own custom selectors is just one of the reasons that the jQuery library remains a major player in JavaScript development. Combined with its other DOM methods, it makes manipulating page elements a snap!



Rob Gravelle

Rob Gravelle resides in Ottawa, Canada. His design company has built web applications for numerous businesses and government agencies. Email him.

Rob's alter-ego, "Blackjacques", is an accomplished guitar player, who has released several CDs and cover songs. His band, Ivory Knight, was rated as one of Canada's top hard rock and metal groups by Brave Words magazine (issue #92).



Make a Comment

Loading Comments...

  • Web Development Newsletter Signup

    Invalid email
    You have successfuly registered to our newsletter.
  •  
  •  
  •  
Thanks for your registration, follow us on our social networks to keep up-to-date