ian
ian

Reputation: 12251

Check external stylesheet has loaded for fallback

There's a similar post already, but I don't feel it answers my particular question well enough.

I want to check that an external stylesheet has loaded. For example, if it has been stymied by a network problem then I want to know and load a fallback. This is as a fallback for a CDN for jQuery's themes CSS but I'd prefer this doesn't end up being specifically about that as I've other external CSS I use that I'd like to apply this to as well.


Edit: A new thought. What about if the stylesheet was loaded via AJAX, and the status code checked? Is that a viable option, does anyone know?


Here's the script I have currently:

<script type="text/javascript">
  var cdn = 'http://code.jquery.com/ui/1.10.1/themes/black-tie/jquery-ui.min.css';
  var ss = document.styleSheets;
  for (var i = 0, max = ss.length; i < max; i++) {
    var sheet = ss[i];
    var rules = sheet.rules ? sheet.rules : sheet.cssRules;
    if (sheet.href == cdn) {
      if ( rules == null || rules.length == 0) {
        var link = document.createElement("link");
        link.rel = "stylesheet";      
        link.href = '/js/jquery-ui/1.10.1/themes/black-tie/jquery-ui.min.css';
        document.getElementsByTagName("head")[0].appendChild(link);  
      }
      break;
    }  
  }
</script>

What I've found on using the console (Chrome v25.0.1364.172) is that even when the stylesheet has loaded from the CDN, document.styleSheets[x].rules is null. I'm not sure if I'm accessing it correctly in the console though, maybe there's another function I should use or object?

CSSStyleSheet {rules: null, cssRules: null, ownerRule: null, media: MediaList, title: null…}
cssRules: null
disabled: false
href: "http://code.jquery.com/ui/1.10.1/themes/black-tie/jquery-ui.min.css"
media: MediaList
ownerNode: link
ownerRule: null
parentStyleSheet: null
rules: null
title: null
type: "text/css"
__proto__: CSSStyleSheet

If anyone can help me find how to check the stylesheet has loaded, that would be very helpful and much appreciated.

Upvotes: 6

Views: 4423

Answers (1)

ian
ian

Reputation: 12251

Note:

See the production code at the github repo for the rack-jquery_ui-themes library. Ignore all interpolation of constants and the uppercase symbols like :THEME, they get substituted elsewhere. Just imagine it's pristine javascript.

What Worked

Many thanks to Brian in the comments to my question for providing the main source of inspiration for this.

<meta id='rack-jquery-ui-themes-fallback-beacon' />
<script type="text/javascript">
  var meta = $("#rack-jquery-ui-themes-fallback-beacon");
  meta.addClass("ui-icon");
  if ( meta.css('width') != "16px" ) {
    $('<link rel="stylesheet" type="text/css" href="/js/jquery-ui/#{JQueryUI::JQUERY_UI_VERSION}/themes/:THEME/#{JQUERY_UI_THEME_FILE}" />').appendTo('head');
  }
  meta.remove();
</script>

Adding a meta tag, then applying a CSS class from the jQuery UI stylesheet, then checking it had changed and adding the fallback link if not, then removing the meta tag.

There is a reason we use a meta tag in the head instead of something like a div in the body. Most CSS files are included by link elements in the head. If the script to check if it loaded comes immediately after that, since it is in the context of the head, the body has not been loaded into the DOM yet. Accessing document.body in that context will cause a null ref error.

What Didn't Work

Using AJAX to make a HEAD request and checking the status:

<script type="text/javascript">
  $.ajax({
    type: "HEAD",
    url: ':CDNURL',
    success: function(css,status) {
      // do nothing
    },
    error: function(xhr,status,error) {
      var link = document.createElement("link");
      link.rel = "stylesheet";     
      link.href = ':FALLBACK_URL';
      document.getElementsByTagName("head")[0].appendChild(link); 
    }
  });
</script>

I found out that this is a potential security threat so browsers won't allow it.

Checking the DOM for stylesheet rules:

<script type="text/javascript">
  var has_jquery_rules = false;
  var i = document.styleSheets.length - 1;
  while (i >= 0 ) {
    var sheet = document.styleSheets[i];
    if(sheet.href == ":CDNURL/ui/#{JQueryUI::JQUERY_UI_VERSION}/themes/:THEME/jquery-ui.min.css") {
      var rules = sheet.rules ? sheet.rules : sheet.cssRules;
      has_jquery_rules = rules.length == 0 ? false : true;
      break; // end the loop.
    }
    has_jquery_rules = false;
    i--;
  }
  if ( has_jquery_rules == false ) {
    $('<link rel="stylesheet" type="text/css" href="/js/jquery-ui/#{JQueryUI::JQUERY_UI_VERSION}/themes/:THEME/#{JQUERY_UI_THEME_FILE}" />').appendTo('head');
  }
</script>

This also doesn't work, as some (all?) browsers block access to rules added by an external stylesheet.

Upvotes: 5

Related Questions