Forum Moderators: open

Message Too Old, No Replies

Dynamically Loading Javascripts / Knowing When Functions Are Available

Script element onload/onreadystatechange or callback's via php

         

astupidname

9:50 am on Aug 23, 2010 (gmt 0)

10+ Year Member



It's a common question, "How do I load javascripts dynamically, and if I do so how do I know when the loaded functions are available?". I'm not asking, but rather I thought I'd share a set of techniques for doing so.
You can load scripts dynamically by using document.createElement to create new script elements with the src of the script you wish to load, and then append the script to an element on the page such as the head section or body or in a div in the body or wherever really. In browsers other than Internet Explorer, script elements react to an onload attribute assigned to them as a function which will be invoked when/if the script is loaded.
IE does it differently, and script elements won't react to an onload function assigned to it, it uses onreadystatechange attribute which you may assign as a function to invoke when the script element's readyState (readyState for script elements only in IE) changes. In IE the readyState property of the script element will be one of three: 'loading', 'loaded' or 'complete'. When the script element is fully loaded in IE it's final readyState will be 'loaded' if the script was not previously cached by the browser, and will be 'complete' if the script happened to already be in the browser's cache. This means that you can check for a readyState of either 'loaded' or 'complete' and the script and it's functions should be available for use then.

So this gives us the following method for dynamically loading javascripts and knowing when the script's functions are available for use:

<script type="text/javascript">

function loadScript(url, callback) {
var js = document.createElement('script');
js.type = 'text/javascript';
js.onload = callback;
//IE won't respond to onload with script elements, uses onreadystatechange. No harm assigning in non-IE
js.onreadystatechange = function () {
var rs = js.readyState;
//alert(rs); //UNCOMMENT THIS ALERT IF YOU WISH TO SEE THE readyState
//js.readyState when script is fully loaded will be 'loaded' if it was not already
//in the browser's cache, or 'complete' if it was already cached.
//Is 'loading' while in process of loading
if (typeof callback == 'function' && rs.match(/(loaded|complete)/i)) {
callback();
}
};
//best not to define the script element's src until
//after onload and onreadystatechange have been defined
js.src = url;
document.body.appendChild(js);
}

window.onload = function () {
var url = '../js_scripts/general_utilities.js',
callback = function () {
//my 'general_utilities.js' file has a function
//named getById, so let's get notified when it's available:
alert('Loaded script file: '+ url +'\ntype of getById = '+ (typeof getById)); //type of getById = function! Good, now we may use it!
};
loadScript(url, callback);
};

</script>


That's all well and good, but is there another way? Yes, you can load scripts and know implicitly when their functions are available without using onload or onreadystatechange if you do a little script processing on the server side. In this example I am talking about just using one .php file to load any/all javascripts from, by passing a 'scriptName' GET parameter to the php file to retrieve the script. Then, if we also use a 'callback' GET parameter, we can insert a callback invocation of a function at the end of the loaded script. Plus there's really a whole lot more we can do, such as attempting to restrict direct download attempts of the script, or we could even set up the php to only retrieve certain functions from a particular javascript file -but I won't go in to that right now.

So what I'm talking about here is, you could set all your script src attributes to loadScript.php file and then use GET parameters as you will see after this:

<?php
//>loadScript.php

define('ROOT', preg_replace('/\/$/', '', $_SERVER['DOCUMENT_ROOT']));
define('SITE_NAME', preg_replace("/^www\./", '', $_SERVER['SERVER_NAME']));
define('JS_FOLDER', ROOT.'/js_scripts'); //SET JS_FOLDER TO THE LOCATION OF YOUR FOLDER CONTAINING ALL JAVASCRIPTS

//Note that only this script 'knows' where your .js files are stored now, if
//you use only this file to load your scripts. Of course the scripts are still easily accessible in browsers

$scriptName = $_GET['scriptName'];
//the scriptName can be passed with or without the '.js' extension at end of the file name
$fullScriptName = JS_FOLDER.'/'.preg_replace("/\.js$/", '', $scriptName).'.js';
$callback = $_GET['callback'];
$referrer = $_SERVER['HTTP_REFERER'];
if (!preg_match('/https?:\/\/(www.)?'.SITE_NAME.'/', $referrer)) {
//haha, anyone trying to direct download the script via
//the url to loadScript.php in the page's source will
//just get a script with an alert of "Hello World"
$scriptContent = 'alert("Hello World!");';
} else if (is_string($scriptName) && strlen($scriptName) && file_exists($fullScriptName)) {
$scriptContent = file_get_contents($fullScriptName);
if (is_string($callback) && strlen($callback)) {
$scriptContent .= "\r\n\r\n".$callback; //assuming $callback is defined as a function call with parens () to invoke it
if (!preg_match("/\(.*\)/", $callback)) { //the $callback is not defined as a function call with parens already, is just name of function to invoke
$scriptContent .= '();';
}
}
//REMOVE OR COMMENT OUT THE BELOW LINE FOR FINAL PRODUCTION USAGE, IS JUST HERE FOR TESTING/EXAMPLE PURPOSES:
$scriptContent .= "\r\n\r\nalert('this alert directly from end of file: $scriptName, referrer: $referrer');";
} else {
$scriptContent = '//File not found';
}

header('Content-type: text/javascript');
echo $scriptContent;

?>


O.k, now let's use loadScript.php to load any/all of our javascripts:

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=ISO-8859-1">
<title>Some Title</title>
<!-- loading a script with no 'callback' required upon it's loading: -->
<script type="text/javascript" src="http://www.example.com/loadScript.php?scriptName=justSomeStuff"></script>
<script type="text/javascript">

function loadScript(url) {
var js = document.createElement('script');
js.src = url;
js.type = 'text/javascript';
document.body.appendChild(js);
}

function registerScriptLoad(message) {
alert(message);
}

</script>
<script type="text/javascript"
src="http://www.example.com/loadScript.php?scriptName=generalUtilities&amp;callback=registerScriptLoad('Loaded%20script%20file:%20generalUtilities.js');"></script>
<!-- note loading the below script, it's callback has no parens (), but the php file will add them (when they are not present) to invoke the BogusObject.sayHi method -->
<script type="text/javascript" src="http://www.example.com/loadScript.php?scriptName=BogusObject&amp;callback=BogusObject.sayHi"></script>
</head>
<body>
<div>
<button type="button"
onclick="loadScript('http://www.example.com/loadScript.php?scriptName=logger&callback=LOGGER.log(\'Loaded script file: logger.js\')');">Load Script</button>
</div>
</body>
</html>


So, you may now choose which method to go by and from here on always have the ability to know when a script has loaded and it's functions or objects or other variables are available for use! Note that the first method is admittedly simpler, whereas the second method uses a bit of server processing but does offer some advanced handling capabilities which could be taken beyond what's shown here even.
Pros and cons of each? Leave a note what you like/dislike or questions about either method!

If anybody's wondering, how to post formatted code on webmasterworld [webmasterworld.com]
Do not copy formatted code on webmasterworld from IE, use other browser such as Firefox.

JAB Creations

3:14 pm on Aug 26, 2010 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member



I actually got this working and it was polling the function I needed that was stumping me. I was not able to find an acceptable AJAX solution as every site that mentioned it kept dictating that the other functions would become part of an object which would require extensively rewriting code. I was initially interested in using AJAX since you don't have to manually poll for the availability of the function(s).

http://www.webmasterworld.com/javascript/4190572.htm [webmasterworld.com]

In example the following code calls for a "chat" file (lean and mean to define the scripts directory and .js bits in the script itself) and then to poll and execute the second parameter (chat_init1 in this case). Also I highly recommend keeping script elements out of the body element as it leads to poor practices such as using document.write and innerHTML. :)

- John

function chat() {ondemand('chat','chat_init1');}