2010-03-30
Style Switching with jQuery
Stylesheets seem to be a non-trivial problem with javascript. The problem I
found was that even though HTML provisions for the "alternate stylesheet" value
to the rel attribute, browsers do not tend to pay attention when this
changes.
I therefore cobbled together a quick demonstration of how to change your site's skin with some simple jQuery. I wanted to do this so that I could have a work-in-progress stylesheet but easily switch to the live stylesheet so I could also see what new things will look like when they go live.
However, it is not beyond the realms of feasibility that you might actually just have more than one stylesheet and they all work and you'd like to allow the visitor to select their preferred skin without reloading the page.
Let's say you have two stylesheets called /static/main.css and
/static/wip.css. The easiest way to allow the switch between these is to
first set up the <link> for the main stylesheet, and then to duplicate the
href to the stylesheet in another <link> tag that is specified as an
alternate stylesheet. Set up the other as another alternate stylesheet, and
give both alternate stylesheets a title:
<html> <head> <link rel="stylesheet" href="/static/main.css" /> <link rel="alternate stylesheet" href="/static/wip.css" title="wip" /> <link rel="alternate stylesheet" href="/static/main.css" title="live" /> </head>
This is repeating yourself a bit, but if you think about it semantically, these are all actually alternate stylesheets, and one of them just happens to be the current stylesheet.
Next, I set up a single link to do the switching. You might prefer to have one link per stylesheet, or a link that toggles. Either way, give all the links the same class name so we can recognise them.
<a href="#" rel="wip" class="ss-switch">Stylesheet</a>
Observe that the rel in the <a> matches the title in the
link.
I made mine toggle since there are only two stylesheets. This same bit of jQuery should work regardless of how many links you use, and it will make the link you used switch back again.
$(function() { $('a.ss-switch').click(function(){ var curr_sheet = $('link[rel=stylesheet]').attr('href'); var new_sheet = $('link[title=' + $(this).attr('rel') + ']').attr('href'); $('link[rel=stylesheet]').attr('href', new_sheet); $(this).attr('rel', $('link[href=' + curr_sheet + ']').attr('title'); return false; }); });
A description, then. Let's do it linewise.
Line 1 should be recognisable to anyone but those new to jQuery as the document-ready function. The function we run here ends on line 9, so we won't mention line 9 again.
This function is run when the document is ready to be played with - probably the most useful event in the page event cycle, and certainly the most useful feature of jQuery. (Getting the document ready event without jQuery is a nightmare, thanks to IE.)
Line 2 assigns a function to be run whenever a matching link is clicked. I have
used the class ss-switch to denote such a link.
The jQuery selector a.ss-switch means the same as the CSS selector: all a
elements with the class ss-switch. Change this if you want a different class
name. Every matched element has its click handler set to the given function,
which starts here and ends on line 8.
The magic of Javascript is that inside the click handler, the keyword this
will refer to the element that we clicked on when this function is run. That
means that it is possible to create this generic function and assign it to
several elements: we can use this inside the function to find out at
runtime which one we are actually using.
On line 3 we get the href of the current stylesheet. We'll use this later to find the title of this stylesheet in the alternate stylesheets. For this reason you should make sure that you have created an alternate stylesheet with the same href as the default one.
On line 4 we can see this funky combination of stuff. Briefly, this function
finds the URL of the alternate stylesheet that matches the rel of the
link we clicked.
Let's pick it apart. First
we see $(this).attr('rel') buried deep in the middle. This will return the
rel attribute of the link we clicked (this). We put the $() around it to
make sure it is a jQuery object. It should be, but this construct will not do
any harm. The rel of the href is inserted using string concatenation into
'link[title=foo]', which is then used in $() again, resulting in a jQuery
handle on the alternate stylesheet whose title matches the rel of the link we
clicked. On this we can call attr('href') to get the URL of the stylesheet we
are changing to, and save this in the new_sheet variable.
On line 5, we set the href attribute of the stylesheet to be the new one. The
effect of this should be immediately apparent, but be aware that it may cause a
brief second where the browser has not yet got that stylesheet and the browser's
default stylesheet shows through. You might want to consider using AJAX to load
it, and then swap it out when you have done so. Or something.
Then on line 6 we reverse the idea that we had for line 4, and swap the rel
of the link with the title of the stylesheet we are swapping from.
We use the curr_sheet variable we set up to find the <link>
tag based on its href.
There should be only one link tag with the same href as the original stylesheet,
so it should be trivial to find it. We take the title tag of that link tag and
set it to be the rel attribute of the link we clicked. That means that the
next time we click the link, the whole process should go around again, except
this time the other way around.
Most importantly, on line 7 we return false. This stops the browser following
the link on the <a> tag in the first place. You can use this behaviour to
set up a default for people without Javascript enabled: simply set instead of
the # as the href some URL that will send the browser to the same page with
the selected CSS as the default.
<a href="?style=wip" rel="wip">WIP Stylesheet</a>