UNPKG

elation

Version:

Elation Javascript Component Framework

1,547 lines (1,378 loc) 84.4 kB
/** @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'] = '&amp;'; if (useQuoteStyle != 'ENT_NOQUOTES') { entities['34'] = '&quot;'; } if (useQuoteStyle == 'ENT_QUOTES') { entities['39'] = '&#039;'; } entities['60'] = '&lt;'; entities['62'] = '&gt;'; } else if (useTable == 'HTML_ENTITIES') { // ascii decimals for better compatibility entities['38'] = '&amp;'; if (useQuoteStyle != 'ENT_NOQUOTES') { entities['34'] = '&quot;'; } if (useQuoteStyle == 'ENT_QUOTES') { entities['39'] = '&#039;'; } entities['60'] = '&lt;'; entities['62'] = '&gt;'; entities['160'] = '&nbsp;'; entities['161'] = '&iexcl;'; entities['16