I’m sure you all know that it’s possible to create plugins and extend various aspects of the jQuery JavaScript library but did you know you could also extend the capabilities of it’s selector engine?
Well, you can! For example, you might want to add a new ‘:inline’ selector which will return those elements that are displayed inline. Have a look:
$.extend($.expr[':'],{ inline: function(a) { return $(a).css('display') === 'inline'; } }); |
Using the above code, when you want to select elements that are displayed inline you can simply include it within the selector:
$(':inline'); // Selects ALL inline elements $('a:inline'); // Selects ALL inline anchors |
That was a pretty simple example but I’m sure you can see the endless possibilites that this enables! And, creating a custom jQuery selector couldn’t really be simpler!
Loaded images selector
You might want to add a ‘loaded’ selector which will work with images, and will return those images that are loaded:
// Flag images upon load: $('img').load(function(){ $(this).data('loaded',true); }); // Extend expr: $.extend($.expr[':'],{ loaded: function(a) { return $(a).data('loaded'); } }); // Example of usage: alert( 'Images loaded so far: ' + $('img:loaded').size() ); |
Querying element data
jQuery’s ‘data’ function allows us to add special data to elements without having to pollute global variables or add invalid element attributes. One of the things that jQuery lacks is the capability to easily query elements according to their data. For example, one might decide to flag all elements added dynamically (with jQuery) as ‘dom’:
// New element: $('<img/>') .data('dom', true) // Flag .appendTo('body'); // Append to DOM |
Currently there’s no easy way to select all elements that have been flagged but what if we added a new ‘:data’ selector which could query such information?
Here’s how we would do it:
// Wrap in self-invoking anonymous function: (function($){ // Extend jQuery's native ':' $.extend($.expr[':'],{ // New method, "data" data: function(a,i,m) { var e = $(a).get(0), keyVal; // m[3] refers to value inside parenthesis (if existing) e.g. :data(___) if(!m[3]) { // Loop through properties of element object, find any jquery references: for (var x in e) { if((/jQueryd+/).test(x)) { return true; } } } else { // Split into array (name,value): keyVal = m[3].split('='); // If a value is specified: if (keyVal[1]) { // Test for regex syntax and test against it: if((/^/.+/([mig]+)?$/).test(keyVal[1])) { return (new RegExp( keyVal[1].substr(1,keyVal[1].lastIndexOf('/')-1), keyVal[1].substr(keyVal[1].lastIndexOf('/')+1)) ).test($(a).data(keyVal[0])); } else { // Test key against value: return $(a).data(keyVal[0]) == keyVal[1]; } } else { // Test if element has data property: if($(a).data(keyVal[0])) { return true; } else { // If it doesn't remove data (this is to account for what seems // to be a bug in jQuery): $(a).removeData(keyVal[0]); return false; } } } // Strict compliance: return false; } }); })(jQuery); |
Usage
Now, selecting elements which have that ‘dom’ flag is really easy:
$(':data(dom)'); // All elements with 'dom' flag $('div:data(dom)'); // All DIV elements with 'dom' flag $(':not(:data(dom))'); // All elements WITHOUT 'dom' flag |
The ‘:data’ extension also allows you to query by comparison, for example:
$(':data(ABC=123)'); // All elements with a data key of 'ABC' equal to 123 |
It also allows you to use regular expressions:
// Let's assume we have slightly varying data across a set of elements: $('div').each(function(i){ $(this).data('divInfo','index:' + i); // Will result in value being 'index:0', 'index:1', 'index:2' etc. etc. }); // We can select all of those DIVs like this: $('div:data(divInfo=/index:\d+/)'); // Note: It's necessary to use non-literal notation when writing these // regular expressions, so if you want to match a real backslash you'd // have to use '\\'. Similarly if you want to test for all digit's // you'd have to use \d instead of d. |
Additionally, you can select elements on a basis of whether or not they have ANY data applied to them:
$(':data'); // All elements with data $(':not(:data)'); // All elements without data |
Some other examples:
-
:red
// Check if color of element is red: $.extend($.expr[':'],{ red: function(a) { return $(a).css('color') === 'red'; } }); // Usage: $('p:red'); // Select all red paragraphs
-
:childOfDiv
// Check if element is a child of a div: $.extend($.expr[':'],{ childOfDiv: function(a) { return $(a).parents('div').size(); } }); // Yes, I know this is exactly the same as $('div p') // This is just a demonstration! ;) // Usage: $('p:childOfDiv'); // Select all paragraphs that have a DIV as a parent
-
:width()
// Check width of element: $.extend($.expr[':'],{ width: function(a,i,m) { if(!m[3]||!(/^(<|>)d+$/).test(m[3])) {return false;} return m[3].substr(0,1) === '>' ? $(a).width() > m[3].substr(1) : $(a).width() < m[3].substr(1); } }); // Usage: $('div:width(>200)'); // Select all DIVs that have a width greater than 200px // Alternative usage: $('div:width(>200):width(<300)'); // Select all DIVs that have a width greater // than 200px but less than 300px
-
:biggerThan()
// Check whether element is bigger than another: $.extend($.expr[':'],{ biggerThan: function(a,i,m) { if(!m[3]) {return false;} return $(a).width() * $(a).height() > $(m[3]).width() * $(m[3]).height(); } }); // Usage: $('div:biggerThan(div#banner))'); // Select all DIVs that are bigger than #banner // Alternative usage: (something a little more complex) // (Making use of custom width() selector) // Select all DIVs with a width less than 600px but an overall // size greater than that of the first paragraph which has a // size greater than img#header: $('div:width(<600):biggerThan(p:biggerThan(img#header):eq(0))');
Like I said, the possibilities are endless…
UPDATE
I’ve created a couple more examples, take a look:
-
:external
// Check whether links are external: // (Only works with elements that have href): $.extend($.expr[':'],{ external: function(a,i,m) { if(!a.href) {return false;} return a.hostname && a.hostname !== window.location.hostname; } }); // Usage: $('a:external'); // Selects all anchors which link to external site/page
-
:inView
// Check whether element is currently within the viewport: $.extend($.expr[':'],{ inView: function(a) { var st = (document.documentElement.scrollTop || document.body.scrollTop), ot = $(a).offset().top, wh = (window.innerHeight && window.innerHeight < $(window).height()) ? window.innerHeight : $(window).height(); return ot > st && ($(a).height() + ot) < (st + wh); } }); // Usage: $('div:inView'); // Selects all DIV elements within the current viewport // Alternative Usage: if ( $('div#footer').is(':inView') ) { // Do stuff... }
UPDATE #2
I’ve created a plugin which makes it a little easier to add new ‘:’ selectors. Although, it’s not really a ‘plugin’, it’s just a function which resides under the jQuery namespace:
(function($){ $.newSelector = function() { if(!arguments) { return; } $.extend($.expr[':'],typeof(arguments[0])==='object' ? arguments[0] : (function(){ var newOb = {}; newOb[arguments[0]] = arguments[1]; return newOb; })() ); } })(jQuery); |
Creating a new selector with the ‘newSelector’ plugin:
// Method 1: $.newSelector('big', function(elem){ return $(elem).width() + $(elem).height() > 1000; }); // Method 2: $.newSelector({ red: function(elem){ return $(elem).css('color') === 'red'; }, yellow: function(elem){ return $(elem).css('color') === 'yellow'; }, green: function(elem){ return $(elem).css('color') === 'green'; }, }); |
Thanks for reading! Please share your thoughts with me on Twitter. Have a great day!
When I first saw the post, I wondered how useful it might be – jQuery has great selection power. Your examples really got me thinking about things, and a very useful post it turned out to be!
Thanks for showing me the light!
Great post thanks dude π
In ‘Some other examples:’ section in ‘:red’ example after first comment
// Check if element is a child of a div:
I think you mean to write
// Check if the color of element is red:
Shane, I’m glad you can see the light! I love how extendable jQuery is… Being able to create plugins and new selectors oddly feels quite empowering! π
Ibrahim, thanks, I just corrected the mistake.
Very nice post. I like to see new and innovative ideas with jQuery.
Nice. I hadn’t noticed the selector extension syntax!
(isn’t p:childOfDiv just div>p, though?)
Janko, thanks! π
This is very interesting. In the ‘data’ examples though, it seems like a language is being created to ask questions about data elements. For example, you might want to select an element if a flag exists, or if a flag’s value meets some threshold, or if one flag and another flag both exist, or even if a flag matches some value that your application is storing somewhere else. This type of selection could be done using the functional ‘filter’ pattern to allow the most expressiveness:
or
or
…using some syntax that allows filtering on some element and provides a filtering function with that element. jQuery already provides a filter() function, but I believe it’s limited to test existence. I really like your idea above, but it seems unnecessary to recreate functional syntax in selectors (although typing “function()” often is admittedly clunky).
Stuart: “(isnβt p:childOfDiv just div>p, though?)”
I really should have named it something else because it actually selects all paragraphs that are both direct and indirect children of a div while ‘div>p’ only selects paragraphs that are direct children.
Bill,
That was originally my main concern, there’s no point in duplicating functionality that already exists, however, the true benefit of the selectors demonstrated in this post (especially ‘width’ and ‘data’) is only clear when used more than once.
Using the filter method is definitely faster but would end up introducing some very bloated code if used over and over again. Another point of these selectors is to condense the logic into concise and more importantly, memorable syntax. I may have gone a little overboard with the regular expression malarkey but it just seemed like a cool idea. π
I also thought about making the ‘:data’ thing into a regular jQuery plugin instead of extending the selector, but I ended up doing the latter – just for demonstration’s sake. π
btw, Bill, your myTunes app was awesome (I used it a lot!)!
best things i read since i discover jQuery !
Thanks for sharing
Thank you so much, James. That was quite enlightening.
I was just wondering: it seems the plan is to bring Sizzle, the new selector engine, as a modular, external but default library to jQuery.
I just hope it’s as extendable as you’ve shown us jQery native’s one to be.
—
On the childOfDiv selector: you could also just try $(‘div p’), right? Or am I missing something?
—
Cheers
That’s insane. I could have used this knowledge earlier this week … I ended up having to do a .filter() on an object where I wanted to use a simple selector. π Thanks loads!
riper, I’m glad you found it useful!
Bruno, I’ve just tested it with the latest Sizzle build and it seems to work perfectly!! Resig’s done a fine job on the new Sizzle engine, it’s superb! And yep, you’re right about
:childOfDiv
being the same as$('div p')
– it was just for illustrative purposes… πDaniel, you’re welcome, hopefully this will save you quite a bit of time in the future!
Fascinating.
I remember reading about this feature in jQuery documentation but I never ever though on the possibilities until I saw your excellent examples.
Awesome.
By the way, I’m not sure 100% right now but I would say $(‘div p’) will return an element like <div><span><p> while :childDiv wont.
Hi James. Would you allow a translation of your article to french (and maybe italian) for the http://www.jquery.info/ website ?
Eenko, childOfDiv is exactly the same as ‘div p’ – but putting a paragraph under a span tag shouldn’t really be done anyway. (block level elements should not be children of inline elements)
Olivier, yep sure! π Email/post a link to the translated post and I’ll link to it in the post.
Thanks! I’ve been using jQuery for years now, but I never seem to use it for more than AJAX and Animations, and don’t really play much with filters and data. A really good example of how to use it and it may come in very handy in my current project.
This is really interesting! I think :data is very useful.
Thank you for sharing
Hi,
I love this article, it’s great to be able to bundle up little macros as selectors.
One thing that confused me though was unrelated to the selector part of things. You’re calling a.hostname in your :external example and I haven’t seen that before – where does that come from?
Tane, jQuery is so much more than XHR/animations, I hope I’ve inspired you to look deeper into some of its lesser known (but way cooler) functionality!
Luca, Thanks!
Gareth, Great, I’m glad you like it! As for the a.hostname thing, ‘hostname’ is a property of the Location object. A link (<a/>) has all the properties of the Location object, some other properties you might find useful:
@James Don’t get me wrong, I have used jQuery to do some complex stuff (i’ve been using jQuery since 0.96) – it’s just I don’t use it for that as often as I’d like to.
I’m hoping that something like this though can help me improve some stuff in my jMaps plugin.
@Tane, 0.96!? I didn’t even know it existed until 1.2 (back in January of this year) I think! btw, jMaps is awesome! π Sorry if I misunderstood you…
Great article, James!
This was a great bit of exploration and research into an infrequently used piece of the jQuery library. Great job James.
James, very innovative stuff… thanks for posting! As others have stated, your examples really put the possibilities in perspective.
I like the readability of the syntax…
Great post, and the :data selector was something I’ve been needing for a long time. I ran a quick test, but not getting the result expected. I have a simple page, with 6 p tags with a class of ‘summary’, I do $(‘.summary’).each( function(i) { $(this).data(‘cnt’, i) } )
Then I console.log $(‘:data{cnt}).size() but it returns 5 elements instead of 6. It seems to always skip the first element? I tried with various elements (li, other p tags, etc) and just running the hide() method on them, the first element always seems to get left out.
If I specifically target it, i.e $(‘:data(cnt=0)).hide() then it works as expected.
Any thoughts?
Thanks.
About a month ago I made a small patch to Sizzle to enable data and css filtering but in a slightly different/non-standard way. It modifies some regular expressions too so you can use (unescaped) dots for data namespaces:
http://jsbin.com/otoqo/edit
And just a small thing:
for (var x in e) { if((/jQueryd+/).test(x)) { return true; } }
will also return true if the element has some event handlers because then it has some data stored on it (i.e the event handlers), you can check it here: $.cache[ $(element).data() ];
Oh, I haven’t yet said: great article indeed π
Malsup, Brian, Mike, THANKS ALL! π
Alex, The reason it’s returning one less element is because on the first element’s index is 0 which is a falsey value in JavaScript. i.e.
With your particular example, you can fix it by starting the index at ONE instead of ZERO within the loop:
Alternatively, you can change the ‘:data’ extension:
Balazs, That Sizzle patch looks awesome! Saved for the future! In fact, I can imagine the CSS selector capability being really useful on it’s own – i.e. using it with Sizzle (alone), no jQuery dependency. Great work!
Thanks for pointing out the ‘:data’ issue. I hadn’t considered that events would also be stored under the same prefix, I guess that renders the ‘:data’ selector pretty useless on its own (if no parameters are provided). I can’t see any easy way round that… π It would be nice if jQuery generated a slightly different prefix for user defined data (instead of ‘jQuery…’).
Ah, James, thanks, I never realized that about 0 as a value, but now that you point it out, it makes sense.
Cool!
The βjQueryβ¦β property actually just stores a single integer, which you get by $(element).data() and the same property of the $.cache object stores the data. So you could check the $.cache[ $(element).data() ] object with a for-in loop and see if there are non-internal properties (there are just two of them, I think).
Balazs, Great idea, I’m gonna try that out tonight! π
Just wondering if there is a simple way to select all the elements before a certain one. For example, if a tr in a table has .Selected, how would I get a count of all the tr’s before it? In short, to get it’s row number?
Here you go Chad: π
Thatnk you for sharing this. π
very nice.. really useful.. thanx.. π
Great post. Really, really useful.
I just started using jquery in earnest in the last two weeks or so. I keep getting fascinated by the power of this lib.
I didn’t know it was so easy to extend the selectors. I came here searching with Google for a way to select elements with a certain. Like “find all divs with background-color: red”.
That is not AFAIK possible with jquery out of the box, but it seems to be simple to create such a selector!
Thanks a million!
Great article and great selectors.
I especially like the :data() selector. However I needed one that allowed me to query for data objects which are specified like this:
So I decided to write my own selector which allows for querying like this:
It does not support regexes like yours, however you can do ^= $= != *= like the attribute selectors.
You can have a look at the sourcecode here.
Thanks for the article as this was the article that thought me how to write :selectors in the first place. And inspired me how to create the selector.
Hi Pim, you can add ~= operator for testing regular expression
case ‘~’: // regular expression
exp = querySplitted[1].substr(1,querySplitted[1].lastIndexOf(‘/’)-1);
modif = querySplitted[1].substr(querySplitted[1].lastIndexOf(‘/’)+1)
re = new RegExp( exp, modif);
return re.test(checkAgainst);
break;
@Anon,
Great thanks for the tip. I have added the regex support.
Thank you very much for the code/info.
Hi, I’m having trouble using this. As an aid to the my co-developer, I’d like to log the data of any element that has it – what am I doing wrong?
@Dan: $.data(this)
Wow… Great post. I was in need of a solution to check width of all elements in a given block and if greater than a specified width set them to that width if they exceed it. Your width selector example fits the bill…
Also your examples of using the ‘data’ function opened up my eyes to some possibilities.
Should this work in IE7?
$(‘div.someClass:width(>500)’).remove();
I’ve noticed that IE7 appears to ignore the class selector and iterates though every DIV checking the width. Chrome & FF work fine. I fixed it by doing this…
$(‘div.someClass’).filter(‘:width(>500)’).remove();
Did I do something wrong?
Nice.., good information for selector extends