{"id":844,"date":"2009-06-24T07:49:11","date_gmt":"2009-06-24T13:49:11","guid":{"rendered":"http:\/\/bililite.nfshost.com\/blog\/?p=844"},"modified":"2012-12-26T15:19:56","modified_gmt":"2012-12-26T21:19:56","slug":"copying-jquery-ui-styles-and-detecting-css-loading","status":"publish","type":"post","link":"https:\/\/bililite.com\/blog\/2009\/06\/24\/copying-jquery-ui-styles-and-detecting-css-loading\/","title":{"rendered":"Copying jQuery UI styles and Detecting CSS Loading"},"content":{"rendered":"<p><strong><a href=\"\/blog\/2012\/12\/26\/themeswitcher-is-dead-long-live-themeswitcher\/\">Updated 2012-12-26 with a much better algorithm<\/a> for detecting stylesheet loading.<\/strong><\/p> \r\n<script type=\"text\/javascript\" src=\"\/inc\/themeswitcher\/jquery.themeswitcher.js\"><\/script>\r\n<p>So let's say I want to create elements that match a <a href=\"http:\/\/jqueryui.com\/themeroller\/\">jQuery UI theme<\/a> (in my case, I was using a <a href=\"https:\/\/developer.mozilla.org\/en\/Canvas_tutorial\">&lt;canvas&gt; element<\/a> and wanted to copy colors). I could try to <a href=\"\/blog\/2009\/01\/16\/jquery-css-parser\/\">parse the CSS file directly<\/a>, but that runs afoul of the <a href=\"http:\/\/www.mozilla.org\/projects\/security\/components\/same-origin.html\">same-origin security<\/a> problem (I use Google's CDN to get the jQuery UI stylesheets, as in <nobr>http:\/\/ajax.googleapis.com\/ajax\/libs\/jqueryui\/1.7.0\/themes\/smoothness\/jquery-ui.css<\/nobr>). 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:<\/p>\r\n<pre><code class=\"language-javascript\">var background = $('&lt;div class=\"ui-state-default\"&gt;').css('background-color');<\/code><\/pre>\r\n<!--more-->\r\n<p>But there's a problem if I'm using <a href=\"https:\/\/github.com\/harborhoffer\/Super-Theme-Switcher\">Themeswitcher<\/a>.\r\n<pre><code class=\"language-html demo\">\r\n&lt;div id=\"result\" style=\"background: #888; height: 2em;\"&gt;&lt;\/div&gt;\r\n&lt;div id=\"themeswitcher\"&gt;&lt;\/div&gt;\r\n<\/code><\/pre>\r\n<pre><code class=\"language-javascript demo\">\r\nfunction showcolor(){\r\n  var background = $('&lt;div id=\"tester\" class=\"ui-state-default\"&gt;').appendTo('body').css('background-color');\r\n  $('#result').text(background).css('color', background);\r\n  $('#tester').remove();\r\n}\r\n$('#themeswitcher').themeswitcher({\r\n  onSelect: showcolor,\r\n  imgpath: '\/inc\/themeswitcher\/images\/'\r\n});\r\n<\/code><\/pre>\r\n<p>If you play with this, you notice that there's a race condition going on. Themeswitcher adds a &lt;link rel=\"stylesheet\"&gt; element and the new stylesheet is loaded asynchronously. Meanwhile, the <code>onSelect<\/code> 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.<\/p>\r\n<h3>Doing It Right (and Failing)<\/h3>\r\n<p>Figuring out what to do is easy. The theme switcher adds stylesheets to the <code>&lt;head&gt;<\/code>, so the newest stylesheet is always $('link:last'). So all we need to do is tell when that &lt;link&gt; is loaded.<\/p>\r\n<p><em>That<\/em> problem is not easy. One solution is to check whether the stylesheet object associated with the link exists, with <code class=\"language-javascript\">$('link:last')[0].sheet.cssRules<\/code>\r\n (see <a href=\"http:\/\/development.lombardi.com\/?p=434\">this discussion<\/a>). But stylesheet objects are subject to the <a href=\"http:\/\/developer.yahoo.com\/yui\/stylesheet\/\">same-origin security rule<\/a>, so this fails with outside stylesheets.<\/p>\r\n<p><a href=\"http:\/\/enhance.qd-creative.co.uk\/2008\/12\/06\/the-magic-of-onload-revealed\/\">It is claimed<\/a> that the onLoad event is fired when stylesheets are loaded, but <a href=\"http:\/\/lists.whatwg.org\/pipermail\/whatwg-whatwg.org\/2009-March\/018835.html\">doesn't work in Firefox or Webkit<\/a>, which rules out all the browsers I care about.<\/p>\r\n<p>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 &lt;div&gt; but that's ugly, inefficient and assumes that the new style always uses a different color.<\/p>\r\n<h3>Hoist the Jolly Roger<\/h3>\r\n<p>If at first you don't succeed, hack it. There's a different element that <em>does<\/em> let us know when it's been loaded: images. <code class=\"language-html\">&lt;img src=\"foo\" onerror=\"func()\" \/&gt;<\/code> will try to load the file \"foo\" and, if that fails, will call <code>func<\/code>. 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:<\/p>\r\n<pre><code class=\"language-html demo\">\r\n  &lt;input type=\"button\" value=\"Better onSelect\" id=\"betteronselect\" \/&gt;\r\n<\/code><\/pre>\r\n\r\n<pre><code class=\"language-javascript demo\">\r\nvar noCache = function() {return '?'+Math.random()};\r\nvar href = $('link:last').attr('href');\r\n$('&lt;img \/&gt;').attr('src', href).error(function() {\r\n  $.noCache = function() {return '' };\r\n  $(this).remove();\r\n}).appendTo('body');\r\n$('#betteronselect').click(function(){\r\n  $('#themeswitcher').empty().themeswitcher({\r\n    imgpath: '\/inc\/themeswitcher\/images\/',\r\n    onSelect: function(){\r\n        var href = $('link:last').attr('href')+noCache();\r\n        $('&lt;img \/&gt;').attr('src', href).error(function() {\r\n          showcolor();\r\n          $(this).remove();\r\n        }).appendTo('body');\r\n    }\r\n  });\r\n});\r\n<\/code><\/pre>\r\n\r\n<p>Of course, my analysis may be wrong and the reason that this works is that it delays the <code>showcolor<\/code> long enough to lose the race. I don't think so, but it will take some experimenting to make sure.<\/p>","protected":false},"excerpt":{"rendered":"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 &lt;canvas&gt; element and wanted to copy colors). I could try to parse the CSS file directly, but that runs afoul of the same-origin [&hellip;]","protected":false},"author":2,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[10,5],"tags":[],"_links":{"self":[{"href":"https:\/\/bililite.com\/blog\/wp-json\/wp\/v2\/posts\/844"}],"collection":[{"href":"https:\/\/bililite.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/bililite.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/bililite.com\/blog\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/bililite.com\/blog\/wp-json\/wp\/v2\/comments?post=844"}],"version-history":[{"count":144,"href":"https:\/\/bililite.com\/blog\/wp-json\/wp\/v2\/posts\/844\/revisions"}],"predecessor-version":[{"id":2712,"href":"https:\/\/bililite.com\/blog\/wp-json\/wp\/v2\/posts\/844\/revisions\/2712"}],"wp:attachment":[{"href":"https:\/\/bililite.com\/blog\/wp-json\/wp\/v2\/media?parent=844"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/bililite.com\/blog\/wp-json\/wp\/v2\/categories?post=844"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/bililite.com\/blog\/wp-json\/wp\/v2\/tags?post=844"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}