elation
Version:
Elation Javascript Component Framework
1,547 lines (1,378 loc) • 84.4 kB
JavaScript
/** @namespace elation */
/** @namespace elation.utils */
/** @namespace elation.html */
var ENV_IS_NODE = (typeof process === 'object' && typeof require === 'function') ? true : false,
ENV_IS_BROWSER = (typeof window !== 'undefined') ? true : false,
ENV_IS_WORKER = (typeof WorkerGlobalScope !== 'undefined' && self instanceof WorkerGlobalScope);
if (typeof window == 'undefined') var window = {};
// compatibility for nodejs/worker threads
"use strict";
var elation = window.elation = new function(selector, parent, first) {
if (typeof selector == 'string' && typeof elation.find == 'function')
elation.find(selector, parent, first);
this.extend = function(name, func, clobber, inheritfrom) {
var ptr = this,
xptr = (typeof exports != 'undefined' ? exports : {}),
parts = name.split("."),
i;
for (i = 0; i < parts.length-1; i++) {
if (typeof ptr[parts[i]] == 'undefined')
ptr[parts[i]] = xptr[parts[i]] = {};
ptr = xptr = ptr[parts[i]];
}
if (typeof ptr[parts[i]] == 'undefined' || ptr[parts[i]] === null || clobber == true) {
ptr[parts[i]] = xptr[parts[i]] = func;
} else {
console.log("elation (warning): tried to clobber existing component '" + name + "'");
}
if (typeof inheritfrom == 'function') {
ptr.prototype = xptr.prototype = new inheritfrom;
ptr.prototype.constructor = xptr.prototype.constructor = ptr;
}
if (typeof exports != 'undefined') exports.extend = this.extend;
}
}
elation.extend('implement', function(obj, iface, ifaceargs) {
if (typeof iface == 'function') {
var foo = new iface(ifaceargs);
for (var k in foo) {
obj[k] = foo[k];
}
}
});
elation.extend('define', function(name, definition, extendclass) {
var constructor = definition._construct;
if (!constructor) {
constructor = (extendclass ? extendclass.prototype.constructor : false);
}
// FIXME - need to figure out a non-horrible way of overriding the class name that's shown in console.log
var func = false;
var funcstr = "elation.utils.arrayset(elation, '" + name + "', false); elation." + name + " = function (args) { if (constructor) return constructor.apply(this, arguments); }; func = elation." + name + ";";
eval(funcstr);
var objdef = func;
if (extendclass) {
if (!constructor) {
objdef = extendclass.prototype.constructor;
}
objdef.prototype = Object.create(extendclass.prototype);
objdef.prototype.constructor = objdef;
}
var keys = Object.keys(definition);
keys.forEach(function(key) { objdef.prototype[key] = definition[key]; });
return objdef;
});
elation.extend('env', {
isNode: (typeof process === 'object' && typeof require === 'function') ? true : false,
isBrowser: (typeof window !== 'undefined' && typeof Window == 'function' && window instanceof Window) ? true : false,
isWorker: (typeof WorkerGlobalScope !== 'undefined' && self instanceof WorkerGlobalScope)
});
/**
* Sets value in a multilevel object element
*
* @function elation.utils.arrayset
* @param {object} obj
* @param {string} element
*/
elation.extend("utils.arrayset", function(obj, element, value) {
var ptr = obj;
var x = element.split(".");
for (var i = 0; i < x.length - 1; i++) {
if (ptr==null || (typeof ptr[x[i]] != 'array' && typeof ptr[x[i]] != 'object' && i != x.length-1)) {
ptr[x[i]] = {};
}
ptr = ptr[x[i]];
}
if (typeof ptr == "object") {
ptr[x[x.length-1]] = value;
}
});
/**
* Retrieves specified dot-separated value from a multilevel object element
*
* @function elation.utils.arrayget
* @param {object} obj
* @param {string} name
* @param {object|number|string} [defval] default value if none found
*/
elation.extend("utils.arrayget", function(obj, name, defval) {
var ptr = obj;
var x = name.split(".");
for (var i = 0; i < x.length; i++) {
if (ptr==null || (!elation.utils.isArray(ptr[x[i]]) && !elation.utils.isObject(ptr[x[i]]) && i != x.length-1)) {
ptr = null;
break;
}
ptr = ptr[x[i]];
}
if (typeof ptr == "undefined" || ptr === null) {
return (typeof defval == "undefined" ? null : defval);
}
return ptr;
});
elation.extend('config', {
data: {},
set: function(name, value) {
return elation.utils.arrayset(this.data, name, value);
},
get: function(name, defaultvalue) {
return elation.utils.arrayget(this.data, name, defaultvalue);
},
merge: function(config) {
elation.utils.merge(config, this.data);
}
});
//Returns true if it is a DOM node
elation.extend("utils.isnode", function(obj) {
return (
typeof Node === "object" ? obj instanceof Node :
typeof obj === "object" && typeof obj.nodeType === "number" && typeof obj.nodeName==="string"
);
});
//Returns true if it is a DOM element
elation.extend("utils.iselement", function(obj) {
return (
typeof HTMLElement === "object" ? obj instanceof HTMLElement : //DOM2
typeof obj === "object" && obj.nodeType === 1 && typeof obj.nodeName==="string"
);
});
elation.extend("utils.isTrue", function(obj) {
if (obj == true || obj == 'true')
return true;
return false;
});
elation.extend("utils.isNull", function(obj) {
if (obj == null || typeof obj == 'undefined')
return true;
return false;
});
elation.extend("utils.isEmpty", function(obj) {
if (obj !== null &&
obj !== "" &&
obj !== 0 &&
typeof obj !== "undefined" &&
obj !== false)
return false;
return true;
});
elation.extend("utils.isObject", function(obj) {
return (obj instanceof Object);
});
elation.extend("utils.isArray", function(obj) {
/*
var objclass = Object.prototype.toString.call(obj),
allow = {
'[object Array]': true,
'[object NodeList]': true,
'[object HTMLCollection]': true
};
if (elation.browser && elation.browser.type == 'msie' && objclass == '[object Object]') {
return !elation.utils.isNull(elation.utils.arrayget(obj, 'length'));
} else {
return allow[objclass] || false;
}
*/
return Array.isArray(obj) || (typeof HTMLCollection != 'undefined' && obj instanceof HTMLCollection);
});
elation.extend("utils.isString", function(obj) {
return (typeof obj == "string");
});
elation.define("class", {
_construct: function(args) {
if (args) {
var keys = Object.keys(args);
keys.forEach(elation.bind(this, function(k) { if (typeof args[k] != 'undefined') this[k] = args[k]; }));
}
},
toJSON: function() {
var keys = Object.keys(this).filter(function(n) { return n[0] != '_'; });
var obj = {};
keys.map(elation.bind(this, function(k, v) { obj[k] = this[k]; }));
return obj;
}
});
elation.extend("component", new function() {
this.init = function(root) {
// if (root == undefined) {
// root = document;
// }
if (!elation.env.isBrowser) return;
// Find all elements which have a data-elation-component attribute
var elements = elation.find('[data-elation-component]');
for (var i = 0; i < elements.length; i++) {
var element = elements[i];
var componentid = this.parseid(element);
if (componentid.type) {
var componentinitialized = element.dataset['elationInitialized'] || false;
if (!componentinitialized) {
var componentargs = {}, events = {};
var componentdata = this.parseargs(element);
// Instantiate the new component with all parsed arguments
elation.component.create(componentid.name, componentid.type, element, componentdata.args, componentdata.events);
}
}
}
}
this.add = function(type, classdef, extendclass) {
// At the top level, a component is just a function which checks to see if
// an instance with the given name exists already. If it doesn't we create
// it, and then we return a reference to the specified instance.
var component = function(name, container, args, events) {
/* handling for any default values if args are not specified */
var mergeDefaults = function(args, defaults) {
var args = args || {};
if (typeof defaults == 'object') {
for (var key in defaults) {
if (elation.utils.isNull(args[key])) {
args[key] = defaults[key];
}
}
}
return args;
};
var realname = name;
if (elation.utils.isObject(name)) {
// Simple syntax just takes an object with all arguments
args = name;
realname = elation.utils.any(args.id, args.name, null);
container = (!elation.utils.isNull(args.container) ? args.container : null);
events = (!elation.utils.isNull(args.events) ? args.events : null);
}
// If no args were passed in, we're probably being used as the base for another
// component's prototype, so there's no need to go through full init
if (elation.utils.isNull(realname) && !container && !args) {
var obj = new component.base(type);
// apply default args
obj.args = mergeDefaults(obj.args, elation.utils.clone(obj.defaults));
return obj;
}
// If no name was passed, use the current object count as a name instead ("anonymous" components)
if (elation.utils.isNull(realname) || realname === "") {
realname = component.objcount;
}
if (!component.obj[realname] && !elation.utils.isEmpty(args)) {
component.obj[realname] = obj = new component.base(type);
component.objcount++;
//}
// TODO - I think combining this logic would let us use components without needing HTML elements for the container
//if (component.obj[realname] && container !== undefined) {
component.obj[realname].componentinit(type, realname, container, args, events);
/*
if (component.extendclass) {
component.obj[realname].initSuperClass(component.extendclass);
}
*/
// fix handling for append component infinite recursion issue
if (args.append instanceof elation.component.base)
args.append = args.append.container;
if (args.before instanceof elation.component.base)
args.before = args.before.container;
// apply default args
try {
if (typeof obj.defaults == 'object')
args = mergeDefaults(args, elation.utils.clone(obj.defaults));
var parentclass = component.extendclass;
// recursively apply inherited defaults
while (parentclass) {
if (typeof parentclass.defaults == 'object')
elation.utils.merge(mergeDefaults(args, elation.utils.clone(parentclass.defaults)),args);
parentclass = parentclass.extendclass;
}
} catch (e) {
console.log('-!- Error merging component args', e.msg);
}
if (typeof obj.init == 'function') {
obj.init(realname, container, args, events);
}
}
return component.obj[realname];
};
component.objcount = 0;
component.obj = {}; // this is where we store all the instances of this type of component
(function() {
let utils = window.elation.utils;
var elation = {};
window.elation.utils.arrayset(elation, type, null);
var namehack = "elation." + type + " = function () { }; component.base = elation." + type;
if (type.indexOf('-') != -1) {
namehack = "utils.arrayset(elation, '" + type + "', function() { }); component.base = utils.arrayget(elation, '" + type + "');";
}
eval(namehack); // FIXME - weirdness to force usable names while console.logging components
})();
component.base.prototype = new this.base(type);
if (extendclass) {
component.extendclassdef = extendclass;
component.extendclass = new extendclass();
if (!component.extendclass._inherited)
component.extendclass._inherited = [];
component.extendclass._inherited.push(component.extendclass);
component.base.prototype.extend(component.extendclass);
}
if (classdef) {
component.base.prototype.extend((typeof classdef == 'function' ? new classdef() : classdef));
component.classdef = classdef;
}
elation.extend(type, component); // inject the newly-created component wrapper into the main elation object
}
this.create = function(id, type, container, args, events) {
var componentclass = elation.utils.arrayget(elation, type);
if (typeof componentclass == 'function') {
var instance = componentclass.call(componentclass, id, container, args, events);
}
//console.error("elation: tried to instantiate unknown component type '" + type + "', id '" + id + "'");
}
this.get = function(id, type, container, args, events) {
var componentclass = elation.utils.arrayget(elation, type);
if (componentclass && typeof componentclass == 'function') {
return componentclass.call(componentclass, id, container, args, events);
} else {
console.log('no way buddy');
this.add(type);
return this.create(id, type, container, args, events);
}
}
this.load = function(componentname, callback) {
// Loads the dependency script/css for the specified component, and execute callback if supplied
var componentbase = componentname.replace('.', '/');
var root = elation.file.root()
var batch = new elation.file.batch();
batch.add(root + '/scripts/' + componentbase + '.js', 'javascript');
//batch.add(root + '/css/' + componentbase + '.css', 'css');
// FIXME - batch loading seems to not load css files reliably
elation.file.get('css', root + '/css/' + componentbase + '.css');
if (callback) batch.callback(callback);
}
this.info = function(type) {
var componentclass = elation.utils.arrayget(elation, type);
if (componentclass && typeof componentclass == 'function') {
return {objcount: componentclass.objcount};
}
}
this.base = function(component) {
this.componentinit = function(name, id, container, args, events) {
this.name = name;
this.id = id;
this.componentname = name; // FIXME - redundant with this.name above, but this.name is very likely to be clobbered by the user
this.args = args || {};
if (container) {
this.container = container;
} else if (this.args.containertag) {
this.container = elation.html.create(this.args.containertag);
} else if (this.defaultcontainer) {
this.container = elation.html.create(this.defaultcontainer);
} else {
this.container = null;
}
this.events = events || {};
for (var k in this.events) {
if (typeof this.events[k] == 'string') {
(function(self, type, blub) {
self[type] = function(ev) { eval(blub); }
elation.events.add(self, type, self);
})(this, k, this.events[k]);
} else {
elation.events.add(this, k, this.events[k]);
}
}
if (this.container) {
this.container.dataset['elationComponent'] = name;
this.container.dataset['elationName'] = id;
this.container.dataset['elationInitialized'] = 1;
if (this.defaultcontainer && this.defaultcontainer.classname && !elation.html.hasclass(this.container, this.defaultcontainer.classname)) {
elation.html.addclass(this.container, this.defaultcontainer.classname);
}
if (this.args.setContent) {
elation.html.setContent(this.container, this.args.setContent);
}
if (this.args.append) {
elation.html.attach(this.args.append, this.container, this.args.before || false);
}
}
elation.events.fire({type: "init", fn: this, data: this, element: this.container});
}
this.initSuperClass = function(classdef) {
var _super = {};
if (classdef) {
for (var k in classdef) {
if (typeof classdef[k] == 'function') {
_super[k] = elation.bind(this, classdef[k]);
}
}
}
return _super;
}
this.extend = function(from) {
for (var k in from) {
if (k != 'constructor' && k != 'prototype') {
this[k] = from[k];
}
}
}
this.set = function(sets, value) {
// special set function to send update notifications when the object (or eventually, individual values) change
if (typeof sets == 'string' && value) {
var k = sets;
sets = {};
sets[k] = value;
}
var changes = 0;
for (var k in sets) {
if (elation.utils.arrayget(this, k) != sets[k]) {
elation.utils.arrayset(this, k, sets[k]);
changes++;
}
}
if (changes > 0) {
// TODO - if we supported bindings, we could send updates directly to specific observers when specific attributes are updated
elation.events.fire({type:'update', origin: this, data: this, element: this.container});
return true;
}
return false;
}
this.setevents = function(events) {
for (var k in events) {
this.events[k] = events[k];
}
}
// execute superclass init function
this.super = function(classname) {
console.log('super',this.name, this);
var self = self || this,
componentclass = elation.utils.arrayget(elation, classname || this.name);
if (componentclass) {
var extendclass = elation.utils.arrayget(componentclass, 'extendclass.init');
if (extendclass)
extendclass.call(self);
}
//delete self;
}
this.fetch = function(type, callback, force) {
var ret;
//var urlbase = "/~bai/"; // FIXME - stupid stupid stupid! move this to the right place asap!
var urlbase = '/';
if (force || !this.content) {
(function(self, callback) {
console.log(urlbase + self.name.replace(".","/") + "." + type);
var args = self.args;
args.events = self.events;
console.log('stupid dumb args is', args);
ajaxlib.Queue({
method: "GET",
url: urlbase + self.name.replace(".","/") + "." + type,
args: elation.utils.encodeURLParams(args),
callback: function(data) { self.content = data; if (typeof callback == 'function') { callback(data); } }
});
})(this, callback);
ret = '<img src="/images/misc/plugin-icon-180x120.png"/>';
} else {
ret = this.content;
if (typeof callback == 'function')
callback(this.content);
}
return ret;
}
this.reparent = function(newparent) {
if (this.container && this.container.parentNode && this.container.parentNode !== newparent) {
this.container.parentNode.removeChild(this.container);
}
if (newparent) {
newparent.appendChild(this.container);
elation.component.init();
}
}
this.handleEvent = function(ev) {
if (typeof this[ev.type] == 'function') {
this[ev.type](ev);
}
}
this.destroy = function() {
var componentclass = elation.utils.arrayget(elation, this.componentname);
if (componentclass && componentclass.obj[this.id]) {
delete componentclass.obj[this.id];
}
// Remove any events which reference this component
var events = elation.events.getEventsByTargetOrOrigin(this);
for (var i = 0; i < events.length; i++) {
var ev = events[i];
elation.events.remove(ev.target, ev.type, ev.origin);
}
}
/*
this.addEventListener = function(type, listener, useCapture) {
elation.events.add(this, type, listener);
}
this.dispatchEvent = function(event) {
elation.events.fire(event);
}
*/
}
this.parseid = function(element) {
// Parse out the data-elation-component and data-elation-name attributes, if set. Fall back on HTML id if no name specified
var componentid = {
type: element.dataset['elationComponent'],
name: element.dataset['elationName'] || element.id
}
return componentid;
}
this.parseargs = function(element) {
if (element.children) {
// Pull out all <data> blocks
var dataresult = elation.find("data", element);
var componentargs = {}, events = {};
for (var j = 0; j < dataresult.length; j++) {
var dataelement = dataresult[j];
if (elation.html.hasclass(dataelement, 'elation-args')) {
// JSON-encoded args inside of <data class="elation-args">...</data>
var argtext = dataelement.textContent || dataelement.innerText;
var argname = (dataelement.attributes['name'] ? dataelement.attributes['name'].value : false);
try {
var content = dataelement.innerHTML.trim();
// if elation-name parameter is specified, merge this data into the appropriate place
var mergeto = componentargs;
if (argname) {
var tmpmergeto = elation.utils.arrayget(componentargs, argname);
if (tmpmergeto === null) { // requested key is new, create it and get a reference to the new object
elation.utils.arrayset(componentargs, argname, {});
mergeto = elation.utils.arrayget(componentargs, argname);
} else {
mergeto = tmpmergeto; // key already exists, store reference
}
}
if (content.length > 0) {
var newcomponentargs = '';
try {
newcomponentargs = JSON.parse(content);
} catch (e) {
newcomponentargs = content;
// Simple string, so set the value directly rather than using merge-by-reference
elation.utils.arrayset(componentargs, argname, content);
}
//dataelement.parentNode.removeChild(dataelement);
if (componentargs != null) { // empty JSON could cause errors later, so reset null to an empty hash
elation.utils.merge(newcomponentargs, mergeto);
}
}
} catch(e) {
// Probably JSON syntax error
console.log("Could not parse args: " + argtext + ": " + e.stack);
}
} else if (elation.html.hasclass(dataelement, "elation-events")) {
try {
var content = dataelement.innerHTML.trim();
if (content.length > 0) {
events = JSON.parse(content);
element.removeChild(dataelement);
if (events == null) { // empty JSON could cause errors later, so reset null to an empty hash
events = {};
}
}
} catch(e) {
// Probably JSON syntax error
console.log("Could not parse " + eventsattr + ": " + element.children[j].innerHTML);
}
}
}
}
// Then, loop through the attributes and parse out any individual arguments which can be specified as attributes
var argprefix = 'elationArgs.';
var eventprefix = 'elationEvents.';
for (var k in element.dataset) {
if (k.substring(0, argprefix.length) == argprefix) {
elation.utils.arrayset(componentargs, k.substring(argprefix.length), element.dataset[k]);
//componentargs[k.substring(argprefix.length)] = element.dataset[k];
} else if (k.substring(0, eventprefix.length) == eventprefix) {
events[k.substring(eventprefix.length)] = element.dataset[k];
}
}
return {args: componentargs, events: events};
}
this.fetch = function(type, name) {
if (type instanceof elation.component.base) {
// If we were passed an already-existing component, just return it
return type;
}
var id;
if (!elation.utils.isNull(type) && elation.utils.iselement(type)) {
// If an HTML element was passed in, find the associated component id
id = this.parseid(type);
} else if (elation.utils.isArray(type)) {
id = { type: type[0], name: type[1] };
} else {
id = { type: type, name: name };
}
if (id.type && id.name) {
var componentclass = elation.utils.arrayget(elation, id.type);
if (componentclass && typeof componentclass == 'function') {
return componentclass(id.name);
}
}
}
});
elation.extend('onloads',new function() {
this.done = false;
this.onloads = [];
this.add = function(expr) {
this.onloads.push(expr);
// if DOM already loaded, execute immediately
if (this.done) this.execute();
}
this.init = function() {
/* for Safari */
//if (/WebKit/i.test(navigator.userAgent)) { // sniff
this.timer = setInterval(function() {
if (/loaded|complete/.test(document.readyState)) {
elation.onloads.execute(); // call the onload handler
}
}, 10);
// return;
//}
/* for Mozilla/Opera9 */
if (document.addEventListener) {
document.addEventListener("DOMContentLoaded", elation.onloads.execute, false);
return;
}
/* for Internet Explorer */
/*@cc_on @*/
/*@if (@_win32)
document.write("<scr"+"ipt id=\"__ie_onload\" defer src=\"/blank.fhtml\"><\/scr"+"ipt>");
var script = document.getElementById("__ie_onload");
script.onreadystatechange = function() {
if (this.readyState == "complete") {
elation.onloads.execute(); // call the onload handler
}
};
return;
/*@end @*/
window.onload = elation.onloads.execute;
}
this.execute = function() {
// quit if this function has already been called
// ^--- no dont do that or else we cant execute after dom load
//if (elation.onloads.done) return;
// flag this function so we don't do the same thing twice
elation.onloads.done = true;
// kill the timer
if (elation.onloads.timer) clearInterval(elation.onloads.timer);
var script = '';
var expr;
while (expr = elation.onloads.onloads.shift()) {
if (typeof expr == 'function') {
expr(); // FIXME - this causes all function references to be executed before all strings
} else {
script += expr + (expr.charAt(expr.length - 1) != ';' ? ';' : '');
}
}
eval(script);
}
});
//elation.onloads.init();
/**
* Bind a function to a specified context, so "this" maps correctly within callbacks
*
* @function elation.bind
* @param {object} ctx Context to bind to
* @param {function} fn Function to bind
*/
elation.extend("bind", function(ctx, fn) {
if (typeof fn == 'function') {
var fnargs = Array.prototype.splice.call(arguments, 2);
fnargs.unshift(ctx);
return (typeof fn.bind == 'function' ?
Function.prototype.bind.apply(fn, fnargs) : // modern browsers have fn.bind() built-in
function() { fn.apply(ctx, arguments); } // older browsers just need a closure to carry the context through
);
} else if (typeof ctx == 'function') {
return ctx;
}
});
elation.extend("html.dimensions", function(element, ignore_size) {
if (!element)
return;
if (typeof element != 'object' || element === window) {
var w = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth,
h = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight;
return {
0 : w,
1 : h,
x : 0,
y : 0,
w : w,
h : h,
s : elation.html.getscroll()
};
}
if ('getBoundingClientRect' in element) {
var rect = element.getBoundingClientRect(),
top = rect.top,
left = rect.left,
width = rect.width,
height = rect.height,
r = Math.round,
x = r(left),
y = r(top),
w = r(width),
h = r(height);
} else {
var w = ignore_size ? 0 : element.offsetWidth,
h = ignore_size ? 0 : element.offsetHeight,
x = element.offsetLeft,
y = element.offsetTop;
}
var scrollleft = element.scrollLeft || 0,
scrolltop = element.scrollTop || 0,
id = element.id || '';
/*
try {
while (element = element.offsetParent) {
x += element.offsetLeft - element.scrollLeft;
y += element.offsetTop - element.scrollTop;
}
} catch(e) {
console.log('html.dimensions: '+e.message);
}
*/
if (document.body.scrollTop == window.scrollY)
y += window.scrollY;
return {
0: x,
1: y,
'x': x,
'y': y,
'w': w,
'h': h,
's': [scrollleft, scrolltop],
'scrollTop': scrolltop,
'scrollLeft': scrollleft,
'width': width || w,
'height': height || h,
'top': top || y,
'left': left || x
};
});
elation.extend("html.size", function(obj) {
return [obj.offsetWidth, obj.offsetHeight];
});
elation.extend("html.position", function(obj) {
var curleft = 0, curtop = 0;
if (obj.offsetParent) {
curleft = obj.offsetLeft;
curtop = obj.offsetTop;
while (obj = obj.offsetParent) {
curleft += obj.offsetLeft;
curtop += obj.offsetTop;
}
}
return [curleft,curtop];
});
// html.preloader will fire events and/or callback when all elements have onload'd
elation.extend('html.preloader', function(elements, args) {
this.elements = elements;
this.args = args || { timeout: 2000, callback: false };
this.index = 0;
this.init = function() {
for (var i=0; i<this.elements.length; i++) {
if (this.elements[i].complete)
this.index++;
else
elation.events.add(this.elements[i], 'load', this);
}
if (!this.validate())
(function(self) {
self.timer = setTimeout(function() {
if (!self.items) {
console.log('2s timeout reached, forcing load.');
self.done();
}
}, self.args.timeout || 2000);
})(this);
}
this.load = function(event, target) {
elation.events.fire('preloader_load', this);
this.validate(true);
}
this.validate = function(increment) {
if (increment) this.index++;
//console.log('validate', increment, this.index, this.elements.length);
if (this.index == this.elements.length) {
this.done();
return true;
}
return false;
}
this.done = function() {
(function(self) {
setTimeout(function() { elation.events.fire('preloader_done', self); }, 1);
})(this);
if (typeof this.args.callback == 'function')
this.args.callback();
clearTimeout(this.timer);
}
this.handleEvent = function(event) {
var event = event || window.event,
target = elation.events.getTarget(event),
type = event.type == 'DOMMouseScroll' ? 'mousewheel' : event.type;
if (typeof this[type] == 'function')
return this[type](event, target);
}
this.init();
});
// methods for css classname information and manipulation
elation.extend("html.hasclass", function(element, className) {
if (element && element.className) {
// ATTN: do hasclass on individual classes, not multiple classes w/ spaces!
var className = className.split(' ');
if ("classList" in element) {
return element.classList.contains(className[0]);
} else {
var re = new RegExp("(^| )" + className[0] + "( |$)", "g");
return element.className.match(re);
}
}
return false;
});
elation.extend("html.class", function(method, elements, className) {
if (!elation.utils.isArray(elements)) {
elements = [ elements ];
}
for (var i=0,element,classes; i<elements.length; i++) {
element = elation.utils.getContainerElement(elements[i]);
classes = className.split(' ');
for (var n=0; n<classes.length; n++) {
element.classList[method](classes[n]);
}
}
});
elation.extend("html.addclass", function(elements, className) {
if (!elements || elements.length == 0)
return;
if ("classList" in elements || (typeof elements.length == 'number' && "classList" in elements[0])) {
elation.html.class('add', elements, className);
} else {
if (elements && !elation.html.hasclass(elements, className)) {
elements.className += (elements.className ? " " : "") + className;
}
}
});
elation.extend("html.removeclass", function(elements, className) {
if (!elements || elements.length == 0)
return;
if ("classList" in elements || (typeof elements.length == 'number' && "classList" in elements[0])) {
elation.html.class('remove', elements, className);
} else {
var re = new RegExp("(^| )" + className + "( |$)", "g");
if (element && element.className && element.className.match(re)) {
element.className = element.className.replace(re, " ");
}
}
});
elation.extend("html.toggleclass", function(elements, className) {
if ("classList" in elements || (typeof elements.length == 'number' && "classList" in elements[0])) {
elation.html.class('toggle', elements, className);
} else {
if (this.hasclass(element, className))
this.removeclass(element, className)
else
this.addclass(element, className);
}
});
// for great justice
elation.extend("html.hasClass", elation.html.hasclass);
elation.extend("html.addClass", elation.html.addclass);
elation.extend("html.removeClass", elation.html.removeclass);
elation.extend("html.toggleClass", elation.html.toggleclass);
/**
* Create a new html element
*
* @function elation.html.create
* @param {object} parms
* @param {string} parms.tag
* @param {string} parms.classname
* @param {string} parms.id
* @param {string} parms.content
* @param {HTMLElement|elation.ui.component} parms.append
* @param {boolean} parms.before
* @param {object} parms.style
* @param {object} parms.additional
*
* @example
* elation.html.create({
* tag:'div',
* classname:'example',
* style: { width:'30px', height:'20px' },
* attributes: { innerHTML: 'Test!' },
* append: elementObj
* });
*/
elation.extend('html.create', function(parms, classname, style, attr, append, before) {
if (typeof document == 'undefined') {
return;
}
if (typeof parms == 'object') {
var tag = parms.tag || 'div',
classname = parms.classname,
id = parms.id,
attr = parms.attributes || parms.attr,
style = parms.style || parms.css,
content = parms.content,
append = parms.append,
before = parms.before;
}
var element = document.createElement(tag || parms || 'div');
if (id)
element.id = id;
if (classname)
element.className = classname;
if (style)
elation.html.css(element, style);
if (content)
elation.html.setContent(element, content);
if (typeof attr == 'object') {
for (var property in attr) {
element[property] = attr[property];
}
}
if (append)
elation.html.attach(append, element, before);
return element;
});
// will do appendChild or insertBefore where appropriate
// will sanitize for elation components to return their containers
elation.extend("html.attach", function(container, element, before) {
if (!container || !element || typeof container == 'string')
return;
var container = elation.utils.getContainerElement(container),
element = elation.utils.getContainerElement(element),
before = elation.utils.getContainerElement(before);
if (before) {
container.insertBefore(element, before);
} else {
container.appendChild(element);
}
});
// determines how best to inject content into container
// automatically used in components with this.args.content
elation.extend("html.setContent", function(element, content, append) {
if (!element || (!content && typeof content != 'string'))
return;
var element = elation.utils.getContainerElement(element);
if (elation.utils.isString(content)) {
if (!append) element.innerHTML = content;
else element.innerHTML += content;
} else if (content.container instanceof HTMLElement) {
if (!append) element.innerHTML = '';
element.appendChild(content.container);
} else if (content instanceof HTMLElement) {
if (!append) element.innerHTML = '';
element.appendChild(content);
}
});
elation.extend('html.getscroll', function(shpadoinkle) {
if (elation.iphone && elation.iphone.scrollcontent)
var pos = [0,0];//elation.iphone.scrollcontent.getPosition();
else if (typeof pageYOffset != 'undefined')
var pos = [
pageXOffset,
pageYOffset
];
else
var QuirksObj = document.body,
DoctypeObj = document.documentElement,
element = (DoctypeObj.clientHeight)
? DoctypeObj
: QuirksObj,
pos = [
element.scrollLeft,
element.scrollTop
];
switch (shpadoinkle) {
case 0:
return pos[0];
case 1:
return pos[1];
default:
return [
pos[0],
pos[1]
];
}
});
elation.extend("html.get_scroll", elation.html.getscroll);
elation.extend("html.getScroll", elation.html.getscroll);
elation.extend("html.styleget", function(el, styles) {
if (typeof styles == 'string') {
styles = [styles];
}
var ret = {};
var computed = window.getComputedStyle(el, null);
for (var k = 0; k < styles.length; k++) {
for (var i = computed.length; i--;) {
var property = elation.utils.camelize(computed[i]);
if (property.indexOf(styles[k]) > -1) {
ret[property] = computed[property];
}
}
}
return ret;
});
elation.extend("html.css", function(el, styles) {
for (var k in styles) {
el.style[k] = styles[k];
}
});
// Cross-browser transform wrapper
elation.extend("html.transform", function(el, transform, origin, transition) {
if (transition) { // Set transition first, if supplied
el.style.webkitTransition = el.style.MozTransition = el.style.msTransition = el.style.transition = transition;
}
if (transform) {
el.style.webkitTransform = el.style.MozTransform = el.style.msTransform = el.style.transform = transform;
}
if (origin) { // Optionally, set transform origin
el.style.webkitTransformOrigin = el.style.MozTransformOrigin = el.style.msTransformOrigin = el.style.transformOrigin = origin;
}
return {
transform: el.style.webkitTransform || el.style.MozTransform || el.style.msTransform || el.style.transform,
transformorigin: el.style.webkitTransformOrigin || el.style.MozTransformOrigin || el.style.msTransformOrigin || el.style.transformOrigin,
transition: el.style.webkitTransition || el.style.MozTransition || el.style.msTransition || el.style.transition
};
});
elation.extend("html.stylecopy", function(dst, src, styles) {
if (typeof styles == 'string') {
styles = [styles];
}
var computed = window.getComputedStyle(src, null);
for (var k = 0; k < styles.length; k++) {
for (var i = computed.length; i--;) {
var property = elation.utils.camelize(computed[i]);
if (property.indexOf(styles[k]) > -1) {
dst.style[property] = computed[property];
}
}
}
});
elation.extend("utils.camelize", function(text) {
return text.replace(/[-\.]+(.)?/g, function (match, chr) {
return chr ? chr.toUpperCase() : '';
});
});
elation.extend("utils.isElement", function(obj) {
try {
//Using W3 DOM2 (works for FF, Opera and Chrome)
return obj instanceof HTMLElement;
}
catch(e){
//Browsers not supporting W3 DOM2 don't have HTMLElement and
//an exception is thrown and we end up here. Testing some
//properties that all elements have. (works on IE7)
return (typeof obj==="object") &&
(obj.nodeType===1) && (typeof obj.style === "object") &&
(typeof obj.ownerDocument ==="object");
}
});
elation.extend("utils.encodeURLParams", function(obj) {
var value,ret = '';
if (typeof obj == "string") {
ret = obj;
} else {
var flattened = elation.utils.flattenURLParams(obj);
for (var key in flattened) {
if (typeof flattened[key] != 'undefined') {
ret += (ret != '' ? '&' : '') + key + (flattened[key] !== null ? '=' + encodeURIComponent(flattened[key]) : '');
}
}
}
return ret;
});
elation.extend("utils.flattenURLParams", function(obj, prefix) {
var ret = {};
for (var k in obj) {
var key = (prefix ? prefix + '[' + k + ']' : k);
if (obj[k] !== null && typeof obj[k] == 'object') {
var flattened = elation.utils.flattenURLParams(obj[k], key);
elation.utils.merge(flattened, ret);
} else {
ret[key] = obj[k];
}
}
return ret;
});
elation.extend("utils.parseURL", function(str) {
var ret = {uri: str, args: {}};
var hashparts = str.split('#');
var parts = hashparts[0].split("?");
if (parts[0]) {
var fileparts = parts[0].split(/:\/\//, 2);
if (fileparts[1]) {
ret.scheme = fileparts[0];
if (fileparts[1][0] == '/') {
ret.host = document.location.host;
ret.path = fileparts[1];
} else {
var pathparts = fileparts[1].split("/");
ret.host = pathparts.shift();
ret.path = '/' + pathparts.join("/");
}
} else {
ret.scheme = document.location.protocol.slice(0, -1);
ret.host = document.location.host;
ret.path = fileparts[0];
}
}
if (parts[1]) {
var args = parts[1].split("&");
ret.args = {};
for (var i = 0; i < args.length; i++) {
var argparts = args[i].split("=", 2);
ret.args[argparts[0]] = decodeURIComponent(argparts[1]);
}
}
if (hashparts[1]) {
var hashargs = hashparts[1].split("&");
ret.hashargs = {};
for (var i = 0; i < hashargs.length; i++) {
var hashargparts = hashargs[i].split("=", 2);
ret.hashargs[hashargparts[0]] = decodeURIComponent(hashargparts[1]);
}
}
return ret;
});
elation.extend("utils.makeURL", function(obj) {
var argstr = elation.utils.encodeURLParams(obj.args);
return obj.scheme + "://" + obj.host + obj.path + (argstr ? '?' + argstr : '');
});
elation.extend("utils.merge", function(entities, mergeto) {
if (typeof entities == 'object' && !entities.tagName && !(typeof HTMLElement != 'undefined' && mergeto instanceof HTMLElement)) {
if (typeof mergeto == 'undefined' || mergeto === null) mergeto = {}; // Initialize to same type as entities
for (var i in entities) {
if (entities[i] !== null) {
if (entities[i] instanceof Array) {
if (mergeto[i] instanceof Array) {
//console.log('concat array: ' + i + ' (' + mergeto[i].length + ' + ' + entities[i].length + ')');
mergeto[i] = mergeto[i].concat(entities[i]);
} else {
//console.log('assign array: ', i, typeof mergeto[i]);
mergeto[i] = entities[i];
}
} else if (entities[i] instanceof Object) {
if (mergeto[i] instanceof Object) {
//console.log('merge object: ', i);
elation.utils.merge(entities[i], mergeto[i]);
} else {
//console.log('assign object: ', i, typeof mergeto[i]);
mergeto[i] = entities[i];
}
} else {
mergeto[i] = entities[i];
}
}
}
}
return mergeto;
});
elation.extend("utils.arraymin", function(array) {
var value=ret=0;
for (var i=total=0; i<array.length; i++) {
value = array[i];
if (ret == 0 || value < ret)
ret = value;
}
return ret;
});
elation.extend("utils.arraymax", function(array) {
var value=ret=0;
for (var i=total=0; i<array.length; i++) {
value = array[i];
if (value > ret) ret = value;
}
return ret;
});
elation.extend("utils.arrayavg", function(array) {
return (arraySum(array) / array.length);
});
elation.extend("utils.arraysum", function(array) {
for (var i=total=0; i<array.length; i++)
total += array[i];
return total;
});
// use when unsure if element is a HTMLElement or Elation Component
elation.extend("utils.getContainerElement", function(element) {
return (element instanceof elation.component.base)
? element.container : (element && element.tagName)
? element : false;
});
// runs through direct children of obj and
// returns the first matching <tag> [className]
elation.extend("utils.getFirstChild", function(obj, tag, className) {
for (var i=0; i<obj.childNodes.length; i++)
if (obj.childNodes[i].nodeName == tag.toUpperCase())
if (className && this.hasclass(obj, className))
return obj.childNodes[i];
else if (!className)
return obj.childNodes[i];
return null;
});
// runs through direct children of obj and
// returns the last matching <tag> [className]
elation.extend("utils.getLastChild", function(obj, tag, className) {
for (var i=obj.childNodes.length-1; i>=0; i--)
if (obj.childNodes[i].nodeName == tag.toUpperCase())
if (className && this.hasclass(obj, className))
return obj.childNodes[i];
else if (!className)
return obj.childNodes[i];
return null;
});
// runs through all children recursively and returns
// all elements matching <tag> [className]
elation.extend("utils.getAll", function(obj, tag, className) {
var ret = [],
all = obj.getElementsByTagName(tag);
for (var i=0; i<all.length; i++)
if (className && this.hasclass(all[i], className))
ret.push(all[i]);
else if (!className)
ret.push(all[i]);
return ret;
});
// runs through the direct children of obj and returns
// all elements matching <tag> [className]
elation.extend("utils.getOnly", function(obj, tag, className) {
if (!obj || !tag)
return;
var ret = [];
for (var i=0; el=obj.childNodes[i]; i++)
if (el.nodeName == tag.toUpperCase()) {
if (className && this.hasclass(el, className))
ret.push(el);
else if (!className)
ret.push(el);
}
return ret;
});
// Navigates up the DOM from a given element looking for match
elation.extend("utils.getParent", function(element, tag, classname, all_occurrences) {
var ret = [];
if (typeof classname != 'string' && elation.utils.isTrue(classname))
all_occurances = true;
while (element && element.nodeName != 'BODY') {
if (element.nodeName == tag.toUpperCase() && (!classname || elation.html.hasclass(element, classname))) {
if (all_occurrences)
ret.push(element);
else
return element;
}
element = element.parentNode;
}
return (ret.length == 0 ? false : ret);
});
elation.extend("utils.isin", function(parent, element) {
if (!parent || !element)
return false;
while (!elation.utils.isNull(element) && element != parent && element != document.body) {
element = element.parentNode;
}
return (parent == element);
});
elation.extend("utils.indexOf", function(array, object) {
if (typeof array == 'string')
array = array.split("");
for (var i=0; i<array.length; i++) {
if (array[i] === object) {
return i;
}
}
return -1;
});
elation.extend("utils.stringify", function(parms, eq, delimeter) {
var value, ret = '', eq = eq || '=', delimeter = delimeter || '&';
for (var key in parms) {
value = parms[key];
ret += key + eq + value + delimeter;
}
return ret.substr(0,ret.length-1);
});
// some deep copy shit i got from stackoverflow
elation.extend("utils.clone", function(obj) {
var copy;
// Handle the 3 simple types, and null or undefined
if (null == obj || "object" != typeof obj)
return obj;
// Handle Date
if (obj instanceof Date) {
copy = new Date();
copy.setTime(obj.getTime());
return copy;
}
// Handle Array
if (obj instanceof Array) {
return obj.slice(0);
}
// Handle Object
if (obj instanceof Object) {
copy = {};
for (var attr in obj) {
//console.log(attr, typeof obj[attr]);
if (obj.hasOwnProperty(attr) && typeof obj[attr] != 'function')
copy[attr] = elation.utils.clone(obj[attr]);
}
return copy;
}
throw new Error("Unable to copy obj! Its type isn't supported.");
});
elation.extend("utils.htmlentities", function(string, quote_style) {
// http://kevin.vanzonneveld.net
var histogram = {}, symbol = '', tmp_str = '', entity = '';
tmp_str = string.toString();
if (false === (histogram = elation.utils.get_html_translation_table('HTML_ENTITIES', quote_style))) {
return false;
}
for (symbol in histogram) {
entity = histogram[symbol];
tmp_str = tmp_str.split(symbol).join(entity);
}
return tmp_str;
});
elation.extend("utils.get_html_translation_table", function(table, quote_style) {
// http://kevin.vanzonneveld.net
var entities = {}, histogram = {}, decimal = 0, symbol = '';
var constMappingTable = {}, constMappingQuoteStyle = {};
var useTable = {}, useQuoteStyle = {};
useTable = (table ? table.toUpperCase() : 'HTML_SPECIALCHARS');
useQuoteStyle = (quote_style ? quote_style.toUpperCase() : 'ENT_COMPAT');
// Translate arguments
constMappingTable[0] = 'HTML_SPECIALCHARS';
constMappingTable[1] = 'HTML_ENTITIES';
constMappingQuoteStyle[0] = 'ENT_NOQUOTES';
constMappingQuoteStyle[2] = 'ENT_COMPAT';
constMappingQuoteStyle[3] = 'ENT_QUOTES';
// Map numbers to strings for compatibilty with PHP constants
if (!isNaN(useTable)) {
useTable = constMappingTable[useTable];
}
if (!isNaN(useQuoteStyle)) {
useQuoteStyle = constMappingQuoteStyle[useQuoteStyle];
}
if (useTable == 'HTML_SPECIALCHARS') {
// ascii decimals for better compatibility
entities['38'] = '&';
if (useQuoteStyle != 'ENT_NOQUOTES') {
entities['34'] = '"';
}
if (useQuoteStyle == 'ENT_QUOTES') {
entities['39'] = ''';
}
entities['60'] = '<';
entities['62'] = '>';
} else if (useTable == 'HTML_ENTITIES') {
// ascii decimals for better compatibility
entities['38'] = '&';
if (useQuoteStyle != 'ENT_NOQUOTES') {
entities['34'] = '"';
}
if (useQuoteStyle == 'ENT_QUOTES') {
entities['39'] = ''';
}
entities['60'] = '<';
entities['62'] = '>';
entities['160'] = ' ';
entities['161'] = '¡';
entities['16