One of the things I am most impressed when we talk about the jQuery is the selector engine. Accessing the DOM elements using selectors in jQuery has become such an easy task, even more so when you mention the fact that (most of) the selectors use the same expressions as a CSS selectors. That is something that non-programming web designer can easily relate to.
This article is a coding exercise in creating a custom selector you may use as a guide to create your own selector.
Download jquery.step.js bundle | View working demo
What am I trying to achieve?
Basically, I needed to get every 3rd element in a collection of elements. (I wanted to apply custom styling to every third element). I was trying to write a simple script for that purpose but then I thought why don't I make a custom selector out if it.
Building custom selectors in jQuery is not that difficult so if you want to have a go at it here is a great article on the subject.
So my goal was to select elements at a certain step but in another case I also needed to start from the nth element not the first one... So this selector I am presenting here is capable of selecting elements at a certain step but also you can make an "offset" and start with selection at any point in a collection of elements.
Here is the complete code:
jQuery.expr[':'].step = function(node,index,meta){
var $index = index;
var $meta = meta[3].toString().split(',');
var $step = parseInt($meta[0]);
var $start = ($meta.length > 1) ? $meta[1] : 0;
if ($start != 0) $start -= 1;
return ( ( ($index-$start) / $step ) == Math.floor( ( ($index-$start) / $step ) ) && ( ($index-$start) >= 0 ) );
};
Take it step by step
In order to use this selector you will have to, of course link to a jQuery library and and also link to the jquery.step.js found in a download bundle.
<script type="text/javascript" src="jquery.js"></script> <script type="text/javascript" src="jquery.step.js"></script>
Step selector is used the regular, jQuery way. This would be the syntax if you want to select every third element:
$('ul#one li:step(3)').css('clear','left');
As mentioned this selector can accept the selection starting point. So if you want to select every 3rd element starting from the 5th element on, you would write it like this:
$('ul#two li:step(3,5)').addClass('alt');
First parameter 3 indicates the step and second parameter 5 indicates the starting point for the selection. Note that the parameters must be separated with comma.
Some practical uses
Let's say you have a gallery of thumbnails floated left and you want to clear the float on every 4th thumbnail. Or you want to assign a specific class name to every 10th list item in order to visually group it. Or you want to insert adds before every 5th news item in your list but not before the first one ... etc :)
Important note
Here's one flaw that I couldn't solve myself... No matter what I tried I couldn't make it to work properly when I used selectors that included class names. This is an kind of example I had trouble with:
$('ul.one li:step(3)').css('clear','left');
It turned out that jQuery selection engine doesn't perform any preselecting when using class names so, when creating custom selector, from indexing point of view, writing this:
$('ul.one li:step(3)').css('clear','left');
is just as same as writing this:
$('ul li:step(3)').css('clear','left');
Filtering by class names is done later, after the custom selector is being used so that is what makes this kinda incomplete.
If you have an idea how to solve this, please share, your name will be posted here and we will all be grateful :)
Nejo 10 Nov, 2010
http://api.jquery.com/nth-child-selector/
when you want an offset, simply put an equation adding the offset number.
I see the same functionality, but I envy your work, and also greatings your link to help any other developer to extend jQuery.
cssglobe 10 Nov, 2010
Diego Perini 10 Nov, 2010
If you are interested in a better way to execute "custom functions" bound to structural/positional pseudo-classes I also suggest you have a look at NWMatcher decorator callback, this is the syntax you would use:
NW.Dom.select( selector, context, callback );
It ensures the passed "callback" will be invoked for each element matching the selection and will probably yield better performances. The "callback" function is passed the element reference as the only parameter, so this avoids having to loop twice over the same elements to apply changes on them.
porcelanosa 11 Nov, 2010
All woks correct.
cssglobe 11 Nov, 2010
The :step() does the selecting before the ULs are filtered by class names so the indexing is wrong. In this case indexing is important so If you want to get a clearer picture of what happens here just insert this line of code just before the last line (where the script returns the node) so you can see each element's index:
$(node).html(index);
You will notice that the first element in second UL doesn't have zero index but it continues as if it was part of the same element collection.
Walter Apai 11 Nov, 2010