Extending jQuery Selectors: ‘between’

There are a ton of jQuery selectors to get at just about any data imaginable on a page. Between actual selectors (such as ID and attribute selectors) and pseudo-selectors (such as :first and :checked), the bulk of HTML element selection is trivial. There are, however, a few selectors I’ve had a need for but aren’t in jQuery currently – for instance, there is a next adjacent selector, but there is no analogous ‘previous adjacent’ selector baked in. How does one get around this? Let’s find out, after the jump.

There is a solution, of course – just like almost every part of jQuery, the Sizzle selector engine is extensible. That’s right – you can implement your own selectors that you can then use just like native selectors (big props to Ben Nadel for demonstrating this technique). One of my first needs in this realm was to create a ‘between’ selector – give me all the elements ‘between’ selectors for element A and element B. Here’s an example of what we want the syntax to look like:

var rowsBetween = $('tr:between(tr:contains(Start), tr:contains(End))');

Given the selector above, we would expect it to return the following table rows:

In order to implement this selector, we’re going to make a few assumptions in order to get it to work. First, we’re going to assume that both the inside selectors resolve to a single element (actually, they can resolve to any number of elements, we will just use only the first match). Next, we are going to assume that the first element actually occurs before the last element. These assumptions obviously make for a buggy implementation of a real selector, but they are perfectly acceptable for a user-driven project.
So, down to it. The first thing we are going to do is setup the jQuery selector engine extension method:

$.expr[':'].between = function(
    objNode,
    intStackIndex,
    arrProperties,
    arrNodeStack)
{
	// Placeholder logic...
	return false;
};

Ben Nadel has an excellent writeup on the selector engine interface in his article http://www.bennadel.com/blog/1457-How-To-Build-A-Custom-jQuery-Selector.htm, so no need to cover it again. Now, our next task is to write the guts of our selector. Here’s the algorithm we are going to be implementing:

  1. Split the arguments by ‘,’ (comma), so that we have the start and end selectors.
  2. Find the start and end elements that correspond to the start and end selectors.
  3. Is the element we are currently testing between the start and end elements?
    1. Yes -> return true
    2. No -> return false

This is a relatively easy algorithm to implement. First, let’s do the chopping:

    var args = arrProperties[3].split(",");
    var startSelector = args[0];
    var endSelector = args[1];

Now that we have the ‘between’ selector values, let’s get the corresponding elements from the element collection.

 var startIndex = -1;
    var endIndex = -1;

    // Find the start and end indexes.
    for (var index = 0; index < arrNodeStack.length; index++)
    {
        if ($(arrNodeStack[index]).is(startSelector) && startIndex < 0)
            startIndex = index;
        else if ($(arrNodeStack[index]).is(endSelector) && endIndex < 0)
            endIndex = index;
    }

$(arrNodeStack[index]) gives us the element that we are currently iterating over. In order to see if it matches our start/end selector, we use the jQuery is() method. We have a little handling here to keep from overwriting our values if we’ve already found a match, but it’s all pretty self-explanatory.

Our last task is to check to see if our selected element is between the start and end elements, and return the appropriate value. That again is pretty straightforward:

    if (startIndex >= 0 &&
        endIndex > 0 &&
        intStackIndex > startIndex &&
        intStackIndex < endIndex)
        return true;
    else
        return false;

Putting this all together, we get the following:

$.expr[':'].between = function(
    objNode,
    intStackIndex,
    arrProperties,
    arrNodeStack)
{
    var args = arrProperties[3].split(",");
    var startSelector = args[0];
    var endSelector = args[1];

    var startIndex = -1;
    var endIndex = -1;

    // Find the start and end indexes.
    for (var index = 0; index < arrNodeStack.length; index++)
    {
        if ($(arrNodeStack[index]).is(startSelector) && startIndex < 0)
            startIndex = index;
        else if ($(arrNodeStack[index]).is(endSelector) && endIndex < 0)
            endIndex = index;
    }

    if (startIndex >= 0 &&
        endIndex > 0 &&
        intStackIndex > startIndex &&
        intStackIndex < endIndex)
        return true;
    else
        return false;
};

That’s it! We’ve now got the ability to use the ‘between’ selector just like any other jQuery selector. Pretty simple, no? From not knowing a thing about selector engine extension to a finished product, this was only a few hours of work (mostly research, too). If you’re looking for an opportunity to try implementing a custom selector, try that ‘previous adjacent’ selector I talked about in the opening paragraph – it should be pretty easy to implement.

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>