How can you reliably and dynamically load a javascript file? This will can be used to implement a module or component that when 'initialized' the component will dynamically load all needed javascript library scripts on demand.
The client that uses the component isn't required to load all the library script files (and manually insert tags into their web page) that implement this component - just the 'main' component script file.
How do mainstream javascript libraries accomplish this (Prototype, jquery, etc)? Do these tools merge multiple javascript files into a single redistributable 'build' version of a script file? Or do they do any dynamic loading of ancillary 'library' scripts?
An addition to this question --- is there a way to handle the event after a dynamically included javascript file is loaded? Prototype has document.observe for document-wide events. Example:
document.observe("dom:loaded", function() {
// initially hide all containers for tab content
$$('div.tabcontent').invoke('hide');
});
What are the available events for a script element?
Source: Tips4all
I did basically the same thing that you did Adam, but with a slide modification to make sure I was appending to the head tag to get the job done. I simply created an include function (code below) to handle both script and css files.
ReplyDeleteThis function also checks to make sure that the script or CSS file hasn't already been loaded dynamically. It does not check for hand coded values and there may have been a better way to do that, but it served the purpose.
function include( url, type ){
// First make sure it hasn't been loaded by something else.
if( Array.contains( includedFile, url ) )
return;
// Determine the MIME-type
var jsExpr = new RegExp( "js$", "i" );
var cssExpr = new RegExp( "css$", "i" );
if( type == null )
if( jsExpr.test( url ) )
type = 'text/javascript';
else if( cssExpr.test( url ) )
type = 'text/css';
// Create the appropriate element.
var tag = null;
switch( type ){
case 'text/javascript' :
tag = document.createElement( 'script' );
tag.type = type;
tag.src = url;
break;
case 'text/css' :
tag = document.createElement( 'link' );
tag.rel = 'stylesheet';
tag.type = type;
tag.href = url;
break;
}
// Insert it to the <head> and the array to ensure it is not
// loaded again.
document.getElementsByTagName("head")[0].appendChild( tag );
Array.add( includedFile, url );
}
I used a much less complicated version recently with jQuery:
ReplyDelete<script src="scripts/jquery.js" type="text/javascript"></script>
<script type="text/javascript">//<![CDATA[
var js = ["scripts/jquery.dimensions.js", "scripts/shadedborder.js", "scripts/jqmodal.js", "scripts/main.js"];
var $head = $("head");
for (var i = 0; i < js.length; i++) $head.append("<script src=\"" + js[i] + "\" type=\"text/javascript\"></scr" + "ipt>");
//]]></script>
It worked great in every browser I tested it in: IE6/7, Firefox, Safari, Opera.
Update: jQuery-less version:
<script type="text/javascript">//<![CDATA[
var js = ["scripts/jquery.dimensions.js", "scripts/shadedborder.js", "scripts/jqmodal.js", "scripts/main.js"];
for (var i = 0, l = js.length; i < l; i++) document.getElementsByTagName("head")[0].innerHTML += ("<script src=\"" + js[i] + "\" type=\"text/javascript\"></scr" + "ipt>");
//]]></script>
Here is some example code I've found... does anyone have a better way?
ReplyDeletefunction include(url)
{
var s = document.createElement("script");
s.setAttribute("type", "text/javascript");
s.setAttribute("src", url);
var nodes = document.getElementsByTagName("*");
var node = nodes[nodes.length -1].parentNode;
node.appendChild(s);
}
Just found out about a great feature in YUI 3 (at the time of writing available in preview release). You can easily insert dependencies to YUI libraries and to "external" modules (what you are looking for) without too much code: YUI Loader.
ReplyDeleteIt also answers your second question regarding the function being called as soon as the external module is loaded.
Example:
YUI({
modules: {
'simple': {
fullpath: "http://example.com/public/js/simple.js"
},
'complicated': {
fullpath: "http://example.com/public/js/complicated.js"
requires: ['simple'] // <-- dependency to 'simple' module
}
},
timeout: 10000
}).use('complicated', function(Y, result) {
// called as soon as 'complicated' is loaded
if (!result.success) {
// loading failed, or timeout
handleError(result.msg);
} else {
// call a function that needs 'complicated'
doSomethingComplicated(...);
}
});
Worked perfectly for me and has the advantage of managing dependencies. Refer to the YUI documentation for an example with YUI 2 calendar.
does anyone have a better way?
ReplyDeleteI think just adding the script to the body would be easier then adding it to the last node on the page. How about this:
function include(url) {
var s = document.createElement("script");
s.setAttribute("type", "text/javascript");
s.setAttribute("src", url);
document.body.appendChild(s);
}
The technique we use at work is to request the javascript file using an AJAX request and then eval() the return. If you're using the prototype library, they support this functionality in their Ajax.Request call.
ReplyDeletejquery resolved this for me with its .append() function
ReplyDelete- used this to load the complete jquery ui package
/*
* FILENAME : project.library.js
* USAGE : loads any javascript library
*/
var dirPath = "../js/";
var library = ["functions.js","swfobject.js","jquery.jeditable.mini.js","jquery-ui-1.8.8.custom.min.js","ui/jquery.ui.core.min.js","ui/jquery.ui.widget.min.js","ui/jquery.ui.position.min.js","ui/jquery.ui.button.min.js","ui/jquery.ui.mouse.min.js","ui/jquery.ui.dialog.min.js","ui/jquery.effects.core.min.js","ui/jquery.effects.blind.min.js","ui/jquery.effects.fade.min.js","ui/jquery.effects.slide.min.js","ui/jquery.effects.transfer.min.js"];
for(var script in library){
$('head').append('<script type="text/javascript" src="' + dirPath + library[script] + '"></script>');
}
To Use - in the head of your html/php/etc after you import jquery.js you would just include this one file like so to load in the entirety of your library appending it to the head...
<script type="text/javascript" src="project.library.js"></script>
Is Rhino one of these tools?
ReplyDeleteRhino is just a JavaScript Engine. Over at Wikipedia they say that (JavaScript_engine)">Rhino is one of Mozilla's ECMA-262 Edition 3 compliant JavaScript Engines. Its most unique quality is that it was written in Java and is open source. You can download the free Java libraries and run a Rhino Javascript Shell right from your terminal. This is quite useful. You can do the same with (Javascript_engine)">SpiderMonkey another Mozilla JavaScript engine that is written in C.
I guess they have tools that merge multiple javascript files into a single redistributable 'build' version of a script file?
ReplyDeleteThis may be a little off topic but I'll throw it out there.
There is a neat way that you can do this on the server side. If you wanted to do this dynamically you would craft a request to the server's script listing the files on in the URL like query parameters. The server script can than combine the scripts together and returns them in a single script.
The one that I am most familiar with can be found by googling combine.php. That article goes above and beyond by using URL Rewriting to make the syntax easier to construct, but the idea still stands that you can reduce the size of files and reduce the number of requests. As always you should be gzipping javascript responses.
Sorry for the multiple posts. It just seems like there are multiple questions =)
all the major javascript libraries like jscript, prototype, YUI have support for loading script files. For example, in YUI, after loading the core you can do the following to load the calendar control
ReplyDeletevar loader = new YAHOO.util.YUILoader({
require: ['calendar'], // what components?
base: '../../build/',//where do they live?
//filter: "DEBUG", //use debug versions (or apply some
//some other filter?
//loadOptional: true, //load all optional dependencies?
//onSuccess is the function that YUI Loader
//should call when all components are successfully loaded.
onSuccess: function() {
//Once the YUI Calendar Control and dependencies are on
//the page, we'll verify that our target container is
//available in the DOM and then instantiate a default
//calendar into it:
YAHOO.util.Event.onAvailable("calendar_container", function() {
var myCal = new YAHOO.widget.Calendar("mycal_id", "calendar_container");
myCal.render();
})
},
// should a failure occur, the onFailure function will be executed
onFailure: function(o) {
alert("error: " + YAHOO.lang.dump(o));
}
});
// Calculate the dependency and insert the required scripts and css resources
// into the document
loader.insert();
If you have jQuery loaded already, you should use $.getScript.
ReplyDeleteThis has an advantage over the other answers here in that you have a built in callback function (to guarantee the script is loaded before the dependant code runs) and you can control caching.
There are scripts that are designed specifically for this purpose.
ReplyDeleteyepnope.js is built into Modernizr, and lab.js is a more optimized (but less user friendly version.
I wouldn't reccomend doing this through a big library like jquery or prototype - because one of the major benefits of a script loader is the ability to load scripts early - you shouldn't have to wait until jquery & all your dom elements load before running a check to see if you want to dynamically load a script.
If you attempt to load a JavaScript file outside of the head tag you will break w3c and xhtml standards.
ReplyDeleteYou will also lose Opera and konqueror support.