Updated 2012-12-26 with a much better algorithm for detecting stylesheet loading.

So let's say I want to create elements that match a jQuery UI theme (in my case, I was using a <canvas> element and wanted to copy colors). I could try to parse the CSS file directly, but that runs afoul of the same-origin security problem (I use Google's CDN to get the jQuery UI stylesheets, as in http://ajax.googleapis.com/ajax/libs/jqueryui/1.7.0/themes/smoothness/jquery-ui.css). I could go through the document stylesheets to find the relevant styles, but there's an easier way; just create an element with the desired classes and query that:

var background = $('<div class="ui-state-default">').css('background-color');

But there's a problem if I'm using Themeswitcher.


<div id="result" style="background: #888; height: 2em;"></div>
<div id="themeswitcher"></div>

function showcolor(){
  var background = $('<div id="tester" class="ui-state-default">').appendTo('body').css('background-color');
  $('#result').text(background).css('color', background);
  $('#tester').remove();
}
$('#themeswitcher').themeswitcher({
  onSelect: showcolor,
  imgpath: '/inc/themeswitcher/images/'
});

If you play with this, you notice that there's a race condition going on. Themeswitcher adds a <link rel="stylesheet"> element and the new stylesheet is loaded asynchronously. Meanwhile, the onSelect callback is called and the style of the test element is grabbed. But, depending on whether the new stylesheet is in the cache and how fast your internet connection is, the new stylesheet may not be loaded. So the color from the old stylesheet is returned.

Doing It Right (and Failing)

Figuring out what to do is easy. The theme switcher adds stylesheets to the <head>, so the newest stylesheet is always $('link:last'). So all we need to do is tell when that <link> is loaded.

That problem is not easy. One solution is to check whether the stylesheet object associated with the link exists, with $('link:last')[0].sheet.cssRules (see this discussion). But stylesheet objects are subject to the same-origin security rule, so this fails with outside stylesheets.

It is claimed that the onLoad event is fired when stylesheets are loaded, but doesn't work in Firefox or Webkit, which rules out all the browsers I care about.

Trust me, I tried this for a long while and did a lot of searching. There's no way to tell that a stylesheet has been loaded. I could create a loop that polled for changes in the test <div> but that's ugly, inefficient and assumes that the new style always uses a different color.

Hoist the Jolly Roger

If at first you don't succeed, hack it. There's a different element that does let us know when it's been loaded: images. <img src="foo" onerror="func()" /> will try to load the file "foo" and, if that fails, will call func. But there's one catch, a bug? security feature? in Webkit: it will not call the onerror function if the file is in the cache already. It doesn't call onload; it just doesn't do anything. So we test first to see if the onerror trick works, and if it doesn't, we add a random parameter to make sure the file reloads. It means that we don't take advantage of caching the CSS file in Webkit browsers, but the code works:


  <input type="button" value="Better onSelect" id="betteronselect" />

var noCache = function() {return '?'+Math.random()};
var href = $('link:last').attr('href');
$('<img />').attr('src', href).error(function() {
  $.noCache = function() {return '' };
  $(this).remove();
}).appendTo('body');
$('#betteronselect').click(function(){
  $('#themeswitcher').empty().themeswitcher({
    imgpath: '/inc/themeswitcher/images/',
    onSelect: function(){
        var href = $('link:last').attr('href')+noCache();
        $('<img />').attr('src', href).error(function() {
          showcolor();
          $(this).remove();
        }).appendTo('body');
    }
  });
});

Of course, my analysis may be wrong and the reason that this works is that it delays the showcolor long enough to lose the race. I don't think so, but it will take some experimenting to make sure.

4 Comments

  1. John says:

    Why not:

    $(‘#switcher’).themeswitcher({
    onClose: function () {
    $.get(“example.css”, function(css) {
    $(”).html(css).appendTo(“head”);
    });
    }
    }).ready(function() {
    $.get(“example.css”, function(css) {
    $(”).html(css).appendTo(“head”);
    });
    });

  2. Danny says:

    @John:
    The problem is that the jQuery UI CSS is hosted on google.com, so I can’t do a $.get for it; that would violate the “same-origin” security policy.
    –Danny

  3. 20 Ba?ar?l? jQuery Kaynaklar? | internet, tasar?m, teknoloji ve kültür says:

    […] More Information on Copying jQuery UI styles and Detecting CSS Loading […]

  4. 20 must-have jQuery UI Resources for learner | JqueryHeaven says:

    […] More Information on Copying jQuery UI styles and Detecting CSS Loading […]

Leave a Reply


Warning: Undefined variable $user_ID in /home/public/blog/wp-content/themes/evanescence/comments.php on line 75