1

Premise: I've found these two answers but I don't understand how I can sistematically apply it for many libraries:

Detect and log when external JavaScript or CSS resources fail to load

HTML if statement to load local JS/CSS upon CDN failure

I've an Augular 1.x application which depends on several libraries, many of them hosted on CDN or external server. Unfortunately when the user is offline, the Internet connection doesn't work well or there is a (CDN) server problem, some libraries occasionally fail to download and hence to load.

In this case I want to show the the user a view like "there was an error. Please reload or try later".

My doubts is: Given that the library are simply loaded (some of them even asynchronously) in index.html by:

<link rel="stylesheet" href="//ajax.googleapis.com/ajax/libs/angular_material/0.8.2/angular material.min.css">

for css (within head) and

<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.4.0-rc.2/angular.min.js"></script>

for JS (at the end of body)

How can I detect the down(loading) failure of even one single library? (in fact, let's say it fails to load Angular's because of connection error, the whole app won't work. but I've several mandatory libraries)

Or should I give priority to reliability(i.e. loading libraries locally) over performance (i.e. cached CDN)? Also, I think it's important to precise that some libraries (e.g. Google Apis) can't really be downloaded or installed via Bower.

Community
  • 1
  • 1
dragonmnl
  • 14,578
  • 33
  • 84
  • 129
  • *"Or should I give priority to reliability..."* Google's CDN achieves a level of reliability you'd be ***very*** hard-pressed to match, much less exceed. – T.J. Crowder May 30 '15 at 13:37
  • *"...but I've several mandatory libraries..."* You might consider creating a single combined file (which you'd then host yourself or with a CDN provider). – T.J. Crowder May 30 '15 at 13:39

3 Answers3

14

Just add an onerror handler to the tag. This can call a fallback, give the user a message etc.

Example

Using a non-existing url will here pop up an alert:

<link 
   onerror="alert('woa, error')" 
   rel="stylesheet" 
   href="//ajax.googleapis.com/ajax/libs/angular_material/0.8.2/angular material.min.cssx" 
>

For custom function, just define a script block in the header before the link tags so you can call a custom function:

<script>
  function myFunction() {alert("whaaaa.."); /* or something more meaningful */}  
</script>

<link 
   onerror="myFunction()" 
   rel="stylesheet" 
   href="//ajax.googleapis.com/ajax/libs/angular_material/0.8.2/angular material.min.cssx" 
>
  • thank you K3N. any idea how to use an AngularJS scope's function (or in general a function I defined) instead of an alert? – dragonmnl Jun 01 '15 at 18:48
  • @dragonmnl sorry, can't help with the angularjs part, but just define a script section before the links which defines a function you want to call, then change the attribute to `onerror="myFunction()"` (updated answer) –  Jun 01 '15 at 19:14
  • just one more thing: any idea how to avoid the page trying to load the other JS if even one fails to load? (e.g. in case internet connection is not available) – dragonmnl Jun 01 '15 at 19:35
  • @dragonmnl you could load the other scripts manually creating script and link elements manually, then if the condition is right set href/src and add them to header (which starts the loading). An onload event can be used instead for those cases to know when functions can be invoked. And there is the onabort event which is suitable for data interruptions. –  Jun 01 '15 at 19:42
  • This is a great solution, especially for cross-domain stylesheets where the `.rules` and `.cssRules` solutions don't work. Unfortunately IE is still a problem because it doesn't support onerror for stylesheets. https://pie.gd/test/script-link-events/ – gidmanma Nov 13 '17 at 16:22
  • Another option for cross-domain is to use the onload event. Set a var like `cdnLoaded = false` on the page. In the onload event handlers set `cdnLoaded = true` then in your onReady event check `cdnLoaded` and load your local stylesheets if it's `false` – gidmanma Nov 13 '17 at 17:35
6

What makes most sense in this case, is a feature check. Test for the existence of JS objects or JS methods to find out if a JS library has been loaded and test for the existence of certain CSS rules to find out if a CSS file has been loaded.

See this Fiddle for a demo.

See also below for the most relevant parts of the code.


How to find out whether a certain CSS rule exists :

window.CSSRuleExists = function(rule) {
    var sSheetList = document.styleSheets;
    for (var sSheet = 0; sSheet < sSheetList.length; sSheet++) {
        var ruleList = sSheetList[sSheet].cssRules;
        for (var item = 0; item < ruleList.length; item ++){
            if(ruleList[item].selectorText === rule) {
                return true;
            }
        }
    }
    return false;
}

How to find out whether a certain JS method exists :

window.JSFunctionExists = function(method, parent) {
    parent = (typeof parent === typeof undefined) ? window : parent;
    return (typeof parent[method] === 'function');
}

How to find out whether a certain JS object exists :

window.JSObjectExists = function(object, parent) {
    parent = (typeof parent === typeof undefined) ? window : parent;
    return (typeof parent[object] === 'object');
}

How to use these functions :

function process() {
    el1.innerHTML = CSSRuleExists('button.special');
    el2.innerHTML = JSFunctionExists('CSSRuleList');
    el3.innerHTML = JSObjectExists('location');
}
John Slegers
  • 45,213
  • 22
  • 199
  • 169
2

based on the accepted answer. Just tested on Firefox, Edge (the new one), Chrome and Opera.

<!DOCTYPE html>
<html lang="en-US">
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Home</title>

    <script>
        function CssOrScriptFallBack(el) {
            if (el instanceof HTMLScriptElement || el instanceof HTMLLinkElement) {
                let Url = el.getAttribute('data-fallback');
                if (Url) {
                    let attr = el instanceof HTMLScriptElement ? 'src' : 'href';
                    el.setAttribute(attr, Url);
                }
            }
        }
    </script>

    <link href='https://not-working-cdn/file-name.min.css' rel='stylesheet'  
          onerror='CssOrScriptFallBack(this)' 
          data-fallback='/css/file-name.min.css' /> 

</head>
<body>
    ...
</body>
</html>
Teo Bebekis
  • 625
  • 8
  • 9