Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I load a stylesheet through ajax AND have a callback when the loading is done

I need to load a bunch of CSS files through ajax and call an animation when the stylesheet has finished loading, otherwise the animation will fail.

What I have done so and used to work pretty well until I came accross doing this cross-domain is this:

$.get(resource.url, {cache:true}, function(css) {
    //Now that the stylesheet is in the browser cache, it will load instantly:  

    $("head").append($("<link>",{
       rel: "stylesheet",
       type: "text/css",
       href: resource.url
    }));

}).then(function(){
   //Animation here that depends on the loaded css
});

This works fine as long as resource.url is on the same domain. Once I try to load the css from another domain $.get will fail like this:

XMLHttpRequest cannot load https://example.com/style.css. Origin https://example.com is not allowed by Access-Control-Allow-Origin.

So I have tried to add CORS into the header through .htaccess:

<IfModule mod_headers.c>
    #cross domain access is okay for resources (#107)
    <FilesMatch "\.(css|js)$"> 
        Header add Access-Control-Allow-Origin "*"
    </FilesMatch>
</IfModule>

This adds the CORS header to all CSS and JS resources.

For some reason CORS doesn't seem to have an effect on either chrome or firefox (newest versions).

I also came to notice that same domain policy is not enforced when doing $.getScript for js files, but it is for $.get:

$.get("https://example.com/script.js", {cache: false}, $.noop, "script"); 

//works regardless of CORS

but:

$.get("https://example.com/script.js", {cache: false}, $.noop); 

//does not work (cross domain policy violation)

So since CORS is not widely supported and doesn't even seem solve the problem for a modern browser, I need something that behaves like $.getScript, but for CSS stylesheets.

It needs to be asynchronous and have a callback mechanism.

Any ideas? Thanks in advance...

like image 905
d_inevitable Avatar asked Jul 12 '12 03:07

d_inevitable


People also ask

Which function is used to load a resource in the background in Ajax which type of calls are allowed by this function?

Ajax method The browser performs a JavaScript call to the Ajax engine. In other words, create an XMLHttpRequest object. In the background, an HTTP request is made to the server and the appropriate data is retrieved.


1 Answers

$.get uses Ajax, which must obey the Same-Origin Policy.

$.getScript normally uses Ajax, but it also has a fallback option for cross-domain use only. Scripts imported via <script> tag are not subject to Same-Origin rules, so jQuery just adds a <script> tag to the page and sets its src attribute to the requested script URL.

The same exemption is made for CSS resources loaded in through <link> tags. In a perfect world, you should be able to 1) make a new <link> element, 2) set its href attribute to the right URL and 3) listen for a load event on that element. I've added a callback to this solution from the jQuery forums:

// note: non-compatible example code, see below for better code
jQuery.getCSS = function( url, media, callback ) {
    jQuery( document.createElement('link') ).attr({
        href: url,
        media: media || 'screen',
        type: 'text/css',
        rel: 'stylesheet'
    }).appendTo('head')
    .on("load", callback);
};

One small problem here: it doesn't work cross-browser. <link> tags do not fire load events in all browsers -- see the jQuery bug report for the $.getCSS function that could have been.

So, the general-case workaround is a bit insane: add the CSS URL as the source of a new <img> tag and listen for its onerror handler:

// correct code!
jQuery.getCSS = function( url, media, callback ) {
    jQuery( document.createElement('link') ).attr({
        href: url,
        media: media || 'screen',
        type: 'text/css',
        rel: 'stylesheet'
    }).appendTo('head');

    jQuery( document.createElement('img') ).attr('src', url)
    .on("error", callback);
};

You could also add a bit of optimization and listen for load event on the <link> (for browsers that support it) and an error event on the <img> (for those that don't), and put some logic in place to ensure the callback is only ever called once, in case both events occur.

As for making $.getCSS return a deferred object, that's a bit out of my area of expertise, but there's no theoretical reason why it couldn't be done.

like image 116
apsillers Avatar answered Sep 18 '22 01:09

apsillers