nickf
nickf

Reputation: 546045

Dynamically including javascript files only once

I have a javascript function I'm writing which is being used to include an external JS file, but only once. The reason I need such a function is because it is being called when some content is loaded via AJAX and I need to run page-specific code to that content (no, just using .live won't cover it).

Here's my attempt, shortened for brevity:

$.include_once = function(filename) {
    if ($("script[src='" + filename + "']").length === 0) {
        var $node = $("<script></script>")
            .attr({
                src : filename,
                type : "text/javascript"
            })
        ;
        $(document.body).append($node);
    }
};

This works fine: the function is called, it loads the external file, and that file is being run when loaded. Perfect.

The problem is that it will always re-load that external file: the query I'm using to check for the presence of the script always finds nothing!

When debugging this, I added some lines:

alert($("script").length);     // alerts: 4
$(document.body).append($node);
alert($("script").length);     // alerts: 4

Looking in the dynamic source (the HTML tab of Firebug), I can't find the script tag at all.

I know that I could maintain an array of files that I've previously included, but I was hoping to go with a method such as this, which (if it worked), seems a bit more robust, since not all the JS files are being included in this way.

Can anyone explain the behaviour seen in this second snippet?

Upvotes: 6

Views: 6420

Answers (4)

Saghachi
Saghachi

Reputation: 945

This is the function I use to load external script and style files dynamically into my page, considering not to load an script twice and detecting both '.css' and '.js' extentions.

function lazyLoad(src) {
    head = document.getElementsByTagName('head')[0];
    if (head.innerHTML.search(src) == -1) {
        if (src.search('.js') > -1) {
            var node = document.createElement('script');
            node.src = src;
            node.async = false;
            node.setAttribute('charset', 'utf-8');
        
            head.appendChild(node);
        } else if (src.search('.css') > -1) {
            var node = document.createElement('link');
            node.href = src;
            node.setAttribute('rel', 'stylesheet');
            node.setAttribute('charset', 'utf-8');

            head.appendChild(node);
        }
    }
}

Upvotes: -1

Crescent Fresh
Crescent Fresh

Reputation: 116980

jQuery is a bit of a dumb-dumb in this case; it doesn't do at all what you'd expect. When you append($node) jQuery does this:

jQuery.ajax({
  url: $node.src,
  async: false,
  dataType: "script"
})

Woops! For local files (eg on the same domain) jQuery performs a standard XMLHttpRequest for the .js file body, and proceeds to "eval" it by a whole convoluted process of creating a <script> tag (again!) and settings it's contents to your .js file body. This is to simulate eval but in the global context.

For cross-domain files, since it cannot perform the standard XMLHttpRequest due to the same-domain policy, jQuery once again creates a <script> element and inserts it into <head>.

In both the local and cross-domain cases above jQuery finally gets around to doing this:

head.removeChild(script);

And booms your .length check! Bummer.

So on to your problem, don't bother jQuery with this. Just do

document.getElementsByTagName('head')[0]
  .appendChild(
    document.createElement('script')
  )
  .src = filename;

Which will do what you'd expect, particularly wrt querying for it later.

Upvotes: 5

Frunsi
Frunsi

Reputation: 7147

Instead of setting the source attribute of the script-tag, set the "text" attribute of the script tag. This works in all modern browsers (the application where I use that in practice does not support IE6, so I do not know about this creep...).

In practice it would look like this (you HAVE TO add code to omit double inclusion on yourself - e.g. a simple array of all alread loaded scripts, though thats very application specific, and why should you anyway load code twice? try to omit double-loading code...!):

var script_source_code_string = <some dynamically loaded script source code>;
var $n = $("<script></script>");
$n.get(0).text = script_source_code_string;
$(document.body).append($n);

Or even simpler (without jquery, my code at this stage does not know jquery, it may also be loaded dynamically):

var script_source_code_string = <some dynamically loaded script source code>;
var s = document.createElement('script');
document.getElementsByTagName('head')[0].appendChild(s);
s.text = script_source_code_string;

Upvotes: 0

Jordan Running
Jordan Running

Reputation: 106027

You're trying to solve a problem that has already been solved several times over. Try LazyLoad for example. There are also similar plugins for jQuery.

Upvotes: 3

Related Questions