6

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?

4 Answers 4

5

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.

Sign up to request clarification or add additional context in comments.

2 Comments

Excellent response. I figured jQuery was being a bit too clever for itself or something.
@nickf: I feel that way about most of jQuery's innards (being too clever that is ;) There's lots of necessary evils in there I guess.
3

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.

Comments

0

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;

Comments

-1

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);
        }
    }
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.