only.js
Version:
Automatically load and execute selector-based dependencies
351 lines • 19.9 kB
JavaScript
/*! Only.js (Only Javascript in HTML - Conditional JavaScript dependency loader & manager)
v2.0.1 (c) Harrison Emmanuel (2016-05-22)
MIT License
------------------------------------------
Built on LAB.js v2.0.3 (c) Kyle Simpson
*/
(function(global) {
"use strict";
var _$O = global.$O, _js = "js", _wait = "wait", _text = "text", _type = "type", _charset = "charset", _elem = "elem", _head = "head", _exec = "exec", _push = "push", _exec_trigger = _exec + "_trigger", _on = "on", _load = "load", _onload = _on + _load, _preload = "pre" + _load, _onpreload = _on + _preload, _loaded = _load + "ed", _className = "className", _doScroll = "doScroll", _document = "document", _Element = "Element", _documentElement = _document + _Element, _finished = "finished", _finished_listeners = _finished + "_listeners", _createElement = "create" + _Element, _frameElement = "frame" + _Element, _insertBefore = "insertBefore", _charAt = "charAt", _call = "call", _callee = _call + "ee", _splice = "splice", _slice = "slice", _script = "script", _scripts = _script + "s", _console = "console", _log = "log", _end = "end", _setOptions = "setOptions", _test = "test", _src = "src", _real_src = "real_" + _src, _error = "error", _toString = "toString", _prototype = "prototype", _ready = "ready", _readyState = _ready + "State", _length = "length", _querySelector = "querySelector", _complete = "complete", _ready_listeners = _ready + "_listeners", _attributes = "attributes", _Event = "Event", _attachEvent = "attach" + _Event, _detachEvent = "detach" + _Event, _setAttribute = "setAttribute", _readystatechange = _ready + "statechange", _onreadystatechange = _on + _readystatechange, _Listener = "Listener", _addEvent = "add" + _Event, _addEventListener = _addEvent + _Listener, _removeEventListener = "remove" + _Event + _Listener, _DOMContentLoaded = "DOMContentLoaded", _l = location, _useLocalXHR = "useLocalXHR", _alwaysPreserveOrder = "alwaysPreserveOrder", _allowDuplicates = "allowDuplicates", _cacheBust = "cacheBust", _basePath = "basePath", _autoPolyfill = "autoPolyfill", root_page = /^[^?#]*\//[_exec](_l.href)[0], root_domain = /^[\w|\-]+\:\/\/\/?[^\/]+/[_exec](root_page)[0], append_to = global[_document][_head] || global[_document].getElementsByTagName(_head)[0], opera_or_gecko = global.opera && Object[_prototype][_toString][_call](global.opera) == "[object Opera]" || "MozAppearance" in global[_document][_documentElement].style, test_script_elem = global[_document][_createElement](_script), explicit_preloading = typeof test_script_elem[_preload] == "boolean", real_preloading = explicit_preloading || test_script_elem[_readyState] && test_script_elem[_readyState] == "uninitialized", script_ordered_async = !real_preloading && test_script_elem.async === true, xhr_or_cache_preloading = !real_preloading && !script_ordered_async && !opera_or_gecko;
function contentLoaded(win, fn) {
var done = false, doc = win[_document], root = doc[_documentElement], modern = doc[_addEventListener], add = modern ? _addEventListener : _attachEvent, rem = modern ? _removeEventListener : _detachEvent, pre = modern ? "" : _on, init = function(e) {
if (e[_type] === _readystatechange && doc[_readyState] !== _complete) return;
(e[_type] === _load ? win : doc)[rem](pre + e[_type], init, false);
if (!done && (done = true)) fn.call(win, e[_type] || e);
}, poll = function() {
try {
root[_doScroll]("left");
} catch (e) {
setTimeout(poll, 50);
return;
}
init("poll");
};
if (doc[_readyState] === _complete) fn.call(win, "lazy"); else {
if (!modern && root[_doScroll]) try {
if (!win[_frameElement]) poll();
} catch (e) {}
doc[add](pre + _DOMContentLoaded, init, false);
doc[add](pre + _readystatechange, init, false);
win[add](pre + _load, init, false);
}
}
function is_func(func) {
return Object[_prototype][_toString][_call](func) == "[object Function]";
}
function is_array(arr) {
return Object[_prototype][_toString][_call](arr) == "[object Array]";
}
function canonical_uri(src, base_path) {
var absolute_regex = /^\w+\:\/\//;
if (/^\/\/\/?/[_test](src)) src = _l.protocol + src; else if (!absolute_regex[_test](src) && src[_charAt](0) != "/") src = (base_path || "") + src;
return absolute_regex[_test](src) ? src : (src[_charAt](0) == "/" ? root_domain : root_page) + src;
}
function merge_objs(source, target) {
for (var k in source) if (source.hasOwnProperty(k)) target[k] = source[k];
return target;
}
function check_chain_group_scripts_ready(chain_group) {
var any_scripts_ready = false;
for (var i = 0; i < chain_group[_scripts][_length]; i++) if (chain_group[_scripts][i][_ready] && chain_group[_scripts][i][_exec_trigger]) {
any_scripts_ready = true;
chain_group[_scripts][i][_exec_trigger]();
chain_group[_scripts][i][_exec_trigger] = null;
}
return any_scripts_ready;
}
function create_script_load_listener(elem, registry_item, flag, onload) {
elem[_onload] = elem[_onreadystatechange] = function() {
if (elem[_readyState] && elem[_readyState] != _complete && elem[_readyState] != _loaded || registry_item[flag]) return;
elem[_onload] = elem[_onreadystatechange] = null;
onload();
};
}
function script_executed(registry_item) {
registry_item[_ready] = registry_item[_finished] = true;
for (var i = 0; i < registry_item[_finished_listeners][_length]; i++) registry_item[_finished_listeners][i]();
registry_item[_ready_listeners] = [];
registry_item[_finished_listeners] = [];
}
function request_script(chain_opts, script_obj, registry_item, onload, preload_this_script) {
setTimeout(function() {
var script, src = script_obj[_real_src], xhr;
if ("item" in append_to) {
if (!append_to[0]) {
setTimeout(arguments[_callee], 25);
return;
}
append_to = append_to[0];
}
script = global[_document][_createElement](_script);
if (script_obj[_type]) script[_type] = script_obj[_type];
if (script_obj[_charset]) script[_charset] = script_obj[_charset];
if (_attributes in script_obj) for (var key in script_obj[_attributes]) if (script_obj[_attributes].hasOwnProperty(key)) script[_setAttribute](key, script_obj[_attributes][key]);
if (preload_this_script) {
if (real_preloading) {
registry_item[_elem] = script;
if (explicit_preloading) {
script[_preload] = true;
script[_onpreload] = onload;
} else script[_onreadystatechange] = function() {
if (script[_readyState] == _loaded) onload();
};
script[_src] = src;
} else if (preload_this_script && src.indexOf(root_domain) === 0 && chain_opts[_useLocalXHR]) {
xhr = new XMLHttpRequest();
xhr[_onreadystatechange] = function() {
if (xhr[_readyState] == 4) {
xhr[_onreadystatechange] = function() {};
registry_item[_text] = xhr.responseText + "\n//@ sourceURL=" + src;
onload();
}
};
xhr.open("GET", src);
xhr.send();
} else {
script[_type] = "text/cache-script";
create_script_load_listener(script, registry_item, _ready, function() {
append_to.removeChild(script);
onload();
});
script[_src] = src;
append_to[_insertBefore](script, null);
}
} else {
if (script_ordered_async) {
script.async = false;
create_script_load_listener(script, registry_item, _finished, onload);
} else {
create_script_load_listener(script, registry_item, _finished, onload);
}
script[_src] = src;
append_to[_insertBefore](script, null);
}
}, 0);
}
function create_sandbox() {
var global_defaults = {}, can_use_preloading = real_preloading || xhr_or_cache_preloading, registry = {}, instanceAPI, nullAPI;
global_defaults[_useLocalXHR] = true;
global_defaults[_alwaysPreserveOrder] = false;
global_defaults[_allowDuplicates] = false;
global_defaults[_cacheBust] = false;
global_defaults[_basePath] = "";
global_defaults[_autoPolyfill] = false;
function execute_preloaded_script(chain_opts, script_obj, registry_item) {
var script;
function preload_execute_finished() {
if (script !== null) {
script = null;
script_executed(registry_item);
}
}
if (registry[script_obj[_src]][_finished]) return;
if (!chain_opts[_allowDuplicates]) registry[script_obj[_src]][_finished] = true;
script = registry_item[_elem] || global[_document][_createElement](_script);
if (script_obj[_type]) script[_type] = script_obj[_type];
if (script_obj[_charset]) script[_charset] = script_obj[_charset];
if (_attributes in script_obj) for (var key in script_obj[_attributes]) if (script_obj[_attributes].hasOwnProperty(key)) script[_setAttribute](key, script_obj[_attributes][key]);
create_script_load_listener(script, registry_item, _finished, preload_execute_finished);
if (registry_item[_elem]) registry_item[_elem] = null; else if (registry_item[_text]) {
script[_onload] = script[_onreadystatechange] = null;
script[_text] = registry_item[_text];
} else script[_src] = script_obj[_real_src];
append_to[_insertBefore](script, null);
if (registry_item[_text]) preload_execute_finished();
}
function do_script(chain_opts, script_obj, chain_group, preload_this_script) {
var registry_item, registry_items, ready_cb = function() {
script_obj.ready_cb(script_obj, function() {
execute_preloaded_script(chain_opts, script_obj, registry_item);
});
}, finished_cb = function() {
script_obj.finished_cb(script_obj, chain_group);
};
script_obj[_src] = canonical_uri(script_obj[_src], chain_opts[_basePath]);
script_obj[_real_src] = script_obj[_src] + (chain_opts[_cacheBust] ? (/\?.*$/[_test](script_obj[_src]) ? "&_" : "?_") + ~~(Math.random() * 1e9) + "=" : "");
if (!registry[script_obj[_src]]) registry[script_obj[_src]] = {
items: [],
finished: false
};
registry_items = registry[script_obj[_src]].items;
if (chain_opts[_allowDuplicates] || registry_items[_length] === 0) {
registry_item = registry_items[registry_items[_length]] = {
ready: false,
finished: false,
ready_listeners: [ ready_cb ],
finished_listeners: [ finished_cb ]
};
request_script(chain_opts, script_obj, registry_item, preload_this_script ? function() {
registry_item[_ready] = true;
for (var i = 0; i < registry_item[_ready_listeners][_length]; i++) registry_item[_ready_listeners][i]();
registry_item[_ready_listeners] = [];
} : function() {
script_executed(registry_item);
}, preload_this_script);
} else {
registry_item = registry_items[0];
if (registry_item[_finished]) finished_cb(); else registry_item[_finished_listeners][_push](finished_cb);
}
}
function create_chain() {
var chainedAPI, chain_opts = merge_objs(global_defaults, {}), chain = [], exec_cursor = 0, scripts_currently_loading = false, group;
function chain_script_ready(script_obj, exec_trigger) {
script_obj[_ready] = true;
script_obj[_exec_trigger] = exec_trigger;
advance_exec_cursor();
}
function chain_script_executed(script_obj, chain_group) {
script_obj[_ready] = script_obj[_finished] = true;
script_obj[_exec_trigger] = null;
for (var i = 0; i < chain_group[_scripts][_length]; i++) if (!chain_group[_scripts][i][_finished]) return;
chain_group[_finished] = true;
advance_exec_cursor();
}
function advance_exec_cursor() {
while (exec_cursor < chain[_length]) {
if (is_func(chain[exec_cursor])) {
try {
chain[exec_cursor++]();
} catch (err) {}
continue;
} else if (!chain[exec_cursor][_finished]) {
if (check_chain_group_scripts_ready(chain[exec_cursor])) continue;
break;
}
exec_cursor++;
}
if (exec_cursor == chain[_length]) {
scripts_currently_loading = false;
group = false;
}
}
function init_script_chain_group() {
if (!group || !group[_scripts]) chain[_push](group = {
scripts: [],
finished: true
});
}
chainedAPI = {
test: function(selector) {
if (!is_array(selector)) selector = [ selector ];
for (var j = 0; j < selector[_length]; j++) {
if (global[_document][_querySelector](selector[j]) === null) return nullAPI;
}
return chainedAPI;
},
js: function() {
(function(args) {
for (var i = 0; i < args[_length]; i++) {
var splice_args, script_obj = args[i], script_list = script_obj;
if (!is_array(script_obj)) script_list = [ script_obj ];
for (var j = 0; j < script_list[_length]; j++) {
init_script_chain_group();
script_obj = script_list[j];
if (is_func(script_obj)) script_obj = script_obj();
if (!script_obj) continue;
if (is_array(script_obj)) {
splice_args = [][_slice][_call](script_obj);
splice_args.unshift(j, 1);
[][_splice].apply(script_list, splice_args);
j--;
continue;
}
if (typeof script_obj == "string") script_obj = {
src: script_obj
};
script_obj = merge_objs(script_obj, {
ready: false,
ready_cb: chain_script_ready,
finished: false,
finished_cb: chain_script_executed
});
group[_finished] = false;
group[_scripts][_push](script_obj);
do_script(chain_opts, script_obj, group, can_use_preloading && scripts_currently_loading);
scripts_currently_loading = true;
if (chain_opts[_alwaysPreserveOrder]) chainedAPI[_wait]();
}
}
})(arguments);
return chainedAPI;
},
wait: function() {
if (arguments[_length] > 0) {
for (var i = 0; i < arguments[_length]; i++) chain[_push](arguments[i]);
group = chain[chain[_length] - 1];
} else group = false;
advance_exec_cursor();
return chainedAPI;
},
end: function() {
return chainedAPI;
}
};
return {
test: chainedAPI[_test],
js: chainedAPI[_js],
wait: chainedAPI[_wait],
end: chainedAPI[_end],
setOptions: function(opts) {
merge_objs(opts, chain_opts);
return chainedAPI;
}
};
}
instanceAPI = {
setDefaults: function(opts) {
merge_objs(opts, global_defaults);
if (global_defaults[_autoPolyfill]) instanceAPI[_js]("https://cdn.polyfill.io/v2/polyfill.min.js");
},
ready: function(fn) {
contentLoaded(global, fn);
},
setOptions: function() {
return create_chain()[_setOptions].apply(null, arguments);
},
test: function() {
return create_chain()[_test].apply(null, arguments);
},
js: function() {
return create_chain()[_js].apply(null, arguments);
},
wait: function() {
return create_chain()[_wait].apply(null, arguments);
},
end: create_sandbox,
noConflict: function() {
global.$O = _$O;
return instanceAPI;
},
sandbox: create_sandbox
};
nullAPI = {
test: function() {
return nullAPI;
},
js: function() {
return nullAPI;
},
wait: function() {
return nullAPI;
},
end: instanceAPI[_end]
};
return instanceAPI;
}
var dataOnly = global[_document][_querySelector]("[data-only]");
if (!!dataOnly) create_sandbox()[_js](dataOnly.getAttribute("data-only"));
(function(el) {
el[_className] = el[_className].replace("no-" + _js, _js);
})(global[_document][_documentElement]);
(function(addEvent, domLoaded, handler) {
if (global[_document][_readyState] === null && global[_document][addEvent]) {
global[_document][_readyState] = "loading";
global[_document][addEvent](domLoaded, handler = function() {
global[_document][_removeEventListener](domLoaded, handler, false);
global[_document][_readyState] = _complete;
}, false);
}
})(_addEventListener, _DOMContentLoaded);
global.$O = create_sandbox();
})(typeof global !== "undefined" ? global : typeof exports !== "undefined" ? exports : this);