adajs
Version:
Integrated Web Framework
788 lines (713 loc) • 16.8 kB
JavaScript
const SINGLETAG = ["br", "hr", "img", "input", "param", "link", "meta", "area", "base", "basefont", "param", "col", "frame", "embed", "keygen", "source"];
const ISHTMLREG = /^\s*<(\w+|!)[^>]*>/;
const REGS = {
k: /\r/g,
l: /\n/g,
a: /</g,
b: />/g,
d: /<%|%>/g,
e: /^=.*;$/,
i: /\r\n/g,
f: />[\s]+</g,
g: /<%[\s\S]*%>/,
h: /\<\!\-\-[\s\S]*?\-\-\>/g,
startdot: /</g,
enddot: />/g,
isDoctype: /\<\!DOCTYPE[\s\S]*?\>/g,
isNote: /\<\!\-\-[\s\S]*?\-\-\>/g,
isXmlTag: /\<\?[\s\S]*?\?\>/g,
propcode: /\@\[\[[0-9]+\]\]\@/g,
eventcode: /\$\[\[[0-9]+\]\]\$/g,
assigncode: /\(\(\[[0-9]+\]\)\)/g,
expresscode: /\[\[[0-9]+\]\]/g,
nodecode: /\{\{node\}\}/g,
prop: /[0-9a-z]+((\.)([0-9a-z_]+))+/g,
customA: /<[\s\S]+?>/g,
customB: /<[0-9a-z_-]+?>/,
customC: /<[0-9a-z_-]+ .*?>/,
customD: /<\/[0-9a-z_-]+?>/
};
const util = {
parseStyle(text = "") {
let r = {};
text.split(";").forEach(item => {
let a = item.split(":");
r[a[0]] = a[1];
});
return r;
},
stringifyStyle(info) {
return Reflect.ownKeys(info).filter(key => !!key).map(key => {
return `${key}:${info[key]}`;
}).join(";");
}
};
class ClassList {
constructor(node) {
this._list = [];
this._node = node;
}
add(className) {
if (this._list.indexOf(className) === -1) {
this._list.push(className);
}
this._node._props.class = this._list.join(" ");
}
contains(className) {
return this._list.contains(className) !== -1;
}
remove(className) {
let index = this._list.indexOf(className);
if (index !== -1) {
this._list.splice(index, 1);
}
this._node._props.class = this._list.join(" ");
}
toggle(className) {
let index = this._list.indexOf(className);
if (index === -1) {
this._list.push(className);
} else {
this._list.splice(index, 1);
}
this._node._props.class = this._list.join(" ");
}
}
class TextNode {
constructor(content, parent = null) {
this._content = content;
this._parentNode = parent;
}
get parentNode() {
return this._parentNode;
}
get nodeType() {
return 3;
}
get textContent() {
return this._content;
}
}
class NodeEvent {
constructor(type) {
this._type = type;
this._goon = true;
this._target = null;
this._currentTarget = null;
}
get type() {
return this._type;
}
get target() {
return this._target;
}
set target(target) {
this._target = target;
}
get currentTarget() {
return this._currentTarget;
}
set currentTarget(target) {
this._currentTarget = target;
}
initEvent() {
}
initMouseEvent() {
}
initUIEvents() {
}
initMutationEvent() {
}
preventDefault() {
}
stopPropagation() {
this._goon = false;
}
}
class Node {
constructor(tagName, parent = null) {
this._tagName = tagName;
this._props = {};
this._children = [];
this._classlist = new ClassList(this);
this._parentNode = parent;
this._dataset = new Proxy({}, {
set: (target, prop, value) => {
target[prop] = value;
let _prop = "data-" + prop.replace(/[A-Z]+/g, str => `-${str[0].toLowerCase()}${str.substring(1)}`);
this._props[_prop] = value;
return true;
},
});
this._listener = {};
this._style = new Proxy({}, {
set: (target, prop, value) => {
if (prop === 'cssText') {
this._props.style = value;
} else {
let info = util.parseStyle(this._props.style);
info[prop] = value;
this._props.style = util.stringifyStyle(info);
}
return true;
},
get: (target, prop) => {
if (prop === 'cssText') {
return this._props.style || 0;
} else {
return util.parseStyle(this._props.style)[prop];
}
}
});
}
get readyState() {
return "complete";
}
get nodeType() {
return 1;
}
get parentNode() {
return this._parentNode;
}
set parentNode(node) {
this._parentNode = node;
}
get classList() {
return this._classlist;
}
get tagName() {
return this._tagName;
}
set tagName(tagName) {
this._tagName = tagName;
}
get childNodes() {
return this._children;
}
get children() {
return this._children;
}
get className() {
return this._props["class"];
}
get innerText() {
let textNodes = [];
Query.travel(this, node => {
if (node.nodeType !== 1) {
textNodes.push(node);
}
});
return textNodes.map(ele => ele.textContent).join("\n");
}
get outerText() {
return this.innerText;
}
get nodeName() {
return this._tagName.toUpperCase();
}
get dataset() {
return this._dataset;
}
get attributes() {
return Reflect.ownKeys(this._props);
}
get innerHTML() {
let str = "";
let html = (node) => {
let result = "", issingletag = SINGLETAG.indexOf(node.tagName) !== -1;
if (node.nodeType !== 1) {
result = node._content || "";
} else {
let _props = [];
if (node._props) {
Reflect.ownKeys(node._props).forEach(function (prop, i) {
if (node._props[prop]) {
_props.push(prop + "=\"" + node._props[prop] + "\"");
}
});
}
if (issingletag) {
result = "<" + node.tagName + (_props.length > 0 ? " " : "") + _props.join(" ") + "/>";
} else {
let _content = [];
node._children.forEach(function (child) {
if (child) {
_content.push(html(child));
}
});
result = "<" + node.tagName + (_props.length > 0 ? " " : "") + _props.join(" ") + ">" + _content.join("") + "</" + node.tagName + ">";
}
}
return result;
};
this._children.forEach(function (node) {
str += html(node);
});
return str;
}
get style() {
return this._style;
}
set innerHTML(content) {
this._children.forEach(el => {
if (el.nodeType === 1) {
el.dispatchEvent(el.createEvent("DOMNodeRemoved"));
}
});
this._children = [];
if (ISHTMLREG.test(content)) {
Parser.parseNode(content).forEach(e => this.appendChild(e));
} else {
this.appendChild(new TextNode(content));
}
}
get outerHTML() {
let result = "", issingletag = SINGLETAG.indexOf(this.tagName) !== -1;
let _props = [];
if (this._props) {
Reflect.ownKeys(this._props).forEach((prop, i) => {
if (this._props[prop]) {
_props.push(prop + "=\"" + this._props[prop] + "\"");
}
});
}
if (issingletag) {
result = "<" + this.tagName + " " + _props.join(" ") + "/>";
} else {
result = "<" + this.tagName + " " + _props.join(" ") + ">" + this.innerHTML + "</" + this.tagName + ">";
}
return result;
}
set outerHTML(content) {
let node = Parser.parseNode(content)[0];
if (this.parentNode) {
let index = this.parentNode._children.indexOf(this);
node._parentNode = this.parentNode;
this.parentNode._children[index] = node;
}
}
createElement(tag) {
return new Node(tag);
}
createElementNS(name, tag) {
let t = new Node(tag);
t.setAttribute("xmlns", name);
return t;
}
createTextNode(content) {
return new TextNode(content);
}
createDocumentFragment() {
return new NodeFragment();
}
getAttribute(propName) {
return this._props[propName];
}
setAttribute(propName, value) {
this._props[propName] = value;
if (propName === "class") {
value.split(" ").forEach(name => this.classList.add(name));
}
if (propName.indexOf("data-") === 0) {
let e = propName.substring(5).replace(/-([0-9a-zA-Z])/g, (a, b) => b.toUpperCase());
this.dataset[e] = value;
}
}
getAttributeNS(ns, key) {
let name = ns.split("/").pop();
return this._props[`${name}:${key}`];
}
setAttributeNS(ns, key, value) {
if (ns) {
let name = ns.split("/").pop();
this._props[`${name}:${key}`] = value;
} else {
this._props[`${key}`] = value;
}
}
removeAttributeNS(ns, key) {
let name = ns.split("/").pop();
delete this._props[`${name}:${key}`];
}
removeAttribute(key) {
delete this._props[key];
}
hasAttribute(attrName) {
return Object.hasOwnProperty(attrName);
}
querySelectorAll(selector) {
return Query.all(selector, this);
}
querySelector(selector) {
return this.querySelectorAll(selector)[0];
}
getElementsByClassName(className) {
return Query.className(className, this);
}
getElementsByTagName(tagName) {
return Query.tag(tagName, this);
}
getElementById(id) {
return Query.id(`#${id}`, this)[0];
}
hasChildNodes() {
return this._children.length > 0;
}
contains(node) {
let r = false;
Query.travel(this, el => {
if (el === node) {
r = true;
}
});
return r;
}
appendChild(node) {
if (node instanceof NodeFragment) {
node.childNodes.forEach(el => {
el.parentNode = this;
this._children.push(el);
});
} else {
if (node._parentNode) {
let index = node._parentNode._children.indexOf(node);
if (index !== -1) {
node._parentNode._children.splice(index, 1);
}
node._parentNode = null;
}
node._parentNode = this;
this._children.push(node);
}
}
removeChild(node) {
let index = this.childNodes.indexOf(node);
if (index !== -1) {
let _removed = this.childNodes[index];
_removed.dispatchEvent(_removed.createEvent("DOMNodeRemoved"));
this._children.splice(index, 1);
}
}
replaceChild(newnode, oldnode) {
let index = this.childNodes.indexOf(oldnode);
if (index !== -1) {
newnode._parentNode = this;
this._children[index] = newnode;
}
}
createEvent(type) {
return new NodeEvent(type);
}
addEventListener(type, fn) {
if (!this._listener[type]) {
this._listener[type] = [];
}
this._listener[type].push(fn);
}
removeEventListener(type, fn) {
if (this._listener[type]) {
let index = this._listener[type].indexOf(fn);
if (index !== -1) {
this._listener[type].splice(index, 1);
}
}
}
dispatchEvent(event) {
event.target = this;
let i = this;
while (i) {
i._triggerEvent(event);
if (event._goon) {
i = i.parentNode;
} else {
break;
}
}
}
createEvent(type) {
return new NodeEvent(type);
}
_triggerEvent(event) {
event.currentTarget = this;
let type = event.type;
if (this._listener[type]) {
this._listener[type].forEach(fn => fn(event));
}
}
}
class NodeFragment extends Node {
}
const Parser = {
preparse(str = "") {
if (str) {
str = str.replace(REGS.isNote, "")
.replace(REGS.isDoctype, "")
.replace(REGS.isXmlTag, "")
.replace(REGS.h, "")
.replace(REGS.f, "><");
SINGLETAG.forEach((tag) => {
let reg = new RegExp(`<${tag} .*?>`, "g");
str = str.replace(reg, function (a) {
return a.substring(0, a.length - 1) + "/>";
});
});
}
return str;
},
parseNode(str = "") {
str = this.preparse(str);
if (str && str !== "") {
let stacks = [],
nodes = [],
current = null;
let tagname = "",
tagendname = "",
propname = "",
value = "",
text = "";
let tagnamestart = false,
propstart = false,
valuestart = false,
tagendstart = false,
element = false;
for (let i = 0, len = str.length; i < len; i++) {
let a = str[i];
if (a !== "\r" && a !== "\n") {
if (a === "<") {
element = true;
if (text.trim() !== "") {
current = new TextNode(text.trim() || "", stacks[stacks.length - 1] || null);
if (stacks[stacks.length - 1]) {
stacks[stacks.length - 1].appendChild(current);
} else {
nodes.push(current);
}
text = "";
}
if (str[i + 1] && str[i + 1] === "/") {
tagendstart = true;
} else {
current = new Node("", null);
current.hasProp = false;
stacks.push(current);
if (stacks.length - 2 >= 0) {
stacks[stacks.length - 2].appendChild(current);
current.parentNode = stacks[stacks.length - 2];
}
tagnamestart = true;
}
continue;
} else if (a === " ") {
if (element) {
if (tagnamestart) {
tagnamestart = false;
current.tagName = tagname.trim();
tagname = "";
}
if (!propstart && !valuestart) {
propstart = true;
continue;
}
} else {
text += a;
}
} else if (a === "=") {
element && (propstart = false);
} else if (a === "'" || a === "\"") {
if (!valuestart && element) {
valuestart = a;
continue;
} else {
if (valuestart === a) {
valuestart = false, current.hasProp = true;
current.setAttribute(propname.trim(), value.trim());
propname = "", value = "";
}
}
} else if (a === ">") {
element = false, propstart = false, valuestart = false, tagnamestart = false;
if (tagendstart) {
tagendstart = false, tagendname = "";
stacks.length === 1 && nodes.push(stacks[0]);
stacks.pop();
}
if (!current.hasProp) {
current.tagName === "" && (current.tagName = tagname.trim());
tagname = "";
}
!element && text && (text += a);
continue;
} else if (a === "/") {
if (str[i + 1] && str[i + 1] === ">") {
element = false, valuestart = false, propstart = false, tagendstart = false, tagnamestart = false, tagendname = "";
if (stacks.length === 1) {
nodes.push(stacks[0]);
}
if (!current.hasProp) {
current.tagName === "" && (current.tagName = tagname.trim());
tagname = "";
}
stacks.pop();
} else {
if (!element) {
text += a;
} else {
valuestart && (value += a);
}
}
continue;
}
tagnamestart && (tagname += a);
propstart && (propname += a);
valuestart && (value += a);
tagendstart && (tagendname += a);
!element && (text += a);
} else {
if (!element) {
text += a;
} else {
valuestart && (value += a);
}
}
}
if (text) {
nodes.push(new TextNode(text, null));
}
return nodes;
} else {
return [];
}
}
};
const Query = {
travel(node, fn) {
if (node.nodeType === 1) {
node.childNodes.forEach(el => {
fn(el);
this.scan(el, fn);
});
} else {
fn(node);
}
},
scan(node, fn) {
if (node.nodeType === 1) {
node.childNodes.forEach(el => {
if (el.nodeType === 1) {
fn(el);
this.scan(el, fn);
}
});
}
},
all(selector, node) {
let result = [];
selector.split(" ").forEach((sel, index) => {
if (index === 0) {
if (sel.indexOf("#") !== -1) {
result = this.id(sel, node);
} else if (sel.indexOf(".") !== -1) {
result = this.className(sel, node);
} else if (sel.indexOf("[") !== -1) {
result = this.prop(sel, node);
} else {
result = this.tag(sel, node);
}
} else {
if (result.length !== 0) {
let _result = [];
if (sel.indexOf("#") !== -1) {
result.forEach(node => {
_result = _result.concat(this.id(sel, node));
});
} else if (sel.indexOf(".") !== -1) {
result.forEach(node => {
_result = _result.concat(this.className(sel, node));
});
} else if (sel.indexOf("[") !== -1) {
result.forEach(node => {
_result = _result.concat(this.prop(sel, node));
});
} else {
result.forEach(node => {
_result = _result.concat(this.tag(sel, node));
});
}
result = _result;
}
}
});
return result;
},
tag(tagName, node) {
let els = [];
this.scan(node, el => {
if (el.tagName === tagName) {
els.push(el);
}
});
return els;
},
id(idSelector, node) {
let id = idSelector.substring(1);
let els = [];
this.scan(node, el => {
if (el._props.id && el._props.id === id) {
els.push(el);
}
});
return els;
},
className(classSelector, node) {
let cls = classSelector.split(".");
cls.shift();
let els = [];
this.scan(node, el => {
let a = cls.find(c => el.classList._list.indexOf(c) === -1);
if (!a) {
els.push(el);
}
});
return els;
},
prop(propSelector, node) {
let info = propSelector.match(/([a-zA-Z\*]*)\[([a-zA-Z-]+)([\~\=\^\*\$\|]*)([\s\S]*)\]/);
let els = [];
if (info) {
let tagName = info[1], propName = info[2], operator = info[3],
value = info[4].substring(1, info[4].length - 1);
this.scan(node, el => {
let target = true;
if (tagName !== "" && tagName !== "*") {
target = el.tagName === tagName;
}
if (target && operator) {
let _val = el._props[propName] || "";
if (operator === "=") {
target = _val === value;
}
if (operator === "~=") {
target = _val.split(" ").indexOf(value) !== -1;
}
if (operator === "|=") {
target = _val.substring(0, value.length + 1) === `${value}_`;
}
if (operator === "^=") {
target = _val.startsWith(value);
}
if (operator === "$=") {
target = _val.endsWith(value);
}
if (operator === "*=") {
target = _val.indexOf(value) !== -1;
}
}
if (target) {
els.push(el);
}
});
}
return els;
}
};
module.exports = Node;