Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

  • The reference link element will be there on the page, so pattern matching for the existence of the link element in the head is not acceptable.
  • I do not have the ability to change the CSS in the stylesheet, so adding dummy CSS is not acceptable.
  • Since I'm not in control of the CSS in the external stylesheet, relying on existing classes in the CSS is fragile, so I'd prefer if there is another solution.
  • Is using a timeout the only way? How long should I set for it? What event should I choose to measure the time?
  • I'd prefer pure javascript, then jQuery, but answers for other javascript frameworks will be accepted since they'll no doubt be helpful to someone.

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.

like image 514
ian Avatar asked Jul 16 '13 01:07

ian


People also ask

Which tag is used to include style sheet from external source?

The <link> tag is most often used to link to external style sheets or to add a favicon to your website. The <link> element is an empty element, it contains attributes only.


1 Answers

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.

like image 196
ian Avatar answered Oct 11 '22 19:10

ian