Last active
August 29, 2015 14:16
-
-
Save Error601/0d1e3d172bcd35750286 to your computer and use it in GitHub Desktop.
loadScripts - run callback functions after a number of scripts have loaded (optionally into a specified element)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/*! | |
* The gist of this gist: | |
* | |
* This script is meant to be a *simple* way | |
* to ensure JavaScript dependencies are loaded | |
* before trying to run functions that need them. | |
* This is by no means an attempt to replace AMD | |
* modules or require.js - it's just a more lightweight | |
* option for loading dependencies. | |
* | |
* After successful loading of JavaScript files | |
* perform specified callback function(s). | |
* You can do a callback for each function | |
* and another callback after all scripts | |
* have loaded. Optionally, you can specify | |
* a DOM element where you want the scripts | |
* loaded (this script uses Element.appendChild | |
* rather than AJAX, so if you want to load your | |
* scripts inside the <body>, you can do so), | |
* otherwise they will be appended to the <head>. | |
* | |
* (will not re-load scripts that are already loaded) | |
* | |
* Yes, these functions are declared in the global | |
* scope because, well, it didn't really matter | |
* in the webapp where these are used. Call it | |
* blashpemy. Call it laziness. Whatever. Feel free | |
* to wrap them in closures or add them to your | |
* app's namespace. | |
* | |
*/ | |
////////////////////////////////////////////////// | |
// DON'T RUN THIS FUNCTION (duh) | |
// FOR EXAMPLES ONLY | |
function usageExamples(){ | |
// argument signature | |
loadScripts( /* array of scripts, (parent element or callback), callback */ ); | |
// load multiple scripts and run callback after they load | |
loadScripts(['script1.js', 'script2.js'], function(){ | |
doSomething(); | |
doSomethingElse(); | |
}); | |
// call this below on success | |
function successCallback(script, status){ | |
// 'script' references the actual | |
// <script> element just loaded | |
// good idea to make sure script actually loaded | |
if (status === 'ok'){ | |
// do stuff | |
} | |
} | |
// call this below if there's an error | |
function errorCallback(script, status){ | |
// we know the 'status' is 'error' | |
// do stuff if there's an error | |
} | |
// scripts can be configured as array of objects also | |
// this allows a callback after loading of each script | |
loadScripts( | |
[ | |
{ | |
url: 'script1.js', | |
// 'success', 'complete', or 'callback' | |
// they're all the same | |
success: successCallback, | |
error: errorCallback | |
}, | |
{ | |
url: 'script2.js', | |
// can also use 'complete' method name | |
complete: function(script, status){ | |
// call anonymous function on success | |
// check status - if no 'error' | |
// function is specified, the 'callback' | |
// function will also run on error | |
if (status === 'ok'){ | |
successCallback(script, status) | |
} | |
if (status === 'error'){ | |
errorCallback(script, status) | |
} | |
} | |
} | |
], | |
// callback after all scripts are loaded | |
function(){ | |
doSomethingAfterLastScriptLoads(); | |
} | |
); | |
} | |
// end usageExamples(); | |
////////////////////////////////////////////////// | |
// add HTML5 'data-' attributes to an element | |
function setElementData(element, name, val){ | |
if (document.head && document.head.dataset){ | |
element.dataset[name] = val; | |
} | |
else { | |
element.setAttribute('data-'+name, val); | |
} | |
} | |
function getScriptElements(){ | |
var scripts = document.querySelectorAll('script[src]'), | |
scriptsArray = window.loadedScripts.slice() || [], | |
len = scripts.length, | |
i = -1, | |
src; | |
while (++i < len){ | |
src = scripts[i].getAttribute('src'); | |
if (scriptsArray.indexOf(src) === -1){ | |
scriptsArray.push(src); | |
} | |
} | |
if (window.jsdebug){ | |
console.log(scriptsArray); | |
} | |
return window.loadedScripts = scriptsArray; | |
} | |
// what scripts have loaded so far? | |
getScriptElements(); | |
function hasScript( url ){ | |
// fastest check | |
// (if window.loadedScripts has all currently loaded scripts) | |
if (window.loadedScripts.indexOf(url) > -1){ | |
return true; | |
} | |
return getScriptElements().indexOf(url) > -1; | |
} | |
// splits params contained in a string into | |
// config object properties | |
function scriptParams( script ){ | |
var obj = { url: '', min: '', name: '' }; | |
if (isString(script)){ | |
script = script.split('|'); | |
obj.url = script[0] ? script[0].trim() : ''; | |
obj.min = script[1] ? script[1].replace(/\*/,'').trim() : ''; | |
obj.name = script[2] ? script[2].replace(/\*/,'').trim() : ''; | |
obj.parent = script[3] ? script[3].replace(/\*/,'').trim() : ''; | |
} | |
else if (isPlainObject(script)) { | |
script.url = script.src = | |
script.url || script.src; // tolerate use of 'src' prop name | |
extend(obj, script); | |
} | |
obj.src = obj.url = obj.url.replace(/\.js$/i,'') + obj.min + '.js'; | |
return obj; | |
} | |
// returns DOM ELEMENT for <script> | |
function scriptElement( src, name ){ | |
var _script = document.createElement('script'); | |
_script.type = "text/javascript"; | |
// fast-track empty script (why would we need this?) | |
if (!src){ | |
return _script; | |
} | |
if (src && !hasScript(src)){ | |
_script.src = src; | |
} | |
if (name){ | |
_script.title = name; | |
setElementData(_script, 'name', name); | |
} | |
if (_script.src || src === '' || src === null){ | |
return _script; | |
} | |
return document.createDocumentFragment(); | |
} | |
// load a script, | |
// optionally into a specific parent element, | |
// and/or with a callback (optional) | |
function loadScript( /* script, parent/callback, callback */ ) { | |
var obj, parent, _parent, | |
script, status, callback, | |
noop = function(){}, | |
args = arguments, | |
arg2 = args[1], | |
arg3 = args[2], | |
len = args.length, | |
done = false; | |
if (len === 0){ | |
// no args no run | |
return; | |
} | |
if (len > 3){ | |
console.log('max 3 arguments allowed'); | |
return; | |
} | |
if (len >= 2){ | |
// parent could be second argument | |
// if there are 2 or 3 args | |
parent = arg2; | |
callback = arg3; | |
} | |
// but arg2 *could* be a callback instead of the parent | |
if (len === 2 && isFunction(arg2)){ | |
parent = 'head'; | |
callback = arg2; | |
} | |
// process params input in string or object format | |
// returns params object | |
obj = scriptParams(args[0]); | |
obj.callback = obj.success || obj.complete || obj.callback || noop; | |
if (!isFunction(callback)){ | |
callback = noop; | |
} | |
script = scriptElement(obj.src, obj.name); | |
script.url = obj.src; | |
script.onload = function(complete){ | |
status = complete || 'ok'; | |
if (!done) { | |
done = true; | |
window.loadedScripts.push(obj.src); | |
obj.callback(this, status); // callback function in config object | |
callback(this, status); // callback function argument | |
} | |
}; | |
script.onreadystatechange = function(){ | |
if (!done) { | |
if (this.readyState === 'complete') { | |
this.onload('complete'); | |
} | |
} | |
}; | |
script.onerror = function(){ | |
status = 'error'; | |
// prefer to call 'error' callback | |
obj.callback = obj.error || obj.callback; | |
if (!done) { | |
done = true; | |
obj.callback(this, status); // callback function in config object | |
callback(this, status); | |
} | |
}; | |
// 'parent' param could be a separate argument for this function | |
// or a property property on the 'script' arg | |
_parent = document.querySelector(parent||obj.parent||'head'); | |
_parent.appendChild(script); | |
} | |
// load multiple scripts | |
// (into the same parent, if specified) | |
// and run optional callbacks for each script | |
// and a final callback after all scripts are loaded | |
function loadScripts( scripts, parent_or_callback, callback ){ | |
var i = -1, _script, _parent, _callback, len; | |
scripts = scripts.slice(); | |
len = scripts.length; | |
if (len === 0){ | |
// need args | |
return; | |
} | |
if (!callback){ | |
if (isFunction(parent_or_callback)){ | |
_parent = 'head'; | |
_callback = parent_or_callback; | |
} | |
else { | |
_parent = parent_or_callback; | |
_callback = function(){}; | |
} | |
} | |
else { | |
_callback = callback; | |
} | |
while (++i < len){ | |
_script = scriptParams(scripts[i]); | |
_script.min = ''; // 'min's been added already | |
_script.parent = _script.parent || _parent; | |
if (i === len-1){ | |
// do callback after *last* script has loaded | |
loadScript(_script, _callback); | |
} | |
else { | |
loadScript(_script); | |
} | |
} | |
} | |
////////////////////////////////////////////////// | |
// helper utility functions | |
////////////////////////////////////////////////// | |
function isString( str ){ | |
return typeof str === 'string'; | |
} | |
function isPlainObject( obj ){ | |
return Object.prototype.toString.call(obj) === '[object Object]'; | |
} | |
function isFunction( func ){ | |
return typeof func == 'function'; | |
} | |
function isArray( arr ){ | |
if ( Array.isArray ) { | |
return Array.isArray(arr); | |
} | |
else { | |
return Object.prototype.toString.call(arr) === '[object Array]'; | |
} | |
} | |
// convert array-like object or arguments to a real array | |
// (twice as fast as Array.prototype.slice.call(arguments)) | |
function toArray(args) { | |
var i = -1, | |
len = args.length, | |
_args = new Array(len); | |
while (++i < len) { | |
_args[i] = args[i]; | |
} | |
return _args; | |
} | |
// copy of jQuery's $.extend() method | |
function extend(){ | |
var src, copyIsArray, copy, name, options, clone, | |
target = arguments[0] || {}, | |
i = 1, | |
length = arguments.length, | |
deep = false, | |
undefined; | |
// Handle a deep copy situation | |
if ( typeof target === "boolean" ) { | |
deep = target; | |
// skip the boolean and the target | |
target = arguments[ i ] || {}; | |
i++; | |
} | |
// Handle case when target is a string or something (possible in deep copy) | |
if ( typeof target !== "object" && !isFunction(target) ) { | |
target = {}; | |
} | |
// extend parent object if only one argument is passed | |
if ( i === length ) { | |
target = this; | |
i--; | |
} | |
for ( ; i < length; i++ ) { | |
// Only deal with non-null/undefined values | |
if ( (options = arguments[ i ]) != null ) { | |
// Extend the base object | |
for ( name in options ) { | |
// don't check for this - extend everything | |
//if ( !options.hasOwnProperty(name) ) { | |
// continue; | |
//} | |
src = target[ name ]; | |
copy = options[ name ]; | |
// Prevent never-ending loop | |
if ( target === copy ) { | |
continue; | |
} | |
// Recurse if we're merging plain objects or arrays | |
if ( deep && copy && ( isPlainObject(copy) || (copyIsArray = isArray(copy)) ) ) { | |
if ( copyIsArray ) { | |
copyIsArray = false; | |
clone = src && isArray(src) ? src : []; | |
} | |
else { | |
clone = src && isPlainObject(src) ? src : {}; | |
} | |
// Never move original objects, clone them | |
target[ name ] = extend(deep, clone, copy); | |
// Don't bring in undefined values | |
} | |
else if ( copy !== undefined ) { | |
target[ name ] = copy; | |
} | |
} | |
} | |
} | |
// Return the modified object | |
return target; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment