xmlplus
Version:
A JavaScript framwork for developing projects efficiently.
1,250 lines (1,233 loc) • 72.5 kB
JavaScript
/*!
* xmlplus.js v1.7.32
* https://xmlplus.cn
* (c) 2017-2026 qudou
* Released under the MIT license
*/
(function (inBrowser, undefined) {
"use strict";
const ELEMENT_NODE = 1;
//const ATTRIBUTE_NODE = 2;
//const TEXT_NODE = 3;
//const CDATA_SECTION_NODE = 4;
//const ENTITY_REFERENCE_NODE = 5;
//const ENTITY_NODE = 6;
//const PROCESSING_INSTRUCTION_NODE = 7;
//const COMMENT_NODE = 8;
const DOCUMENT_NODE = 9;
//const DOCUMENT_TYPE_NODE = 10;
const DOCUMENT_FRAGMENT_NODE = 11;
//const NOTATION_NODE = 12;
const svgns = "http://www.w3.org/2000/svg";
const htmlns = "http://www.w3.org/1999/xhtml";
const xlinkns = "http://www.w3.org/1999/xlink";
// vdoc is for virtual DOM, and rdoc is for real DOM.
let vdoc, rdoc;
let XPath, DOMParser_, XMLSerializer_, NodeElementAPI;
const Manager = [HtmlManager(),CompManager(),,TextManager(),TextManager(),,,,TextManager(),,];
const PM = PackageManager();
const Template = { css: "", cfg: {}, opt: {}, ali: {}, map: { cfgs: {}, attrs: {} }, fun: new Function };
// isHTML contains isSVG
const isSVG = {}, isHTML = {};
(function () {
let i = -1, k = -1,
s = "animate animateMotion animateTransform circle clipPath cursor defs desc discard ellipse feBlend feColorMatrix feComponentTransfer feComposite feConvolveMatrix feDiffuseLighting feDisplacementMap feDistantLight feDropShadow feFlood feFuncA feFuncB feFuncG feFuncR feGaussianBlur feImage feMerge feMergeNode feMorphology feOffset fePointLight feSpecularLighting feSpotLight feTile feTurbulence filter foreignObject g hatch hatchpath image line linearGradient marker mask mesh meshpatch meshrow metadata mpath path pattern polygon polyline radialGradient rect set solidcolor stop svg switch symbol text textPath tspan unknown use view".split(" "),
h = "a abbr acronym address applet area article aside audio b base basefont bdi bdo big blockquote body br button canvas caption center cite code col colgroup command datalist dd del details dfn dialog dir div dl dt em embed fieldset figcaption figure font footer form frame frameset h1 h2 h3 h4 h5 h6 head header hr html i iframe img input ins kbd keygen label legend li link main map mark menu menuitem meta meter nav noframes noscript object ol optgroup option output p param pre progress q rp rt rtc ruby s samp script section select small source span strike strong style sub summary sup table tbody td textarea tfoot th thead time title tr track tt u ul var video wbr xmp void".split(" ");
while (h[++k]) isHTML[h[k]] = 1;
while (s[++i]) isSVG[s[i]] = isHTML[s[i]] = 1;
}());
// The Library contains the components imported by the imports function.
// The components are initialized by the system
// It contains two levels: the first is the component space and the second is the component name.
// eg. Library["//xp"][Input] = {};
let Library = {};
// The Store establishes the mapping from component instance uid to component instance.
let Store = {};
// The Global stores the application objects created by the '$.startup' function.
// You can access the objects through the function '$.getElementById'.
let Global = {};
let $ = {
debug: true,
startup: startup,
create: function (path, options) {
let widget = $.hasComponent(path);
if (!widget)
throw Error(`component ${path} not exists`);
return widget.fun(null, null, options);
},
guid: (function () {
let counter = 0;
function intToABC(num) {
let ch = String.fromCharCode(97 + num % 26);
return num < 26 ? ch : intToABC(Math.floor(num / 26) - 1) + ch;
}
return () => {
return intToABC(counter++);
};
}()),
ready: function (callback) {
if (!$.isFunction(callback))
throw Error("Invalid callback, expected a function");
rdoc.addEventListener('DOMContentLoaded', callback);
},
type: (function () {
let i, class2type = {},
types = "Boolean Number String Function AsyncFunction Array Date RegExp Object Error".split(" ");
for (i = 0; i < types.length; i++)
class2type[ "[object " + types[i] + "]" ] = types[i].toLowerCase();
return function(obj) {
return obj == null ? obj + "" : class2type[class2type.toString.call(obj)] || "object";
};
}()),
isArray: Array.isArray || function (obj) {
return obj instanceof Array
},
likeArray: function (obj) {
let length = !!obj && "length" in obj && obj.length,
type = $.type(obj);
if (type === "function" || $.isWindow(obj))
return false;
return type === "array" || length === 0 ||
typeof length === "number" && length > 0 && (length - 1) in obj;
},
isWindow: function (obj) {
return obj != null && obj == obj.window;
},
isFunction: function (obj) {
let type = $.type(obj);
return type == "function" || type == "asyncfunction";
},
isNumeric: function (obj) {
let type = $.type(obj);
return (type === "number" || type === "string") && !isNaN(obj - parseFloat(obj));
},
isPlainObject: function (obj) {
return $.type(obj) == "object" && !$.isWindow(obj) && Object.getPrototypeOf(obj) == Object.prototype;
},
isEmptyObject: function (obj) {
for (let name in obj) return false;
return true;
},
isSystemObject: function (obj) {
return obj && typeof obj.guid == "function" && !!Store[obj.guid()];
},
extend: function () {
// This function is modified from jQuery.
// https://jquery.com/
let options, name, src, copy, copyIsArray, clone,
target = arguments[ 0 ] || {},
i = 1,
length = arguments.length,
deep = false;
if ( typeof target === "boolean" ) {
deep = target;
target = arguments[ i ] || {};
i++;
}
if ( typeof target !== "object" && !$.isFunction( target ) ) {
target = {};
}
if ( i === length ) {
target = this;
i--;
}
for ( ; i < length; i++ ) {
if ( ( options = arguments[ i ] ) != null ) {
for ( name in options ) {
src = target[ name ];
copy = options[ name ];
if ( target === copy ) {
continue;
}
if ( deep && copy && ( $.isPlainObject( copy ) ||
( copyIsArray = $.isArray( copy ) ) ) ) {
if ( copyIsArray ) {
copyIsArray = false;
clone = src && $.isArray( src ) ? src : [];
} else {
clone = src && $.isPlainObject( src ) ? src : {};
}
target[ name ] = $.extend( deep, clone, copy );
} else if ( copy !== undefined ) {
target[ name ] = copy;
}
}
}
}
return target;
},
each: function (objs, callback) {
if ($.likeArray(objs)) {
for (let i = 0; i < objs.length; i++ )
if (callback.call(objs[i], i, objs[i]) === false) break;
} else for (let key in objs) {
if (callback.call(objs[key], key, objs[key]) === false) break;
}
return objs;
},
parseXML: function (data) {
let xml;
if (!data || typeof data !== "string")
throw Error("Invalid data, expected a string");
try {
xml = (new DOMParser_()).parseFromString(data, "text/xml");
} catch (e) {
xml = undefined;
}
if (!xml || xml.getElementsByTagName( "parsererror" ).length)
throw Error("Invalid XML: " + data);
return xml;
},
serialize: function (node) {
return (new XMLSerializer_).serializeToString(node, true);
},
hasNamespace: function (space) {
return !!Library[space];
},
hasComponent: function (path) {
if (typeof path != "string")
return false;
if (isHTML[path])
return true;
let s = ph.split(path);
s.dir = s.dir.substr(2);
return Library[s.dir] && Library[s.dir][s.basename] || false;
},
getElementById: function (id, isGuid) {
if (isGuid)
return Store[id] && Store[id].api;
return inBrowser ? (Global[id] || rdoc.getElementById(id)) : null;
},
proxyToJSON: function (proxy) {
let type = Object.prototype.toString.call(proxy);
switch (type) {
case "[object Object]":
let obj = {};
for(let k in proxy)
obj[k] = $.proxyToJSON(proxy[k]);
return obj;
case "[object Array]":
let i, arr = [];
for (i = 0; i < proxy.length; i++)
arr.push($.proxyToJSON(proxy[i]));
return arr;
case "[object Literal]":
case "[object Normal]":
return proxy.value;
default:
throw new Error("Type error!");
}
},
delay: ms => new Promise((resolve, reject) => setTimeout(resolve, ms))
};
let ph = (function () {
let table = {};
function normalizeArray(parts, allowAboveRoot) {
let i = parts.length - 1, up = 0;
for (; i >= 0; i--) {
let last = parts[i];
if (last === '.')
parts.splice(i, 1);
else if (last === '..')
parts.splice(i, 1), up++;
else if (up)
parts.splice(i, 1), up--;
}
if (allowAboveRoot)
for ( ; up--; up )
parts.unshift('..');
return parts;
}
function filter(xs) {
let i = 0, res = [];
for (; i < xs.length; i++)
xs[i] && res.push(xs[i]);
return res;
}
function isAbsolute(path) {
return path.charAt(0) === '/';
}
function normalize(path) {
let absolute = isAbsolute(path),
trailingSlash = path.substr(-1) === '/';
path = normalizeArray(filter(path.split('/')), !absolute).join('/');
if (!path && !absolute)
path = '.';
if (path && trailingSlash)
path += '/';
return (absolute ? '/' : '') + path;
}
function join(part1, part2) {
let paths = [part1,part2];
return normalize(filter(paths).join('/'));
}
function split(path) {
let i = path.lastIndexOf('/');
return { dir: path.substring(0, i), basename: path.substr(i+1).toLowerCase() };
}
// (dir/foo, ..) => dir, (dir, ./bar) => dir/bar
function fullPath(dir, patt) {
let key = dir + patt;
if (table[key])
return table[key];
if (patt.substr(0, 2) == "//")
return table[key] = patt.substr(2);
return table[key] = isAbsolute(patt) ? join(dir.split('/')[0], patt.slice(1)) : join(dir, patt);
}
return { split, fullPath };
}());
let hp = {
parseToXML: function (input, dir) {
if ( input == null )
throw Error("Invalid input, expected a string or a xml node");
if ( input.ownerDocument )
return input;
if ( typeof input != "string" )
throw Error("invalid input, expected a string or a xml node");
if ( isHTML[input] )
return vdoc.createElement(input);
if ( input.charAt(0) == '<' ) try {
return $.parseXML(input).lastChild;
} catch (e) {
return vdoc.createTextNode(input);
}
let i = input.lastIndexOf('/');
dir = ph.fullPath(dir || "", input.substring(0, i) || ".");
let basename = input.substr(i+1);
if ( Library[dir] && Library[dir][basename.toLowerCase()] )
return vdoc.createElementNS("//" + dir, 'i:' + basename);
return vdoc.createTextNode(input);
},
defDisplay: (function () {
let elemDisplay = {};
return function ( nodeName ) {
let elem, display;
if (!elemDisplay[nodeName]) {
elem = rdoc.createElement(nodeName);
rdoc.body.appendChild(elem);
display = getComputedStyle(elem, "").getPropertyValue("display");
elem.parentNode.removeChild(elem);
display == "none" && (display = "block");
elemDisplay[nodeName] = display;
}
return elemDisplay[nodeName];
};
}()),
offsetParent: function (elem) {
let parent = elem.offsetParent;
while ( parent && hp.css(parent, "position") == "static" )
parent = parent.offsetParent;
return parent || rdoc.documentElement;
},
offset: function (elem) {
let obj = elem.getBoundingClientRect();
return {
left: obj.left + pageXOffset,
top: obj.top + pageYOffset
};
},
css: function (elem, name) {
return elem.style[name] || getComputedStyle(elem, "").getPropertyValue(name);
},
addClass: function (elem, value) {
let klass = elem.getAttribute("class"),
input = value.split(/\s+/),
result = klass ? klass.split(/\s+/) : [];
for (let i = 0; i < input.length; i++)
if (result.indexOf(input[i]) < 0)
result.push(input[i]);
elem.setAttribute("class", result.join(" "));
},
callback: function () {
let ret = this.fn.apply(this.data, [].slice.call(arguments));
return ret == this.data ? this.api : ret;
},
build: function (data, object) {
let api = {};
let proxy = new Proxy(object, {
get(target, propKey, receiver) {
if (object[propKey] === undefined)
return Reflect.get(target, propKey, receiver);
if (!api.hasOwnProperty(propKey))
api[propKey] = hp.callback.bind({fn: object[propKey], data: data, api: proxy});
return api[propKey];
},
});
return proxy;
},
create: function (item) {
item.api || (item.api = item.back = hp.build(item, item.typ > 1 ? TextElementAPI : NodeElementAPI));
return item;
},
elem: function () {
return this.ele || Store[this.xml.lastChild.uid].elem();
},
appendTo: function () {
if (this.ele) return this.ele;
let target = this.fdr.sys[this.map.appendTo];
if (!target)
return Store[this.xml.lastChild.uid].elem()
return Store[target.guid()].appendTo();
},
createElement: (function() {
let buffer = {};
return function (node, parent) {
let nodeName = node.nodeName;
buffer[nodeName] || (buffer[nodeName] = rdoc.createElementNS(isSVG[nodeName] ? svgns : (isHTML[nodeName] ? htmlns : node.namespaceURI), nodeName));
let elem = buffer[nodeName].cloneNode();
parent.appendChild(elem);
for (let i = 0; i < node.attributes.length; i++) {
let attr = node.attributes[i];
if (attr.prefix == "xlink")
elem.setAttributeNS(xlinkns, attr.nodeName, attr.nodeValue);
if (attr.nodeName != "id" && (!isHTML[nodeName] || !attr.prefix))
elem.setAttribute(attr.nodeName, attr.nodeValue);
}
return elem;
};
}()),
nodeIsMatch: function (xml, expr, node) {
let i = XPath.select(expr, xml);
return [].slice.call(i).indexOf(node) != -1
},
xpathQuery: (function() {
let exprs = {};
return function (expr, xml) {
let nodes = [];
if (!exprs[expr])
exprs[expr] = (new XPathEvaluator).createExpression(expr);
let result = exprs[expr].evaluate(xml, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE);
if (result)
for (let i = 0, len = result.snapshotLength; i < len; i++)
nodes.push(result.snapshotItem(i));
return nodes;
}
}()),
parseHTML: function parse(node) {
if (node.nodeType != ELEMENT_NODE) return;
if (isHTML[node.nodeName.toLowerCase()]) {
for (let k in node.childNodes)
parse(node.childNodes[k]);
} else {
let xml = $.serialize(node).replace(/(<\/?\w:)((\w|\d)+)/ig, function (s, prefix, localName) {
return prefix.toLowerCase() + localName;
});
let id = node.getAttribute("id"),
val = startup(xml, node.parentNode);
id && (Global[id] = val) && val.attr("id", id);
node.parentNode.replaceChild(node.parentNode.lastChild, node);
}
}
};
let bd = {
isLiteral: function (value) {
return $.isNumeric(value) || $.type(value) == "string" || $.type(value) == "boolean";
},
bindLiteral: function (view, value) {
let elem = view.elem();
let key = "value";
let { proxy, revoke } = Proxy.revocable({}, {get: get, set: set, deleteProperty: deleteProperty})
let type = 0;
switch(elem.nodeName) {
case "PROGRESS":
case "SELECT":
case "OPTION":
case "TEXTAREA":
type = 1;
break;
case "INPUT":
let at = elem.getAttribute("type");
type = (at == "range" || at == "text" ? 1 : 2);
}
let sys = view.env.fdr.sys;
let dispatchEvent = elem.dataset.dispatchEvent;
function get(target, propKey, receiver) {
if (propKey === Symbol.toStringTag)
return "Literal";
if (propKey != key)
return Reflect.get(target, propKey, receiver);
let value = type == 1 ? elem.value : (type ? elem.checked : view.api.text());
if (dispatchEvent) {
let object = {value: value};
view.api.trigger("$/after/getting", object);
value = object.value;
}
return value;
}
function set(target, propKey, value) {
if (propKey != key)
return Reflect.set(target, propKey, value);
if (!bd.isLiteral(value))
throw Error("Only accept data of literal type");
if (dispatchEvent) {
let object = {value: value};
view.api.trigger("$/before/setting", object);
value = object.value;
}
type == 1 ? (elem.value = value) : (type ? (elem.checked = value) : view.api.text(value));
return true;
}
// I feel that the deletion operation is somewhat redundant.
function deleteProperty(target, propKey, receiver) {
if (propKey == key)
throw Error("This value is prohibited from being deleted");
return true;
}
view.api.once("$/before/remove", e => {
revoke();
e.stopPropagation();
});
return proxy;
},
bindObject: function (view, value) {
let [objects, proxys] = [value, {}];
let { proxy, revoke } = Proxy.revocable(objects, {get: get, set: set, deleteProperty: deleteProperty});
function get(target, propKey, receiver) {
if (!objects.hasOwnProperty(propKey))
return Reflect.get(target, propKey, receiver);
return proxys[propKey][0].proxy;
}
function set(target, propKey, value) {
if (!objects.hasOwnProperty(propKey))
throw Error("The proxy does not exist");
proxys[propKey] ||= [];
if ($.isArray(objects[propKey]))
eraseArray(proxys[propKey]);
objects[propKey] = value;
let items = view.fdr.sys[propKey];
if (!items) {
let normal = bd.bindNormal(view, value)
return proxys[propKey].push({proxy: normal});
}
if ($.isSystemObject(items))
items = [items];
items = items.map(v => {return Store[v.guid()]});
items.forEach(view => {
let proxy = view.api.bind(value);
proxys[propKey].push({view: view, proxy: proxy});
});
return true;
}
function eraseArray(proxyList) {
proxyList.forEach(item => {
while (item.proxy.length)
delete item.proxy[0];
});
proxyList.splice(0);
}
function deleteProperty(target, propKey, receiver) {
if (!objects.hasOwnProperty(propKey))
return Reflect.deleteProperty(target, propKey, receiver);
if ($.isArray(objects[propKey])) {
eraseArray(proxys[propKey]);
} else {
proxys[propKey].forEach(i => {
if (i.view)
i.view.api.remove();
else
delete i.proxy.value;
});
proxys[propKey].splice(0);
}
delete objects[propKey];
return true;
}
view.api.once("$/before/remove", e => {
revoke();
e.stopPropagation();
});
return proxy;
},
bindArray: function (view, value) {
let render = view.node.cloneNode(false);
render.removeAttribute("id");
view.api.hide();
let List = new Function;
List.prototype = {length: 0, push: push, pop: pop};
let [views, list, empty] = [[], new List, []];
let { proxy, revoke } = Proxy.revocable(list, {get: get, set: set, deleteProperty: deleteProperty});
function push(value) {
let item = view.api.before(render.cloneNode(false));
views.push(item);
empty.push.apply(list, [item.bind(value)]);
return true;
}
function pop(exp = 1) {
if (!list.length)
return undefined;
let item,
i = list.length - 1;
if (exp)
item = $.proxyToJSON(list[i]);
delete proxy[i];
return item;
}
function get(target, propKey, receiver) {
if (propKey === Symbol.toStringTag)
return "Array";
if ($.isNumeric(propKey))
return list[propKey];
return Reflect.get(target, propKey, receiver);
}
function set(target, propKey, value) {
if ($.isArray(value))
throw Error("Do not accept data of array type.");
if (propKey == list.length)
return push(value);
if (!views[propKey])
throw Error(`Prop name ${propKey} does not exist.`);
views[propKey] = views[propKey].replace(render.cloneNode(false));
empty.splice.apply(list, [propKey, 1, views[propKey].bind(value)])
return true;
}
function deleteProperty(target, propKey, receiver) {
if (!views[propKey])
return Reflect.deleteProperty(target, propKey, receiver);
views[propKey].remove();
views.splice(propKey,1);
empty.splice.apply(list, [propKey,1]);
return true;
}
view.api.once("$/before/remove", e => {
revoke();
e.stopPropagation();
});
return proxy;
},
bindNormal: function (view, value) {
let value_ = value;
let key = "value";
let { proxy, revoke } = Proxy.revocable({}, {get: get, set: set, deleteProperty: deleteProperty});
function get(target, propKey, receiver) {
if (propKey != key)
return Reflect.get(target, propKey, receiver);
return value_
}
function set(target, propKey, value) {
if (propKey === Symbol.toStringTag)
return "Normal";
if (propKey != key)
return Reflect.set(target, propKey, value);
value_ = value;
return true;
}
function deleteProperty(target, propKey, receiver) {
propKey == key && revoke();
return true;
}
view.api.once("$/before/remove", e => {
revoke();
e.stopPropagation();
});
return proxy;
}
};
let Collection = (function () {
let emptyArray = [],
fn = new Function,
slice = emptyArray.slice,
list = "every forEach indexOf map pop push shift some splice unshift".split(' ');
fn.prototype = {
length: 0,
slice: function () {
let result = new Collection,
list = slice.apply(this, slice.call(arguments));
for (let i = 0; i < list.length; i++)
result.push(list[i]);
return result;
},
hash: function () {
let i = 0, table = {};
for (; i < this.length; i++)
table[this[i]] = this[i];
return table;
},
call: function (fnName) {
if (typeof fnName != "string")
throw Error("Invalid function name, expected a string");
let args = slice.call(arguments).slice(1);
for (let i = 0; i < this.length; i++)
if ($.isFunction(this[i][fnName]))
this[i][fnName].apply(this[i], args);
return this;
},
values: function () {
let result = new Collection;
for (let i = 0; i < this.length; i++)
result.push(this[i].val());
return result;
}
};
for (let i = 0; i < list.length; i++)
fn.prototype[list[i]] = emptyArray[list[i]];
return fn;
}());
let MessageModuleAPI = (function () {
let table = {};
function watch(type, fn) {
if (typeof fn !== "function")
throw Error("Invalid handler, expected a function");
let uid = this.elem().xmlTarget.uid;
table[uid] = table[uid] || {};
table[uid][type] = table[uid][type] || [];
table[uid][type].push({watcher: this, fn: fn});
return this;
}
function glance(type, fn) {
function callback(e) {
this.unwatch(type, callback);
fn.apply(this, [].slice.call(arguments));
}
return this.api.watch(type, callback);
}
function unwatch(type, fn) {
let item = table[this.elem().xmlTarget.uid] || {};
if (type == undefined) {
for (type in item)
unwatch.call(this, type);
return this;
}
if (!item[type]) return this;
let buf = [].slice.call(item[type]);
if (typeof fn == "function") {
for (let k in buf)
if (fn == buf[k].fn)
item[type].splice(item[type].indexOf(buf[k]), 1);
} else {
delete item[type];
}
return this;
}
function notify(type, data) {
let that = this;
data = data == null ? [] : ($.isArray(data) ? data : [data]);
// the false of returned means the process has been canceled
(function iterate(target) {
let uid = target.uid;
if (target.fdr) {
let filter = target.map.msgFilter;
if (filter && filter.test(type) && target != that)
return true;
if (iterate(Store[Store[uid].xml.lastChild.uid]) == false)
return false;
} else {
let cancel;
let targets = table[uid] && table[uid] || {};
$.each(targets[type], (key, item) => {
let e = {type: type, target: that.api, currentTarget: item.watcher.api};
e.stopImmediateNotification = ()=> e.cancelImmediate = true;
e.stopNotification = ()=> e.cancel = true;
item.fn.apply(e.currentTarget, [e].concat(data));
if (e.cancelImmediate)
return !(cancel = true);
e.cancel && (cancel = e.cancel);
});
if (cancel) return false;
}
for (let i = 0; i < target.node.childNodes.length; i++) {
let node = target.node.childNodes[i];
if (node.nodeType == 1)
if (iterate(Store[node.uid]) == false)
return false;
}
}(this));
return this;
}
function remove(target) {
delete table[target.uid];
}
function messages() {
let result = {};
(function iterate(target) {
let uid = target.uid;
if (target.fdr)
iterate(Store[Store[uid].xml.lastChild.uid]);
else if (table[uid]) {
for (let key in table[uid])
result[key] = 1;
}
for (let i = 0; i < target.node.childNodes.length; i++) {
let node = target.node.childNodes[i];
node.nodeType == 1 && iterate(Store[node.uid]);
}
}(this));
return Object.keys(result);
}
return { watch, glance, unwatch, notify, remove, messages };
}());
let EventModuleAPI = (function () {
let eventTable = {},
listeners = {},
topElements = {},
ignoreProps = /^([A-Z]|returnValue$|layer[XY]$|keyLocation$)/,
specialEvents={ click: "MouseEvents", mousedown: "MouseEvents", mouseup: "MouseEvents", mousemove: "MouseEvents" };
function assert(type, selector, fn) {
if ($.type(type) != "string")
throw Error("Invalid type, expected a string");
if (!$.isFunction(fn))
throw Error("Invalid handler, expected a function");
if (selector !== undefined && $.type(selector) != "string")
throw Error("Invalid selector, expected a string");
}
function on(type, selector, fn) {
if ($.isFunction(selector))
fn = selector, selector = undefined;
xmlplus.debug && assert(type, selector, fn);
let uid = this.elem().xmlTarget.uid,
listener = this.api;
function handler(event) {
let e = createProxy(event, listener);
if (!selector)
return fn.apply(listener, [e].concat(event.data)), e;
listener.find(selector).forEach(function(item) {
if (item.contains(e.target))
fn.apply(item, [e].concat(event.data));
});
return e;
}
eventTable[uid] = eventTable[uid] || {};
eventTable[uid][type] = eventTable[uid][type] || [];
eventTable[uid][type].push({selector: selector, fn: fn, handler: handler});
let aid = this.env.aid;
listeners[aid] = listeners[aid] || {};
if (!listeners[aid][type]) {
listeners[aid][type] = type;
if (!topElements[aid])
topElements[aid] = topElement(this.elem());
topElements[aid].addEventListener(type, eventHandler)
}
return this;
}
function once(type, selector, fn) {
let realCallback;
if ($.isFunction(fn))
realCallback = fn, fn = callback;
else if ($.isFunction(selector))
realCallback = selector, selector = callback;
else
throw Error("Invalid handler, expected a function");
function callback(e) {
e.currentTarget.off(type, callback);
realCallback.apply(this, [].slice.call(arguments));
}
return this.api.on(type, selector, fn);
}
function off(type, selector, fn) {
let k, item = eventTable[this.elem().xmlTarget.uid] || {};
if (type === undefined) {
for (type in item)
off.call(this, type);
return this;
}
if (!item[type]) return this;
let buf = [].slice.call(item[type]);
if ($.isFunction(selector)) {
for (k in buf)
if (selector == buf[k].fn)
item[type].splice(item[type].indexOf(buf[k]), 1);
} else if (selector === undefined) {
item[type].splice(0);
} else if ($.isFunction(fn)) {
for (k in buf)
if (fn == buf[k].fn && selector == buf[k].selector)
item[type].splice(item[type].indexOf(buf[k]), 1);
} else { // typeof selector == "string" only
for (k in buf)
if (selector == buf[k].selector)
item[type].splice(item[type].indexOf(buf[k]), 1);
}
return this;
}
function trigger(type, data, bubble) {
let event = Event(type, true);
event.xmlTarget = Store[this.uid];
event.bubble_ = bubble == false ? false : true;
event.data = data == null ? [] : ($.isArray(data) ? data : [data]);
this.elem().dispatchEvent(event);
return this;
}
function remove(target) {
delete eventTable[target.uid];
}
function Event(type, bubble) {
let canBubble = !(bubble === false),
event = rdoc.createEvent(specialEvents[type] || "Events");
event.initEvent(type, canBubble, true);
return event;
}
function createProxy(event, listener) {
let proxy = { originalEvent: event };
for (let key in event)
if ( !ignoreProps.test(key) && event[key] !== undefined)
proxy[key] = event[key];
proxy.currentTarget = listener;
proxy.target = (event.xmlTarget || hp.create(event.target.xmlTarget)).api;
proxy.preventDefault = ()=> event.preventDefault();
proxy.stopImmediatePropagation = ()=> proxy.cancelImmediateBubble = true;
proxy.stopPropagation = ()=> proxy.cancelBubble = true;
return proxy;
}
function eventHandler(event) {
let target = event.target,
cancelBubble = false;
while (target && target.xmlTarget) {
let uid = target.xmlTarget.uid,
items =(eventTable[uid] || {})[event.type] || [];
for (let i = 0; i < items.length; i++) {
let e = items[i].handler(event);
if (e.cancelImmediateBubble) {
cancelBubble = true;
break;
}
e.cancelBubble && (cancelBubble = true);
}
if (cancelBubble || event.bubbles === false || event.bubble_ === false)
break;
target = target.parentNode;
}
}
function topElement(node) {
while (node) {
if ( node.nodeType == DOCUMENT_FRAGMENT_NODE || node.nodeType == DOCUMENT_NODE )
return node.lastChild;
node = node.parentNode;
}
}
return { on, once, off, trigger, remove };
}());
let CommonElementAPI = {
elem: function elem() {
return this.elem();
},
text: function (value) {
let elem = this.elem();
if (value === undefined)
return elem.textContent;
if ( elem.childNodes.length == 1 && elem.lastChild.nodeType ) {
this.node.data = elem.lastChild.data = value + "";
} else {
this.api.kids(0).call("remove");
this.api.append(vdoc.createTextNode(value + ""));
}
return this;
},
prop: function (name, value) {
if (value === undefined)
return this.elem()[name];
this.elem()[name] = value;
return this;
},
removeProp: function (name) {
delete this.elem()[name];
return this;
},
attr: function (name, value) {
let elem = this.elem();
if (value === undefined)
return elem.getAttribute(name);
elem.setAttribute(name, value + '');
return this;
},
removeAttr: function (name) {
this.elem().removeAttribute(name);
return this;
},
addClass: function (value, ctx) {
ctx = (ctx && Store[ctx.guid()] || this).env;
let elem = this.elem(),
klass = elem.getAttribute("class"),
input = (value + '').replace(/\$/g, ctx.aid + ctx.cid).split(/\s+/),
result = klass ? klass.split(/\s+/) : [];
for (let i = 0; i < input.length; i++)
if (result.indexOf(input[i]) < 0)
result.push(input[i]);
elem.setAttribute("class", result.join(' '));
return this;
},
removeClass: function (value, ctx) {
let elem = this.elem();
if (value === undefined)
return elem.setAttribute("class", ""), this;
ctx = (ctx && Store[ctx.guid()] || this).env;
let klass = elem.getAttribute("class"),
input = (value + '').replace(/\$/g, ctx.aid + ctx.cid).split(/\s+/),
result = klass ? klass.split(/\s+/) : [];
for (let k, i = 0; i < input.length; i++ ) {
k = result.indexOf(input[i]);
k >= 0 && result.splice(k, 1);
}
elem.setAttribute("class", result.join(" "));
return this;
},
hasClass: function(value) {
let elem = this.elem(),
env = this.env;
value = (value + '').replace(/\$/g, env.aid + env.cid);
if (value.length == 0)
return false;
return new RegExp(' ' + value + ' ').test(' ' + elem.getAttribute("class") + ' ');
},
contains: function (obj) {
if (!obj) return false;
let target = this.elem(),
elem = $.isSystemObject(obj) && obj.elem() || obj;
do {
if (elem == target) return true;
elem = elem.parentNode;
} while (elem);
return false;
},
append: function (target, options, parent) {
parent = parent || this.appendTo();
if ($.isSystemObject(target)) {
if (target.contains(this.api))
throw Error("Attempt to append a object which contains current node");
let src = Store[target.guid()],
srcEnv = src.env,
srcParent = src.elem().parentNode,
isTop = srcEnv.xml.lastChild == src.node;
parent.appendChild(src.elem());
this.node.appendChild(src.node);
Manager[src.typ].chenv(this.env, src);
if (isTop) {
srcEnv.xml.appendChild(vdoc.createElement("void"));
parseEnvXML(srcEnv, srcParent, srcEnv.xml.lastChild);
}
srcEnv.fdr.refresh();
this.env.fdr.refresh();
return target;
}
target = hp.parseToXML(target, this.env.dir);
if (target.nodeType == ELEMENT_NODE && $.isPlainObject(options)) {
target.getAttribute("id") || target.setAttribute("id", $.guid());
this.env.cfg[target.getAttribute("id")] = options;
}
this.node.appendChild(target);
parseEnvXML(this.env, parent, this.node.lastChild);
target.nodeType == ELEMENT_NODE && target.hasAttribute("id") && this.env.fdr.refresh();
return hp.create(Store[this.node.lastChild.uid]).api;
},
before: function (target, options, elem) {
if (this.node == this.env.xml.lastChild)
throw Error("insert before document node is not allowed");
elem = elem || this.elem();
if ($.isSystemObject(target)) {
if (target.contains(this.api))
throw Error("attempt to insert a object which contains current node");
let src = Store[target.guid()],
srcEnv = src.env,
srcParent = src.elem().parentNode,
isTop = srcEnv.xml.lastChild == src.node;
this.node.parentNode.insertBefore(src.node, this.node);
elem.parentNode.insertBefore(src.elem(), elem);
Manager[src.typ].chenv(this.env, src);
if (isTop) {
srcEnv.xml.appendChild(vdoc.createElement("void"));
parseEnvXML(srcEnv, srcParent, srcEnv.xml.lastChild);
}
srcEnv.fdr.refresh();
this.env.fdr.refresh();
return target;
}
target = hp.parseToXML(target, this.env.dir);
if ( target.nodeType == ELEMENT_NODE && $.isPlainObject(options) ) {
target.getAttribute("id") || target.setAttribute("id", $.guid());
this.env.cfg[target.getAttribute("id")] = options;
}
let newNode = this.node.parentNode.insertBefore(target, this.node);
parseEnvXML(this.env, elem, newNode);
elem.parentNode.insertBefore(elem.lastChild, elem);
target.nodeType == ELEMENT_NODE && target.hasAttribute("id") && this.env.fdr.refresh();
return hp.create(Store[this.node.previousSibling.uid]).api;
},
replace: function (target, options) {
this.api.trigger("$/before/remove");
let elem = this.elem();
if ($.isSystemObject(target)) {
if (target.contains(this.api))
throw Error("Attempt to replace a object which contains current node");
let src = Store[target.guid()],
srcEnv = src.env,
srcParent = src.elem().parentNode,
isTop = srcEnv.xml.lastChild == src.node;
elem.parentNode.replaceChild(src.elem(), elem);
this.node.parentNode.replaceChild(src.node, this.node);
this.node = src.node;
Manager[src.typ].chenv(this.env, src);
if (isTop) {
srcEnv.xml.appendChild(vdoc.createElement("void"));
parseEnvXML(srcEnv, srcParent, srcEnv.xml.lastChild);
}
srcEnv.fdr.refresh();
this.env.fdr.refresh();
Manager[this.typ].recycle(this);
return target;
}
target = hp.parseToXML(target, this.env.dir);
if (target.nodeType == ELEMENT_NODE && $.isPlainObject(options)) {
target.getAttribute("id") || target.setAttribute("id", $.guid());
this.env.cfg[target.getAttribute("id")] = options;
}
this.node.appendChild(target);
parseEnvXML(this.env, elem, target);
this.node.parentNode.replaceChild(target, this.node);
elem.parentNode.replaceChild(elem.lastChild, elem);
target.nodeType == ELEMENT_NODE && target.hasAttribute("id") && this.env.fdr.refresh();
Manager[this.typ].recycle(this);
return hp.create(Store[target.uid]).api;
},
remove: function () {
this.api.trigger("$/before/remove");
if (this.env.xml.lastChild == this.node) {
this.api.replace("void");
} else {
let elem = this.elem();
elem.parentNode.removeChild(elem);
this.node.parentNode.removeChild(this.node);
this.node.nodeType == ELEMENT_NODE && this.node.hasAttribute("id") && this.env.fdr.refresh();
Manager[this.typ].recycle(this);
}
},
find: function (expr) {
return this.env.fdr.sys(expr, this.api);
},
get: function (index, nodeType) {
nodeType = nodeType || ELEMENT_NODE;
let next = this.node.firstChild,
count = -1;
while (next) {
if (next.nodeType == nodeType && ++count == index)
break;
next = next.nextSibling;
}
return next && hp.create(Store[next.uid]).api;
},
first: function (nodeType) {
nodeType = nodeType || ELEMENT_NODE;
let next = this.node.firstChild;
while (next && next.nodeType != nodeType)
next = next.nextSibling;
return next && hp.create(Store[next.uid]).api;
},
last: function (nodeType) {
nodeType = nodeType || ELEMENT_NODE;
let prev = this.node.lastChild;
while (prev && prev.nodeType != nodeType)
prev = prev.previousSibling;
return prev && hp.create(Store[prev.uid]).api;
},
prev: function (nodeType) {
nodeType = nodeType || ELEMENT_NODE;
let prev = this.node.previousSibling;
while (prev) {
if (prev.nodeType == nodeType)
return hp.create(Store[prev.uid]).api;
prev = prev.previousSibling;
}
},
next: function (nodeType) {
nodeType = nodeType || ELEMENT_NODE;
let next = this.node.nextSibling;
while (next) {
if (next.nodeType == nodeType)
return hp.create(Store[next.uid]).api;
next = next.nextSibling;
}
},
kids: function (nodeType) {
if (nodeType == undefined)
nodeType = ELEMENT_NODE;
let result = new Collection,
next = this.node.firstChild;
while (next) {
if (next.nodeType == nodeType || nodeType == 0)
result.push(hp.create(Store[next.uid]).api);
next = next.nextSibling;
}
return result;
},
val: function () {
return this.value;
},
localName: function () {
return this.node.localName;
},
namespace: function () {
let uri = this.node.namespaceURI;
return isHTML[this.node.nodeName] ? uri : "//" + ph.fullPath(this.env.dir, uri || '');
},
guid: function () {
return this.node.uid;
},
toString: function () {
return this.node.getAttribute("id") || this.node.uid;
},
serialize: function (serializeXML) {
let elem = serializeXML ? this.node : this.elem(),
prev = elem.previousSibling;
if (prev && prev.nodeType == DOCUMENT_TYPE_NODE)
elem = elem.ownerDocument;
return $.serialize(elem);
},
bind: function (value) {
let proxy;
if (bd.isLiteral(value)) {
if (this.fdr)
throw Error("A Literal value can only be bound to a htmltag");
proxy = bd.bindLiteral(this, value);
proxy.value = value;
} else if ($.isPlainObject(value)) {
if (!this.fdr)
throw Error("A PlainObject is not allowed to bind a htmltag");
proxy = bd.bindObject(this, value);
let props = Object.getOwnPropertyNames(value);
props.forEach(key => proxy[key] = value[key]);
} else if ($.isArray(value)) {
if (this.env.xml.lastChild == this.node)
throw Error("Document nodes are not allowed to bind arrays");
proxy = bd.bindArray(this, value);
for (let i in value)
proxy[i] = value[i];
while (proxy.length > value.length)
proxy.pop(0);
} else {
throw Error("Invalid value, expected a literal, a plainObject, or an array");
}
return proxy;
}
};
let ClientElementAPI = {
css: function (name, value) {
let elem = this.elem();
if (value == undefined) {
name = (name + '').replace(/([A-Z])/g, "-$1").toLowerCase();
return elem.style[name] || getComputedStyle(elem, "").getPropertyValue(name);
}
typeof value == "number" && (value += '');
elem.style[name] = value;
return this;
},
show: function () {
let elem = this.elem(),
style = elem.style;
style.display == "none" && (style.display = "")
if (getComputedStyle(elem, "").display == "none")
style.display = hp.defDisplay(elem.nodeName);
return this;
},
hide: function () {
this.elem().style.display = "none";
return this;
},
width: function (value) {
let elem = this.elem();
if (value === undefined)
return elem.getBoundingClientRect().width;
elem.style.width = parseFloat(value) + "px";
return this;
},
outerWidth: function (includeMargins) {
let elem = this.elem();
if (includeMargins) {
let styles = getComputedStyle(elem, null);
return elem.offsetW