controls
Version:
framework for dynamic html documents and UI solutions
1,248 lines (1,094 loc) • 94.2 kB
JavaScript
// controls.js
// UI framework, code generation tool
// status: proposal, example, valid prototype, under development
// demo: http://aplib.github.io/controls.js/
// issues: https://github.com/aplib/controls.js/issues
// (c) 2013 vadim b.
// License: MIT
(function() { 'use strict';
var controls = {
VERSION: '0.7.04'/*#.#.##*/,
id_generator: 53504,
// assignable default template engine
//template: function(templ) { return new Function('return \'' + templ.replace('\n', '\\\n').replace(/'/g, "\\'") + '\''); },
template: function(templ) {
return new Function('return \'' + templ.replace(/'|\n/g, function(substr) {
return {"'":"\\'", "\n":"\\n\\\n"}[substr];
}) + '\'');
},
subtypes: {} // Registered subtypes
};
var IDENTIFIERS = ',add,_add,attach,attr,_attr,attrs,attributes,class,controls,data,delete,each,element,findFirst,findLast,first,forEach,id,insert,_insert,__type,last,length,name,parameters,parent,refresh,remove,style,template,';
var ENCODE_HTML_MATCH = /&(?!#?\w+;)|<|>|"|'|\//g;
var ENCODE_HTML_PAIRS = { "<": "<", ">": ">", '"': '"', "'": ''', "&": "&", "/": '/' };
var DECODE_HTML_MATCH = /&#(\d{1,8});/g;
/**
* Initialize control object
*
* @param {object} object Control object
* @param {string} __type Base type of the control, in format namespace.control
* @param {object} parameters Parameters hash object
* @param {object} attributes Attributes hash object
* @param {function} outer_template Outer template
* @param {function} inner_template Inner template
* @returns {object} Control object
*/
controls.controlInitialize = function(object, __type, parameters, attributes, outer_template, inner_template) {
if (attributes) {
object.id = attributes.id || (attributes.id = (++controls.id_generator).toString(16)); // set per session uid
object.name = attributes.$name;
// default move $prime to $text
if ('$prime' in attributes) {
var prime = attributes.$prime;
if (prime instanceof DataArray || prime instanceof DataObject)
this.bind(prime);
else
attributes.$text = prime;
delete attributes.$prime;
}
object.attributes = attributes;
} else
object.attributes = {id:(object.id = (++controls.id_generator).toString(16))}; // set per session uid
object.__type = (__type.indexOf('.') >= 0) ? __type : ('controls.' + __type);
object.parameters = parameters || {};
object.controls = []; // Collection of child nodes
if (outer_template) {
outer_template.no_serialize = true;
Object.defineProperty(object, 'outer_template', {
enumerable: true, writable: true,
value: outer_template
});
}
if (inner_template) {
inner_template.no_serialize = true;
Object.defineProperty(object, 'inner_template', {
enumerable: true, writable: true,
value: inner_template
});
}
return object;
};
/**
* Register control constructor in the controls library
*
* @param {string} type Type of the control
* @param {function} constructor Control constructor function
* @param {function} revive Control revive function
* @returns {undefined}
*/
controls.typeRegister = function(type, constructor, revive) {
controls.factoryRegister(type, constructor);
constructor.is_constructor = true;
constructor.revive = revive;
};
/**
* Register control factory function in the controls library
*
* @param {string} type Type of the control
* @param {function} factory Control factory function
* @returns {undefined}
*/
controls.factoryRegister = function(type, factory) {
var key_parameters = {},
__type = parse_type(type, key_parameters) .toLowerCase();
// normalize prop name, remove lead '/'
for(var prop in key_parameters)
if (prop[0] === '/') {
key_parameters[prop.slice(1)] = key_parameters[prop];
delete key_parameters[prop];
}
if (__type.length < type.length || Object.keys(key_parameters).length) {
// type is subtype with parameters, register to controls.subtypes
key_parameters.__ctr = factory;
var subtypes_array = controls.subtypes[__type] || (controls.subtypes[__type] = []);
subtypes_array.push(key_parameters);
} else {
// register as standalone type
// check name conflict
if (controls[__type])
throw new TypeError('Type ' + type + ' already registered!');
controls[__type] = factory;
}
};
/**
* Register existing parameterized type as a standalone type
*
* @param {string} alias New alias that will be registered, in format namespace.control
* @param {string} type Existing base type + additional #parameters, in format existingtype#parameters
* @returns {undefined}
*/
controls.typeAlias = function(alias, type) {
var parameters = {},
__type = parse_type(type, parameters) .toLowerCase(),
constructor = resolve_ctr(__type, parameters);
if (!constructor)
throw new TypeError('Type ' + __type + ' not registered!');
controls[alias.toLowerCase()] = { __type: __type, parameters: parameters, isAlias: true };
};
controls.parse = function(text) {
try {
return JSON.parse(text) || {};
} catch(e) { console.log(e); }
return {};
};
// >> Events
/**
* Force events collection and event object
*
* @param {object} object Object owns a collection of events
* @param {string} type Type of event
* @param {boolean} capture Capture event flag
* @returns {controls.Event} Collection of a specified type
*/
function force_event(object, type, capture) {
var events = object.events || (object.events = {}),
key = (capture) ? ('#'/*capture*/ + type) : type,
event = events[key];
if (!event) {
events[key] = event = new controls.Event(object, type, capture);
// add DOM listener if attached
if (event.is_dom_event) {
var element = object._element;
if (element)
element.addEventListener(type, event.raise, capture);
}
}
return event;
};
var dom_events =
',change,DOMActivate,load,unload,abort,error,select,resize,scroll,blur,DOMFocusIn,DOMFocusOut,focus,focusin,focusout,\
click,dblclick,mousedown,mouseenter,mouseleave,mousemove,mouseover,mouseout,mouseup,wheel,keydown,keypress,keyup,oncontextmenu,\
compositionstart,compositionupdate,compositionend,DOMAttrModified,DOMCharacterDataModified,DOMNodeInserted,\
DOMNodeInsertedIntoDocument,DOMNodeRemoved,DOMNodeRemovedFromDocument,DOMSubtreeModified,';
controls.Event = function(default_call_this, type, capture, listeners_data) {
var listeners = this.listeners = [],
call_this = this.call_this = default_call_this; // owner of the event object
this.type = type;
this.capture = capture;
this.is_dom_event = (dom_events.indexOf(',' + type + ',') >= 0);
// revive from JSON data
if (listeners_data)
for(var i = 0, c = listeners_data.length; i < c; i+=2) {
listeners.push(listeners_data[i]);
var c_this = listeners_data[i+1];
listeners.push((c_this === call_this) ? null : call_this);
}
this.raise = function() {
for(var i = 0, c = listeners.length; i < c; i+=2)
listeners[i].apply(listeners[i+1] || call_this, arguments);
};
};
controls.Event.prototype = {
addListener: function(call_this/*optional*/, listener) {
if (arguments.length > 1)
this.listeners.push(listener, (call_this === this.call_this) ? null : call_this);
else
this.listeners.push(call_this, null);
},
removeListener: function(listener) {
var listeners = this.listeners,
index = listeners.indexOf(listener);
if (index >= 0)
listeners.splice(index, 2);
},
clear: function() {
this.listeners.length = 0;
},
toJSON: function() {
var jsonlisteners = [],
listeners = this.listeners;
// Serialize listeners
for(var i = 0, c = listeners.length; i < c; i+=2) {
var event_func = listeners[i];
if (!event_func.no_serialize) {
jsonlisteners.push(extract_func_code(event_func));
// call_this not serialize
jsonlisteners.push(null);
}
}
return {type:this.type, capture:this.capture, listeners:jsonlisteners};
}
};
// Post processing
var post_events = [];
setInterval(function() {
if (post_events.length > 0)
for(var i = 0, c = post_events.length; i < c; i++) {
try {
post_events[i].post_event.raise();
}
catch(e) { console.log(e); }
post_events.length = 0;
};
}, 30);
// >> Data objects
var data_object_common = {
listen: function(call_this/*optional*/, listener) {
var event = this.event || (this.event = new controls.Event(this));
event.addListener.apply(event, arguments);
return this;
},
listen_: function(call_this/*optional*/, listener) {
if (typeof listener === 'function')
listener.no_serialize = true;
else
call_this.no_serialize = true;
return this.listen.apply(this, arguments);
},
removeListener: function(listener) {
var event = this.event;
if (event)
event.removeListener(listener);
return this;
},
subscribe: function(call_this/*optional*/, listener) {
if (typeof(call_this) === 'function') {
listener = call_this;
call_this = this;
}
if (!listener)
return this;
var post_event = this.post_event || (this.post_event = new controls.Event(this));
post_event.addListener.apply(post_event, arguments);
return this;
},
unsubscribe: function(listener) {
var post_event = this.post_event;
if (post_event)
post_event.removeListener(listener);
return this;
},
raise: function() {
var event = this.event;
if (event)
event.raise.apply(this, arguments);
var post_event = this.post_event;
if (post_event) {
var index = post_events.indexOf(this);
if (index < 0 || index !== post_events.length - 1) {
if (index >= 0)
post_events.splice(index, 1);
post_events.push(this);
}
}
},
set: function(name, value) {
this.state_id++;
this[name] = value;
this.last_name = name;
this.raise();
},
setx: function(collection) {
var modified;
for(var prop in collection)
if (collection.hasOwnProperty(prop)) {
modified = true;
this.state_id++;
this[prop] = collection[prop];
this.last_name = collection;
}
if (modified)
this.raise();
}
};
function DataObject(parameters, attributes) {
this.state_id = Number.MIN_VALUE;
}
DataObject.prototype = data_object_common;
controls.typeRegister('DataObject', DataObject);
var data_array_common = {
// ops: 1 - insert, 2 - remove, ...
push: function(item) {
var proto = Object.getPrototypeOf(this);
for(var i = 0, c = arguments.length; i < c; i++)
proto.push.call(this, arguments[i]);
this.state_id += c;
this.last_operation = 1;
this.last_index = this.length - 1;
this.raise(this);
}
// TODO
};
function LocalStorageAdapter(parameters, attributes) {
};
LocalStorageAdapter.prototype = {
raise: function(type) {}
};
controls.typeRegister('LocalStorage', LocalStorageAdapter);
// DataArray
//
// Parameters:
// adapter {string} - registered type
// Attributes:
// data - an array of values for the initial filling of the data array
//
// No!Brrr! TODO this
function DataArray(parameters, attributes) { // factory method
var array = [];
if (attributes) {
// $data
var data = attributes.$data;
if (data)
for(var i = 0, c = data.length; i < c; i++)
array[i] = data[i];
}
for(var prop in data_object_common)
if (data_object_common.hasOwnProperty(prop))
array[prop] = data_object_common[prop];
for(var prop in data_array_common)
if (data_array_common.hasOwnProperty(prop))
array[prop] = data_array_common[prop];
array.state_id = Number.MIN_VALUE; // Value identifying the state of the object is incremented each state-changing operation
array.last_operation = 0; // Last state-changing operation
array.last_changed = undefined; // Last changed property name or index
if (parameters && parameters.adapter)
if (!(this.adapter = controls.create(parameters.adapter)))
throw new TypeError('Invalid data adapter type "' + parameters.adapter + '"!');
return array;
}
controls.factoryRegister('DataArray', DataArray);
// >> Controls prototype
controls.control_prototype = new function() {
this.initialize = function(__type, parameters, _attributes, outer_template, inner_template) {
return controls.controlInitialize(this, __type, parameters, _attributes, outer_template, inner_template);
};
function setParent(value, index) {
var parent = this._parent;
if (value !== parent) {
this._parent = value;
var name = this._name;
if (parent) {
var parent_controls = parent.controls,
index = parent_controls.indexOf(this);
if (index >= 0)
parent_controls.splice(index, 1);
if (name && parent.hasOwnProperty(name) && parent[name] === this)
delete parent[name];
}
if (value) {
var value_controls = value.controls;
// profiling: very expensive operation
// var index = value_controls.indexOf(this);
// if (index >= 0)
// parent_controls.splice(index, 1);
if (index === undefined)
value_controls.push(this);
else
value_controls.splice(index, 0, this);
if (name)
value[name] = this;
}
this.raise('parent', value);
}
}
Object.defineProperties(this, {
// name of the control in parent collection
name: {
enumerable: true,
get: function() { return this._name; },
set: function(value) {
if (IDENTIFIERS.indexOf(',' + value + ',') >= 0)
throw new SyntaxError('Invalid name "' + value + '"!');
var name = this._name;
if (value !== name) {
this._name = value;
var parent = this._parent;
if (parent) {
if (name && parent.hasOwnProperty(name) && parent[name] === this)
delete parent[name];
if (value)
parent[value] = this;
}
}
}
},
// The associated element of control
element: {
enumerable: true,
get: function() { return this._element; },
set: function(attach_to_element) {
var element = this._element;
if (attach_to_element !== element) {
this._element = attach_to_element;
var events = this.events;
if (events)
for(var event_type in events) {
var event = events[event_type];
if (event.is_dom_event) {
// remove event raiser from detached element
if (element)
element.removeEventListener(event.type, event.raise, event.capture);
// add event raiser as listener for attached element
if (attach_to_element)
attach_to_element.addEventListener(event.type, event.raise, event.capture);
}
}
this.raise('element', attach_to_element);
}
}
},
parent: {
enumerable: true,
get: function() { return this._parent; },
set: setParent
},
wrapper: {
enumerable: true,
get: function() { return this._wrapper; },
set: function(value) {
var wrapper = this._wrapper;
if (value !== wrapper) {
this._wrapper = value;
if (wrapper) {
var wrapper_controls = wrapper.controls;
var index = wrapper_controls.indexOf(this);
if (index >= 0)
wrapper_controls.splice(index, 1);
}
if (value) {
var value_controls = value.controls;
// profiling: indexOf very expensive operation
// var index = value_controls.indexOf(this);
// if (index >= 0)
// wrapper_controls.splice(index, 1);
value_controls.push(this);
}
}
}
},
length: {
enumerable: true,
get: function() {
return this.controls.length;
}
}
});
this.find = function(selector, by_attrs, recursive) {
if (arguments.length < 3)
recursive = true;
var controls = this.controls,
result = [];
if (typeof selector === 'object' || typeof by_attrs === 'object') {
for(var i = 0, c = controls.length; i < c; i++) {
var control = controls[i];
// by properties
if (selector)
for(var prop in selector)
if (selector.hasOwnProperty(prop)) {
if (control[prop] === selector[prop])
result.push(control);
// by attributes
else if (by_attrs) {
var attributes = control.attributes;
for(var prop in by_attrs)
if (by_attrs.hasOwnProperty(prop) && attributes[prop] === by_attrs[prop])
result.push(control);
} else if (recursive) {
// find recursively
var finded = control.find(selector, by_attrs, recursive);
if (finded.length)
result.push.apply(result, finded);
}
}
}
} else if (!selector) {
return controls[0];
} else if (typeof selector === 'string') {
return callWithSelector.call(this, this.find, selector);
} else if (typeof selector === 'function') {
for(var i = 0, c = controls.length; i < c; i++) {
var control = controls[i];
if (selector(control))
result.push(control);
// find recursively
else if (recursive) {
var finded = control.find(selector, by_attrs, recursive);
if (finded.length)
result.push.apply(result, finded);
}
}
}
};
this.select = function(selector, by_attrs) {
return this.find(selector, by_attrs, false);
};
this.first = function(selector, by_attrs) {
return this.findFirst(selector, by_attrs, false);
};
/**
*
* @param {string,function} selector (string) format name:type`class#id, (object) Control property values for comparing
* @param {type} attrs Control attribute values for comparing
* @returns {object} matched control
*/
this.findFirst = function(selector, by_attrs, recursive) {
if (arguments.length < 3)
recursive = true;
var controls = this.controls;
if (typeof selector === 'object' || typeof by_attrs === 'object') {
for(var i = 0, c = controls.length; i < c; i++) {
var control = controls[i];
// by properties
if (selector)
for(var prop in selector)
if (selector.hasOwnProperty(prop) && control[prop] === selector[prop])
return control;
// by attributes
if (by_attrs) {
var attributes = control.attributes;
for(var prop in by_attrs)
if (by_attrs.hasOwnProperty(prop) && attributes[prop] === by_attrs[prop])
return control;
}
// find recursively
if (recursive) {
var finded = control.findFirst(selector, by_attrs, recursive);
if (finded)
return finded;
}
}
} else if (!selector) {
return controls[0];
} else if (typeof selector === 'string') {
return callWithSelector.call(this, this.findFirst, selector);
} else if (typeof selector === 'function') {
for(var i = 0, c = controls.length; i < c; i++) {
var control = controls[i];
if (selector(control))
return control;
// find recursively
if (recursive) {
var finded = control.findFirst(selector, by_attrs, recursive);
if (finded)
return finded;
}
}
}
};
this.last = function(selector, by_attrs) {
return this.findLast(selector, by_attrs, false);
};
this.findLast = function(selector, by_attrs, recursive) {
if (arguments.length < 3)
recursive = true;
var controls = this.controls;
if (typeof selector === 'object' || typeof by_attrs === 'object') {
for(var i = controls.length - 1; i >= 0; i--) {
var control = controls[i];
// by properties
if (selector)
for(var prop in selector)
if (selector.hasOwnProperty(prop) && control[prop] === selector[prop])
return control;
// by attributes
if (by_attrs) {
var attributes = control.attributes;
for(var prop in by_attrs)
if (by_attrs.hasOwnProperty(prop) && attributes[prop] === by_attrs[prop])
return control;
}
// find recursively
if (recursive) {
var finded = control.findLast(selector, by_attrs, recursive);
if (finded)
return finded;
}
}
} else if (!selector) {
return controls[0];
} else if (typeof selector === 'string') {
return callWithSelector.call(this, this.findLast, selector);
} else if (typeof selector === 'function') {
for(var i = controls.length - 1; i >= 0; i--) {
var control = controls[i];
if (selector(control))
return control;
// find recursively
if (recursive) {
var finded = control.findLast(selector, by_attrs, recursive);
if (finded)
return finded;
}
}
}
};
function callWithSelector(method, selector) {
// name:...
var name, type = selector, colonpos = selector.indexOf(':');
if (colonpos >= 0) {
name = selector.substr(0, colonpos);
type = selector.substr(colonpos + 1);
}
// `...
var clss, gravepos = type.indexOf('`');
if (gravepos >= 0) {
type = selector.substr(0, gravepos);
clss = selector.substr(gravepos + 1);
}
// #...
var num, numpos = type.indexOf('#');
if (numpos >= 0) {
type = selector.substr(0, numpos);
num = selector.substr(numpos + 1);
}
if (type && type.indexOf('.') < 0)
type = 'controls.' + type;
var by_props, by_attrs;
if (type) (by_props || (by_props = {})).__type = type;
if (name) (by_props || (by_props = {})).name = name;
if (clss) (by_attrs || (by_attrs = {})).class = clss;
return method.call(this, by_props, by_attrs);
}
// default html template
this.outer_template = function(it) { return '<div' + it.printAttributes() + '>' + (it.attributes.$text || '') + it.printControls() + '</div>'; };
controls.default_outer_template = this.outer_template;
// default inner html template
this.inner_template = function(it) { return (it.attributes.$text || '') + it.printControls(); };
controls.default_inner_template = this.inner_template;
// default inline template
this.outer_inline_template = function(it) { return '<span' + it.printAttributes() + '>' + (it.attributes.$text || '') + it.printControls() + '</span>'; };
controls.default_outer_inline_template = this.outer_inline_template;
// snippets:
//
// {{? it.attributes.$icon }}<span class="{{=it.attributes.$icon}}"></span> {{?}}
// {{? it.attributes.$text }}{{=it.attributes.$text}}{{?}}
// include list of subcontrols html:
// {{~it.controls :value:index}}{{=value.wrappedHTML()}}{{~}}
this.innerHTML = function() {
// assemble html
return this.inner_template(this);
};
this.outerHTML = function() {
// assemble html
return this.outer_template(this);
};
this.wrappedHTML = function() {
var wrapper = this._wrapper;
return (wrapper) ? wrapper.wrappedHTML() : this.outerHTML();
};
// set template text or template function
this.template = function(outer_template, inner_template) {
if (outer_template) {
if (typeof outer_template === 'string')
outer_template = controls.template(outer_template);
if (!this.hasOwnProperty("outer_template"))
Object.defineProperty(this, "outer_template", { configurable:true, enumerable:true, writable:true, value:outer_template });
else
this.outer_template = outer_template;
}
if (inner_template) {
if (typeof outer_template === 'string')
inner_template = controls.template(inner_template);
if (!this.hasOwnProperty("inner_template"))
Object.defineProperty(this, "inner_template", { configurable:true, enumerable:true, writable:true, value:inner_template });
else
this.inner_template = inner_template;
}
return this;
};
this.toJSON = function() {
var json = {
__type: this.type(),
attributes: this.attributes
};
var name = this.name;
if (name)
json.name = name;
var ctrls = this.controls;
if (ctrls.length)
json.controls = ctrls;
if (this.hasOwnProperty('outer_template')) {
var outer_template = this.outer_template;
if (!outer_template.no_serialize)
json.outer_template = extract_func_code(this.outer_template);
}
if (this.hasOwnProperty('inner_template')) {
var inner_template = this.outer_template;
if (!inner_template.no_serialize)
json.inner_template = extract_func_code(this.inner_template);
}
var events = this.events;
if (events) {
var jevents = [];
for(var prop in events)
if (events.hasOwnProperty(prop)) {
var event = events[prop],
listeners = event.listeners,
serialize = false;
for(var i = 0, c = listeners.length; i < c; i+=2)
if (!listeners[i].no_serialize) {
serialize = true;
break;
}
if (serialize)
jevents.push(event);
}
if (jevents.length)
json.events = jevents;
}
return json;
};
// TODO: remove excess refresh calls
this.refresh = function() {
var element = this._element;
if (element) {
if (!element.parentNode) {
// orphaned element
this._element = undefined;
} else {
try {
// Setting .outerHTML breaks hierarchy DOM, so you need a complete re-initialisation bindings to DOM objects.
// Remove wherever possible unnecessary calls .refresh()
var html = this.outerHTML();
if (html !== element.outerHTML) {
this.detachAll();
element.outerHTML = html;
this.attachAll();
}
}
catch (e) {
// Uncaught Error: NoModificationAllowedError: DOM Exception 7
// 1. ? xml document
// 2. ? "If the element is the root node" ec orphaned element
this._element = undefined;
}
}
}
return this;
};
this.refreshInner = function() {
var element = this._element;
if (element)
element.innerHTML = this.innerHTML();
return this;
};
// Attach to DOM element
this.attach = function(some) {
this.element = (!arguments.length)
? document.getElementById(this.id)
: (typeof(some) === 'string') ? document.getElementById(some) : (some && (some._element || some));
return this;
};
// Attach this and all nested controls to DOM by id
this.attachAll = function() {
if (!this._element)
this.element = document.getElementById(this.id);
for(var ctrls = this.controls, i = 0, c = ctrls.length; i < c; i++)
ctrls[i].attachAll();
return this;
};
// Detach from DOM
this.detach = function() {
this.element = undefined;
return this;
};
// Detach this and all nested from DOM
this.detachAll = function() {
this.element = undefined;
for(var ctrls = this.controls, i = 0, c = ctrls.length; i < c; i++)
ctrls[i].detachAll();
return this;
};
// Replace control in the hierarchy tree
this.replaceItself = function(control) {
var controls = this.controls;
// .controls may be a DataArray
for(var i = controls.length - 1; i >= 0; i--)
control.add(controls.shift());
var parent = this.parent;
if (!parent)
control.parent = undefined;
else {
var index = parent.controls.indexOf(this);
this.parent = undefined;
setParent.call(control, parent, index);
}
var element = this._element;
if (!element)
control.element = undefined;
else {
control.element = element;
control.refresh(); // rewrite dom
}
};
// opcode {number} - 0 - insert before end, 1 - insert after begin, 2 - insert before, 3 - insert after
this.createElement = function(node, opcode) {
var element = this._element,
parent = this.parent;
if (element)
throw new TypeError('Element already exists!');
if (!node && parent) {
node = parent.element;
opcode = 0;
}
if (node && '__type' in node)
node = node.element;
if (!node)
throw new TypeError('Failed to create element!');
if (node.insertAdjacentHTML) {
var pos;
switch(opcode) {
case 1: pos = 'afterbegin'; break;
case 2: pos = 'beforebegin'; break;
case 3: pos = 'afterend'; break;
default: pos = 'beforeend';
}
// illegal invocation on call this method before element completed
node.insertAdjacentHTML(pos, this.outerHTML());
} else {
var fragment = document.createDocumentFragment(),
el = document.createElement('div');
el.innerHTML = this.outerHTML();
var buf = Array.prototype.slice.call(el.childNodes);
for(var i = 0, c = buf.length; i < c; i++)
fragment.appendChild(buf[i]);
switch(opcode) {
case 1:
(node.childNodes.length === 0) ? node.appendChild(fragment) : node.insertBefore(node.firstChild, fragment);
break;
case 2:
var nodeparent = node.parentNode;
if (nodeparent)
nodeparent.insertBefore(fragment, node);
break;
case 3:
var nodeparent = node.parentNode;
if (nodeparent) {
var next_node = node.nextSibling;
if (next_node)
nodeparent.insertBefore(fragment, next_node);
else
nodeparent.appendChild(fragment);
}
break;
default:
node.appendChild(fragment);
}
}
return this.attachAll();
};
this.deleteElement = function() {
var element = this._element;
if (element) {
this.detachAll();
var parent_node = element.parentNode;
if (parent_node)
parent_node.removeChild(element);
this._element = undefined;
}
return this;
};
this.deleteAll = function() {
this.deleteElement();
for(var ctrls = this.controls, i = ctrls.length - 1; i >= 0; i--)
ctrls[i].deleteAll();
return this;
};
/**
* Add event listener.
*
* @param {string} type Event type. Event type may be DOM event as "click" or special control event as "type".
* @param {object} [call_this] The value to be passed as the this parameter to the target function when the event handler function is called.
* @param {function} listener Event handler function.
* @param {boolean} [capture] This argument will be passed to DOM.addEventListener(,, useCapture).
* @returns Returns this.
*/
this.on = this.listen = function(type, /*optional*/ call_this, listener, /*optional*/ capture) {
if (typeof(call_this) === 'function') {
capture = listener;
listener = call_this;
call_this = null;
}
if (type && listener)
force_event(this, type, capture)
.addListener(call_this, listener);
return this;
};
// set listener and check listener as no_serialize
this.on_ = this.listen_ = function(type, call_this, listener, capture) {
if (typeof(call_this) === 'function') {
capture = listener;
listener = call_this;
call_this = null;
}
if (type && listener) {
force_event(this, type, capture)
.addListener(call_this, listener);
listener.no_serialize = true;
}
return this;
};
// Alias for listen()
this.addListener = function(type, call_this/*optional*/, listener, capture) {
return this.listen(type, call_this, listener, capture);
};
/**
* Remove event listener.
*
* @param {string} type Event type.
* @param {function} listener Event handler function.
* @param [capture] This argument will be passed to DOM.removeEventListener(,, useCapture).
* @returns Returns this.
*/
this.removeListener = function(type, listener, capture) {
if (type && listener)
force_event(this, type, capture).removeListener(listener);
return this;
};
/**
* Raise event.
*
* @param {string} type Event type.
* @param Arbitrary number of arguments to be passed to handlers.
* @returns Returns this.
*/
this.raise = function(type) {
var events = this.events;
if (type && events) {
var capture_event = events['#' + type],
event = events[type],
args = Array.prototype.slice.call(arguments, 1);
if (capture_event)
capture_event.raise.apply(this, args);
if (event)
event.raise.apply(this, args);
}
return this;
};
this.parameter = function(name, value) {
var parameters = this.parameters;
if (arguments.length <= 1)
return parameters[name] || parameters['/'+name];
if (value !== parameters[name]) {
parameters[name] = value;
this.refresh();
}
};
this._parameter = function(name, value) {
this.parameter(name, value);
return this;
};
// set attribute value
this.attr = function(name, value) {
var attributes = this.attributes;
if (arguments.length === 0)
return undefined;
if (arguments.length === 1)
return attributes[name];
if (value !== attributes[name]) {
attributes[name] = value;
if (this._element)
this.refresh();
}
};
this._attr = function(name, value) {
this.attr(name, value);
return this;
};
// set attributes
this.attrs = function(_attributes) {
var attributes = this.attributes;
if (arguments.length > 0) {
var updated = false;
for(var prop in _attributes)
if (_attributes.hasOwnProperty(prop)) {
var value = _attributes[prop];
if (value !== attributes[prop]) {
attributes[prop] = value;
updated = true;
}
}
if (updated && this._element)
this.refresh();
}
return attributes;
};
this._attrs = function(_attributes) {
this.attrs(_attributes);
return this;
};
// get/set path.type/parameters
this.type = function(type, apply_inherited) {
// >> get type
if (!arguments.length) {
var inheritable = '', unheritable = '', parameters = this.parameters;
for(var prop in parameters)
if (parameters.hasOwnProperty(prop)) {
var value = parameters[prop];
if (typeof value === 'boolean' && value)
value = '';
else {
if (typeof value !== 'string')
value = String(value);
value = ((value.indexOf(' ') >= 0) ? ('="' + value + '"') : ('=' + value));
}
if (prop[0] === '/') {
// inheritable parameters
if (inheritable) inheritable += ' ';
inheritable += prop.substr(1) + value;
} else {
// not inheritable parameters
if (unheritable) unheritable += ' ';
unheritable += prop + value;
}
}
var type = this.__type;
if (unheritable)
type += ' ' + unheritable;
if (inheritable)
type += '/' + inheritable;
return type;
}
// << get type
// >> set type and parameters
var parameters = this.parameters; // rebuild parameters
for(var prop in parameters)
if (parameters.hasOwnProperty(prop))
delete parameters[prop];
if (apply_inherited && this.parent) {
// get inheritable parameters from this object for transfer to the created object
var parent_parameters = parent.parameters;
for(var prop in parent_parameters)
if (parameters.hasOwnProperty(prop) && prop[0] === '/')
parameters[prop] = parent_parameters[prop];
}
this.__type = parse_type(type, parameters, this.attributes) || this.__type;
this.raise('type');
// no automatic refresh() calls
// << set type and parameters
};
this._type = function(type, apply_inherited) {
this.type(type, apply_inherited);
return this;
};
// Get html code of the selected attributes
//
// attributes (optional, string) - attributes, comma separated list
// exclude (optional, bool) - use first argument as filter (false) or exclude list (true)
// example: it.printAttributes("style") - result only one style attribute 'style="..."'
// example: it.printAttributes("-id") - result attributes all exclude id
//
this.printAttributes = function(filter) {
var result = '', attributes = this.attributes;
if (filter) {
// TODO: temporary inserted this checking:
if (filter.indexOf(',') >= 0)
console.log('printAttributes() Use a space to separate of identifiers');
if (filter[0] === '-') {
// exclusion defined
var exclude = filter.substr(1).split(' ');
for(var prop in attributes)
if (attributes.hasOwnProperty(prop) && prop[0] !== '$' && exclude.indexOf(prop) < 0) {
var value = attributes[prop];
if (value)
result += ' ' + prop + '="' + value + '"';
}
}
else {
// list of attributes
var attrs = filter.split(' ');
for(var i = 0, c = attrs.length; i < c; i++) {
var key = attrs[i],
value = attributes[key];
if (value)
result += ' ' + key + '="' + value + '"';