Skull Jackpot

Various thoughts of minimal interest to others

Super CSS Selector Support with jQuery

April 19, 2009

CSS provides a wide array of selectors to specify which elements should be subject to a particular style. CSS 2.1 gives web designers even more granularity when targeting specific elements. This is great in theory. But browser support for these selectors has lagged behind, and IE6 support is limited to only the most basic selectors. This has led to most folks working in the real world stashing the more advanced selectors away for rainy-day experimentation, or idle dreams about the eventual time when we can use these tools.

Current State of Affairs

Web folks are a clever bunch, though. If browser support for CSS won’t give us a way to target a particular element, a common solution is to make classes do the heavy lifting for us. Going through files to add class="text" to every text input is an all-too-common experience for web designers.

Adding the classes allows us to target elements on all browsers, but is labor-intensive and potentially error-prone. Moreover, if your site includes content (like comments or blog posts) which will entered by users, you have no easy way to ensure those classes are added appropriately.

A Glimmer of Hope

jQuery is one of several great javascript frameworks which have burst onto the scene in the past year or so. One of the reasons jQuery has gotten a lot of attention from web designers is the way it allows you to select elements – in many cases, jQuery selectors are identical to CSS selectors. jQuery is also extremely extensible. If there’s functionality you need which isn’t provided by the core framework, you can easily add it via a plugin (or by extending jQuery directly).

In the course of a recent project, I used jQuery to add a first-child class to some <li> elements in our top navigation, to shore up IE6 CSS support without having to alter the markup. After seeing how easy jQuery made that, I realized it’s be possible to write a plugin allowing any browser supported by jQuery capable of supporting all CSS 2.1 selectors. So I wrote SuperSelectors. It goes through your site’s stylesheets, dynamically adding classes to elements so that even IE6 can bask in the glow of properly supporting CSS selectors like ul li:first-child li:nth-child(odd) a:hover.

Pencils out

Before we can start to determine which elements ought to have classes added, we’ll need a way to access each stylesheet being used by a given page. Fortunately, the document.styleSheets array does exactly what we want. Unfortunately, we have to incur a little bit of overhead here. Since browsers will silently excise rules they don’t understand, we have to re-request the CSS files via javascript, and parse those for matches.

Now, the fact that jQuery supports CSS selector syntax comes in really handy. We can pass the matching CSS rules directly to jQuery, and add classes appropriately. Then, any browser supported by jQuery can properly target elements, even useful-but-not-broadly-supported CSS rules like ul li:first. All we need to do to have classes added to match any of the rules in our CSS is include jQuery and the SuperSelectors plugin, and add the following javascript:

$(document).ready(function() {
  $(document).superSelectify();
});

This is a great start, but some of us are maintaining sites with established conventions for element classnames. Maybe you’re working on an educational site, and the child is already reserved for another context. No problem. SuperSelectors will allow custom classes to be passed in, while still providing sensible defaults. You can override any (or all) of the default classnames. If you need to alter the class attached to child elements to be child-element, your javascript would just change to be:

$(document).ready(function() {
  $(document).superSelectify({childClass: "child-element"});
});

Attribution needed

What about special cases which fall outside the normal advanced selectors? Maybe we need to have styles applied conditionally, based on attributes. For example, maybe the links in your file download list should have an icon based on the extension of the file they’re pointing to. CSS 2.1 allows us to target elements based on attribute values – we can even match against substrings within the attribute value, which is a perfect fit for finding the file extensions of our links. The number of potential choices for attribute selectors alone is more than we can reasonably build into a single plugin, but fortunately SuperSelectors supports arbitrary selectors through the additionalElementHash attribute. This can be done with or without overriding the default classnames. A setup which addressed the file-type requirement mentioned above would look like this:

$(document).ready(function() {
  $(document).superSelectify({additionalElementHash: {jpeg: 'a[href$=".jpg"]', pdf: 'a[href$=".pdf"]', doc: 'a[href$=".doc"]', gif: 'a[href$=".gif"]', xls: 'a[href$=".xls"]', png: 'a[href$=".png"]'}});
});

A little more effort than the no-argument invocation, but that will enable matching almost any elements you can think of – you’re only limited by jQuery’s selector support, which is one of the best out there.

Hovercraftiness

It’s probably time to acknowledge the elephant in the room – specifically, the fact that IE6 only supports the :hover pseudo-selector for anchor tags. Through the years, there have been a variety of approaches to add this in, including Peter Nederlof’s whatever:hover. This is all well and good, but it’d be nice to be able to have one-stop shopping for all our CSS needs. Fortunately, jQuery makes it easy for us to add this functionality into Super Selectors. Declare your :hover rules normally, add an equivalent rule using the hover class, and SuperSelectors will take care of adding / removing the classnames from elements on mouseover.

Faster, faster

Things are really shaping up nicely. Any browser which jQuery supports can now use virtually any legitimate CSS selector, but we can still do better. If we know which advanced selectors our site will need to support, it seems wasteful to have to re-request all those CSS files, and then scan them for matches. In fact, on slow connections, or with sufficiently-large amount of CSS, users could see a page without the added classes for a fraction of a second, similar to the old Flash of Unstyled Content bug.

SuperSelectors makes it simple to avoid the unnecessary request/parsing overhead and display properly-classed markup right away. You just need to pass in all your selectors via the manualSelectors attribute, as a comma-delimited list. As with everything else, it can be used solo or in combination with any other arguments.:

$(document).ready(function() {
  $(document).superSelectify({childClass: "child-element", manualSelectors: 'div.example > ul, ul.sample li:first, input[type="text"], input[type="password"]'}});
});

For complex CSS needs, this may require a fair amount of work. However, this approach saves us from having to re-request and parse all the CSS files in use. Consequently, it will be much faster – especially for sites with large (or large numbers of) CSS files. Check out the example.

One caveat

SuperSelectors does a lot to help enable better CSS selector support across browsers, but it can’t do everything for us. We still need rules in our CSS which use the classes SuperSelectors will add.

Because Internet Explorer will discard the entire rule if it doesn’t understand one of the selectors, we do have to do a little duplication in our CSS. That means that, instead of

div.example > ul,
div.example ul.child {
background: #0f0;
color: #f0f;
}

You should write:

div.example > ul {
background: #0f0;
color: #f0f;
}

div.example ul.child {
background: #0f0;
color: #f0f;
}

In Closing

The goal of SuperSelectors is to make it possible for all of us to start using CSS 2.1 selectors in our everyday work. It doesn’t have CSS3 support (yet), but SuperSelectors will automatically benefit as jQuery adds support for CSS3 selectors.

You can download SuperSelectors as a zip or tarball. Github also hosts the official SuperSelectors repository, if you’re interested in helping make SuperSelectors even better.

21 comments to “Super CSS Selector Support with jQuery”

  1. April 20 2009 at 8:24 pm Josh wrote:

    this is *awesome*. nice work chris!

  2. April 20 2009 at 9:21 pm Chris wrote:

    Thanks, Josh!

    I’m already starting to use this in all my work, and hope that other folks find it useful, too.

    It’ll be coming to skulljackpot.com soon :)

  3. April 21 2009 at 9:27 am Nicholas Begson-Shilcock wrote:

    Good write up, Chris. This looks like it will be really useful :)

  4. April 22 2009 at 11:36 pm Annie wrote:

    Chris!! Thanks so much for this! This is really going to come handy. I have to agree, this is an awesome write-up. I’m pimpin’ this one out fo sho’! :-D

  5. June 9 2009 at 1:25 pm Alan wrote:

    Chris, your Super CSS Selector looks great! I was worrying about how to accomplish this very task when I found your page.

    Unfortunately I can’t use GPL 3 licenses in my project. Would you consider a commercial license version?

  6. November 9 2009 at 6:57 am Bill wrote:

    Maybe I’m mental, but this is totally not working at all for me. Can you please show a simple example of how to implement this.

    For example, if my CSS is this:

    #nav ul li:first-child {
    margin-left:0;
    }

    and I attach super selctor js and jquery before it:

    and then put this function in:

    $(document).ready(function() {
    $(document).superSelectify();
    });

    No in IE6 my first-child pseudo class should work? Is that right? Did I miss something?

  7. November 9 2009 at 7:10 am Chris wrote:

    You’re very close, Bill.

    Right now, SuperSelectors just adds classes to the appropriate elements to allow

    Change the relevant portion of your CSS to

    #nav ul li:first-child {
    margin-left:0;
    }

    #nav ul li.first-child { /* targets the class SuperSelectors will add for us */
    margin-left:0;
    }

    and it should work for you.

  8. November 18 2009 at 11:03 pm Eric Lightbody wrote:

    This plugin looks amazing, however, I’m getting this error within FF using jquery 1.3.2: uncaught exception: Syntax error, unrecognized expression: >

  9. November 19 2009 at 11:00 am Chris wrote:

    @Eric- Can you post a link to a page which throws that error? I’m unable to replicate (using Firefox 3.5.5 – jquery 1.3.2).

    Do the test or demo pages also throw that error for you?

  10. December 3 2009 at 2:32 pm Darrel wrote:

    Will this also accommodate multiple class selectors in IE6?

    One of the biggest drags in supporting IE6 is having to bloat the CSS with unique class names:

    .myButton
    .myBigButton

    I’d much rather use:

    .myButton.big but IE6 can’t support this.

    I’ve been trying to come up with a solution via jQuery but haven’t thought of one yet. This plugin looks great, but I didn’t see a mention of multi-class support.

  11. December 3 2009 at 2:54 pm Chris wrote:

    @Darrel – You can get most of the way there already by using the additionalElementHash parameter.

    $(document).superSelectify({additionalElementHash: {myBigButton: '.myButton.big'}});

    Will automatically add the class “myBigButton” to all the elements you’re concerned with.

    As noted under “One Caveat” above, you’ll still need to have a duplicate rule in your CSS file for now, but you won’t have to worry about any of that clutter in your markup (or having to make sure that every single case where “myButton” and “big” are both set also has “myBigButton”.

    Hope that helps!

  12. December 3 2009 at 2:56 pm Darrel wrote:

    Chris:

    Yes, it helps. I’ve used that technique before, albeit manually.

    Indeed, it helps de-clutter the HTML, but as you state, one still has a cluttered CSS file.

    As of now, I think I’m going to grin and bear it. If we’re supporting IE6, then we’re just going to have to deal with bloated CSS. Hopefully that’ll be yet-another-argument to drop IE6 support soon.

    One can hope. ;o)

  13. December 5 2009 at 8:07 pm mcm_ham wrote:

    Just letting you know there’s another discussion about this idea here:


    http://forums.mozillazine.org/viewtopic.php?f=25&t=1591615

  14. December 7 2009 at 4:59 pm Eric Lightbody wrote:

    @chris I don’t remember what was going on before, but I tried it on a new project and I have pseudo selectors in ie now. So awesome, thank you!

  15. December 10 2009 at 5:53 pm Chris wrote:

    Just pushed out an updated version of SuperSelectors this afternoon.

    The quick example mcm_ham had posted over in Mozillazine prompted a brainstorm, and I managed to solve the biggest limitation in SuperSelectors (needing to have duplicate style rules in your CSS).

    SuperSelectors will now create the CSS for you – this means that you can write rules like div.example > div input[type="text"] {background: #f0f;} and have it just work – even in IE6!

  16. January 23 2010 at 8:50 am kevadamson wrote:

    Hi

    Can get this to work, but it seems to be stripping all my css background-images out in IE (at least, when I preview the file locally)!?

    Also, I tend to pull in CSS using:

    /*\*/
    @import “../assets/css/layout.css”;
    /**/

    but it doesn’t work when I do this.

    Any thoughts? Thanks :)

  17. January 23 2010 at 6:12 pm Chris wrote:

    @kevadamson -

    Mind posting your test case for background images for me somewhere? I’m not quite sure what’d be causing those symptoms, offhand.

    I’ll look at the import problem. It’s possible that the import syntax you use is tripping up the regular expression for comment removal – is that the old commented backslash trick to hide from IE5/Mac? Man, I didn’t realize that browser was still getting use. :)

  18. February 1 2010 at 12:37 pm manuel wrote:

    Chris, I suggest you edit the post to remove the “caveat” part. I almost disregarded your script because of that (before reading the comments).

    RE: IE Mac, it’s not being used anymore, I think. But from an accessibility standpoint, a lot of people hides some or all CSS from it, to make the content at least readable and navigable by the small % of people using it.

  19. February 1 2010 at 12:42 pm Chris wrote:

    That’s a fair point, manuel – enough enhancements have happened since the initial release that I probably should do a new writeup.

  20. July 13 2010 at 5:02 pm RogerH wrote:

    Hi Chris,

    I second the motion for a updated article incorporating all the changes. A few examples in the README file would be a great help.

    I am having much trouble getting this to work. Doesn’t this example have one too many/few right/left curley brackets?

    $(document).ready(function() {
    $(document).superSelectify({childClass: “child-element”, manualSelectors: ‘div.example > ul, ul.sample li:first, input[type="text"], input[type="password"]‘}});
    });

    From reading the code, I didn’t notice anything for :before. Is it supported?

  21. July 17 2010 at 2:09 pm RogerH wrote:

    After several attempts, I did get manualSelectors working. I copied the linked.html file and modified the bottom so it looks like this:

    // ul {background: #c2f060;color: #5d5f2e;}\n\
    #ex12 div.example input[type="text"] {background: #c2f060;border: 1px solid #c2f060;color: #5d5f2e;}\n\
    #ex13 div.example input[type="password"] {background: #c2f060;border: 1px solid #c2f060;color: #5d5f2e;}\n\
    #ex14 div.example input[type="radio"] {background: #c2f060;border: 1px solid #c2f060;color: #5d5f2e;}\n\
    #ex15 div.example input[type="checkbox"] {background: #c2f060;border: 1px solid #c2f060;color: #5d5f2e;}\n\
    #ex16 div.example input[type="file"] {background: #c2f060;border: 1px solid #c2f060;color: #5d5f2e;}\n\
    #ex17 div.example input[type="button"] {background: #c2f060;border: 1px solid #c2f060;color: #5d5f2e;}\n\
    #ex18 div.example input[type="image"] {background: #c2f060;border: 1px solid #c2f060;color: #5d5f2e;}\n\
    #ex19 div.example input[type="reset"] {background: #c2f060;border: 1px solid #c2f060;color: #5d5f2e;}\n\
    #ex20 div.example input[type="submit"] {background: #c2f060;border: 1px solid #c2f060;color: #5d5f2e;}\n\
    #ex21 div.example a[href$=".jpg"] {background: #c2f060;color: #5d5f2e;}\n\
    ‘});

    //]]>

    Is the above the way you would have coded it?

    I did find an oldish plugin called pseudo that supports :before and :after.

Leave a comment

HTML - You can use:<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>