Make Your Own Custom jQuery Selector

Tweet

Of all the JavaScript libraries out there, jQuery arguably has the selector syntax closest to CSS, and that makes it easy for CSS authors to dive in, even though there are 52 selectors to choose from. If you like CSS’s :first-child, :hover, or :only-child, there are 34 similar “pseudo” pseudo-class selectors on the menu. Talk about spoiled for choice!Should you want more than jQuery’s native selectors—such as :animated, :even, :not(), and :visible—there’s a raft of custom selectors from the community (like :exactIgnoreCase(), :nothidden, or :loaded()) to fill any gaps.So this all means that there’s no need for you to worry about learning to write custom selectors—right? Wrong!The easier a task is to complete, the greater the chance that it will be completed. Here’s a test for you to try out: ask a new developer to use (let alone understand) this code:

if ($(el).offset().top > $(window).scrollTop() - $(el).outerHeight(true) &&    $(el).offset().top < $(window).scrollTop() + $(el).outerHeight(true) + $(window).height()){  //do something}

Now ask them to try this on for size:

if ($(el).is(":inview")) {  //do something}

No prizes for working out that each of those snippets does the same thing, and that the second version is going to see much more use. Both check to see if an element is currently in the viewport.If you find yourself with a lengthy condition to get a Boolean, or your true/false Boolean is showing up again and again, you might be on target to create a custom expression for your selector. And making them could hardly be easier:

jQuery.extend(jQuery.expr[':'], { //$.extend($.expr[':'] is just as good  expression: function () {    //establish boolean to be returned, or    return false;  }});

The expression is what will be called in your selector … it could be inview, loaded, or nothidden.The function is what you’d expect. We’ve added the return false, as that’s what you want the function to do at the minimum. Other than that, use any methods you need to return the Booleans that will power your selector.Let’s have a first run at our inview selector:

jQuery.extend(jQuery.expr[':'], {  inview: function (el) {    if ($(el).offset().top > $(window).scrollTop() - $(el).outerHeight(true) &&        $(el).offset().top < $(window).scrollTop() + $(el).outerHeight(true) + $(window).height()) {      return true;    }    return false;  }});

The code is far from optimized, but it’s working. For testing, I like to add selectors in their own script block between the jQuery include and my working script block. For production, though, you can include them like a plugin. Go ahead and add it to a page, and you can use it in several ways:

$("p:inview").css("color","orange");

or

$("p").filter(":inview").each(function(){  //do something});

or

if ($("p#desc").is(":inview")) {  //do something}

Now let’s take a look at some optimizations. Straight out of the box we can see some repetition that we can cache, including some repeated objects. As we’re only returning true or false, we can simply return our condition and we’ll be on our way:

jQuery.extend(jQuery.expr[':'], {  inview: function (el) {    var $e = $(el),    $w = $(window),    top = $e.offset().top,    height = $e.outerHeight(true),    windowTop = $w.scrollTop(),    windowScroll = windowTop - height,    windowHeight = windowTop + height + $w.height();    return (top > windowScroll && top < windowHeight);  }});

And we’re there! We’ve extended jQuery’s expression engine and our code is both optimized and readable.It’s not the only “in view”-style selector you’ll find on the Web, but ours is an evolution. It takes the height of the element into account, so that it keeps reporting true as long as the element is partially in the viewport.jQuery’s selectors are a natural for customization, and give you a great way to wet your feet before jumping into using .extend() to make plugins.

Note: Want more?

If you want to read more from Craig, check out jQuery: Novice to Ninja, the book he co-wrote with Earle Castledine.

Free book: Jump Start HTML5 Basics

Grab a free copy of one our latest ebooks! Packed with hints and tips on HTML5's most powerful new features.

  • Mal Curtis

    Hey Craig – wish I’d had this on Saturday night when I got kinda frustrated at some of the custom selector syntax.
    Can you explain the difference between this and passing a string rather than a function, a few of the tutorials online seemed to prefer that method.
    e.g. jQuery.extend(
    jQuery.expr[ ":" ],
    {
    myselector : (
    “jQuery( a ).attr( ‘rel’ ) == ‘value’ && ” +
    “jQuery( a ).attr( ‘otherattr’ ) >= 9″
    )
    });

  • goldfidget

    Very very useful.

  • BrenFM

    Coincidentally, our designer asked if I could do EXACTLY this (inview) this morning. Perfect timing! Thanks Sitepoint.

  • Sean Hogan

    I think custom css selectors are completely wrong.

    If you restrict your selectors to the ones supported by browsers then jQuery (or your selector engine of choice) can delegate entirely to the native querySelectorAll() call, which will improve their performance much more than any minor differences between the engines.

    Plus you are forced somewhat to maintain a better match between your scripts and your stylesheets, because they are both limited by the same range of selectors.

    • Mal Curtis

      Hi Sean,

      I think you’re wrong, as they’re being used in this context to provide easy access to functionality you’d still have to be writing.

      For example in the plugin I created in the weekend I wanted easy access to the forms that were being watched for change, so I implemented the :dirty selector.

      The great thing that this allows me to do is abstract away from what makes a form dirty. For now this loops through all forms with a class of dirtylistening, but in the future this will be handled differently. If I don’t have a custom selector, then I’d have to find all the places in my code where I find the dirty forms, and replace them with the new method of finding them.

      It also provides a way of finding these elements within the scope of another element, which is a lot more difficult to do with just a plain old function. For example, all dirty forms inside my modal would be $(‘#facebox :dirty’). Much easier than having an option jQuery selector being passed to a function which finds all the dirty forms.

      https://github.com/snikch/jquery.dirtyforms

  • twenty205

    Nice one, great introduction, I’m now more likely to dive in and use them. Thanks