js 多功能高兼容的动态创建DOM元素方法
简述:
动态创建一个DOM元素,可为元素添加标签属性、内置属性、事件(可修饰)、多个子元素,且具有类似Vue的函数生命周期。
兼容性:
Chrome, Firefox, Opera, Safari, Edge, IE :全兼容
语法:
newElement(option)
参数:
option
{Object|String}
: 元素配置 (字符串类型时支持标签选择器表达式以及标签形式的HTML代码)
option:String:HtmlTag
标签形式的HTML代码:
<tag attr1 attr2="value">
HtmlValue
</tag>
<tag />
或<tag>
option:String:HtmlExpression
标签选择器表达式:
tagName
#id
.className
[attrName="attrValue"]
{HtmlValue}
- 开头为标签名称,后接标签属性。
以#
开头为ID名,以.
开头为类名(可嵌入多个),以[...]
形式作为自定义属性,以{...}
形式作为innerHTML
内容
option:Object
对象类型参数规则:
tag
{String}
: 元素标签 (默认为"div")id
{String}
: 元素ID名class
{String|Array}
: 元素类名 (字符串每个类名以空格隔开)style
{String|Object}
: 元素样式 (字符串每个样式时以分号隔开)attr
{Object}
: 元素标签属性 (值为字符串类型; option.id 会覆盖 option.attr.id, option.class 会与 option.attr.class 合并)prop
{Object}
: 元素内置属性event
{Object}
: 元素事件 (值为函数类型; 函数作用域为当前产生的元素, 参数为当前事件的Event实例; 键的绑定事件名以.xxx
后缀可作为事件修饰符)html
{String|Number|Object|Array|Function}
: 元素HTML内容或子元素 (字符串类型时不支持标签选择器表达式; 函数类型时作用域为当前产生元素的配置, 参数为当前产生元素的父元素, 当返回的参数符合配置规则时生成该元素, 返回参数为字符串类型时支持标签选择器表达式)beforeCreate
{Function}
: 创建元素前的操作 (作用域为输入源配置)created
{Function}
: 创建元素后的操作 (作用域为输入源配置, 参数为创建的元素)beforeBind
{Function}
: 绑定数据前的操作 (作用域为已转化的配置, 参数为创建的元素)binded
{Function}
: 绑定数据后的操作 (作用域为已转化的配置, 参数为创建的元素)
内置事件修饰符
stop
: 阻止冒泡prevent
: 阻止默认事件capture
: 阻止捕获self
: 只监听触发该元素的事件once
: 只触发一次ctrl
: 需要按下Ctrl键shift
: 需要按下Shift键alt
: 需要按下Alt键meta
: 需要按下Win键或Command键left
: 左键事件right
: 右键事件middle
: 中间滚轮事件
函数:
/** 动态创建DOM元素
*
* @brief 可为元素添加标签属性、内置属性、事件(可修饰)、多个子元素,且建立有类似Vue的函数生命周期。
* @param {Object|String} option 元素配置 (字符串类型时支持标签选择器表达式以及标签形式的HTML代码)
* @return {Object|Null} 返回生成的元素
*/
function newElement (option) {
var REG = {
TAG: "^\\s*<([A-Za-z:_][\\w\\.:-]*)\\s*(([^<>/]*(=('.*'|\".*\"|`.*`|.*?))?\\s*)*?)\\s*(>(.*)<\\/([A-Za-z:_][\\w\\.:-]*)|\\/)?>\\s*$",
TAG_ATTR: "[^\\s]+=('.*?'|\".*?\"|`.*?`|[^\\s]*)",
TAG_ATTR_INVAL: "^([^\\s='\"`]+)=(.*)$",
TAG_ATTR_NOVAL: "[^\\s]+",
EXP_CHECK: "^[A-Za-z:_].*",
EXP_OPTION: "(\\{.*?\\}|\\[.*?\\]|#[^<>/\\s\\.\\[\\]\\{\\}]+|\\.[^<>/\\s\\.\\[\\]\\{\\}]+)",
EXP_TAG: "^([A-Za-z:_][\\w:-]*).*",
EXP_ID: "^#.+",
EXP_CLASS: "^\\..+",
EXP_ATTR: "^\\[.+?\\]$",
EXP_CONTENT: "^\\{.*?\\}$",
EVENT_SPLIT: "^([^\\.]+)(\\..+)*",
EVENT_FIND: "\\.[^\\. ]+"
};
var newReg = function (flags, symbol, symbol_spare) {
var reg;
try { reg = RegExp(flags); }
catch (err) { reg = RegExp(); symbol = null; }
if (symbol) {
try { reg = RegExp(flags, symbol); }
catch (err) {
if (symbol_spare) {
try { reg = RegExp(flags, symbol_spare); } catch (err) {}
}
}
}
return reg;
};
var isElement = function (val) {
var flag = false;
if (typeof val == "object" && val != null) {
if (typeof HTMLElement !== "undefined") flag = val instanceof HTMLElement;
else if (typeof Element !== "undefined") flag = val instanceof Element;
else if (val.nodeType === 1) flag = true;
}
return flag;
};
var isArray = function (val) {
return Object.prototype.toString.call(val) === "[object Array]";
};
var includes = function (arr, val) {
if (isArray(arr) && arr.length > 0) {
for (var i = 0; i < arr.length; i++) {
if (arr[i] === val) return true;
}
}
};
var objKeys = function (val) {
var keys = [];
if (val === Object(val)) {
for (var prop in val) {
if (Object.prototype.hasOwnProperty.call(val, prop)) keys.push(prop);
}
}
return keys;
};
var forEach = function (val, fn) {
if (typeof fn === "function" && typeof val === "object" && val != null && val.length > 0) {
for (var i = 0; i < val.length; i++) fn(val[i], i, val);
}
};
var addEvent = function (el, type, fn, capture) {
if (el.addEventListener) {
el.addEventListener(type, fn, capture);
} else if (el.attachEvent) {
el.attachEvent("on" + type, fn);
}
};
var removeEvent = function (el, type, fn, capture) {
if (el.removeEventListener) {
el.removeEventListener(type, fn, capture);
} else {
el.detachEvent("on" + type, fn);
}
};
var cleanDelimit = function (str) {
if (str) {
var str_L = str.substr(0, 1), str_R = str.substr(str.length - 1);
if ((str_L == "'" || str_L == '"' || str_L == "`") && str_L == str_R) {
return str.slice(1, str.length - 1);
} else return str;
} else return "";
};
var checkOption = function (option) {
if (typeof option == "object" && option != null) {
return option;
} else if (typeof option == "string") {
var otag = option.match(newReg(REG.TAG, "s"));
if (otag && otag[1] && (otag[1] === otag[8] || otag[8] == void 0 || otag[8] == "")) {
var oattr = {};
if (otag[2]) {
var oattr_tmp1 = otag[2].match(newReg(REG.TAG_ATTR, "gs", "g"));
if (oattr_tmp1) forEach(oattr_tmp1, function (av) {
var oattr_tmp2 = av.match(newReg(REG.TAG_ATTR_INVAL, "s"));
if (oattr_tmp2) {
oattr[oattr_tmp2[1]] = cleanDelimit(oattr_tmp2[2]);
otag[2] = otag[2].replace(av, "");
}
});
var oattr_tmp3 = otag[2].match(newReg(REG.TAG_ATTR_NOVAL, "g"));
if (oattr_tmp3) forEach(oattr_tmp3, function (av) {
if (!oattr.hasOwnProperty(av)) oattr[av] = "";
});
}
return { tag: otag[1], attr: oattr, html: otag[7] || null };
} else if (newReg(REG.EXP_CHECK).test(option)) {
var ooption = { tag: option.replace(newReg(REG.EXP_TAG, "s"), "$1"), id: "", "class": "", html: "", attr: {} },
oexp = option.match(newReg(REG.EXP_OPTION, "gs", "g"));
if (oexp) forEach(oexp, function (v) {
if (newReg(REG.EXP_ID).test(v)) {
var oid = v.replace(/^#+/, "");
if (oid) ooption.id = oid;
} else if (newReg(REG.EXP_CLASS).test(v)) {
var oclass = v.replace(/^\.+/, "");
if (oclass) ooption["class"] += (ooption["class"] ? " " : "") + oclass;
} else if (newReg(REG.EXP_ATTR, "s").test(v)) {
v = v.slice(1, v.length - 1);
var attrSymIndex = v.indexOf("=");
if (attrSymIndex == -1) {
ooption.attr[v] = "";
} else {
var attrName = v.slice(0, attrSymIndex),
attrValue = v.slice(attrSymIndex + 1);
ooption.attr[attrName] = cleanDelimit(attrValue);
}
} else if (newReg(REG.EXP_CONTENT, "s").test(v)) {
ooption.html += v.slice(1, v.length - 1);
}
});
return ooption;
}
}
};
var createElement = function (option) {
if (!option) return null;
// 创建元素前的操作
if (typeof option.beforeCreate == "function") {
option.beforeCreate.call(option);
}
// 创建元素
option.tag = option.tag || "div";
var dom = document.createElement(option.tag);
// 创建元素后的操作
if (typeof option.created == "function") {
option.created.call(option, dom);
}
// 初始化数据
var className = "",
style = "",
ioption = {
tag: option.tag,
attr: option.attr && typeof option.attr == "object" ? option.attr : {},
prop: option.prop && typeof option.prop == "object" ? option.prop : {},
event: option.event && typeof option.event == "object" ? option.event : {},
html: option.html || null
};
// 设置ID
if (option.id) ioption.attr.id = option.id;
// 设置类名
if (ioption.attr["class"]) className = ioption.attr["class"];
if (isArray(option["class"]) && option["class"].join("")) {
className += (className ? " " : "") + option["class"].join(" ");
} else if (typeof option["class"] == "string" && option["class"]) {
className += (className ? " " : "") + option["class"];
}
if (className) ioption.attr["class"] = className;
// 设置样式
if (ioption.attr.style) style = ioption.attr.style;
if (typeof option.style == "object" && option.style && objKeys(option.style).length > 0) {
style += (style ? ";" : "");
forEach(objKeys(option.style), function (k) {
style += k + ":" + option.style[k] + ";";
});
} else if (typeof option.style == "string" && option.style) {
style += (style ? ";" : "") + option.style;
}
if (style) ioption.attr.style = style;
// 绑定数据前的操作
if (typeof option.beforeBind == "function") {
option.beforeBind.call(ioption, dom);
}
// 设置属性
forEach(objKeys(ioption.attr), function (key) {
var ikey = key.toLowerCase();
if (ikey == "id") dom.id = ioption.attr[key];
else if (ikey == "class") dom.className = ioption.attr[key];
else if (ikey == "style") dom.style.cssText = ioption.attr[key];
else {
try { dom.setAttribute(key, ioption.attr[key]); } catch (error) {}
}
});
// 设置内置属性
forEach(objKeys(ioption.prop), function (key) {
dom[key] = ioption.prop[key];
});
// 设置事件
forEach(objKeys(ioption.event), function (key) {
forEach(key.split(" "), function (k) {
var match = k.match(newReg(REG.EVENT_SPLIT));
if (match && match[1]) {
var modmat = match[2] ? match[2].match(newReg(REG.EVENT_FIND, "g")) : null;
var fn = function (event) {
event = event || window.event;
if (modmat && modmat.length > 0) {
if (includes(modmat, ".prevent")) event.preventDefault();
if (includes(modmat, ".stop")) event.stopPropagation();
if (includes(modmat, ".self")) { if (event.target !== event.currentTarget) return; }
if (includes(modmat, ".ctrl")) { if (!event.ctrlKey) return; }
if (includes(modmat, ".shift")) { if (!event.shiftKey) return; }
if (includes(modmat, ".alt")) { if (!event.altKey) return; }
if (includes(modmat, ".meta")) { if (!event.metaKey) return; }
if (includes(modmat, ".left")) { if ("button" in event && event.button !== 0) return; }
if (includes(modmat, ".middle")) { if ("button" in event && event.button !== 1) return; }
if (includes(modmat, ".right")) { if ("button" in event && event.button !== 2) return; }
if (includes(modmat, ".once")) removeEvent(dom, match[1], fn);
}
if (typeof ioption.event[key] == "function") ioption.event[key].call(dom, event);
};
addEvent(dom, match[1], fn, modmat ? includes(modmat, ".capture") : false);
}
});
});
// 设置HTML内容和子元素
var htmlAdd = function (html) {
if (typeof html == "string" || typeof html == "number") {
dom.innerHTML += html;
} else if (typeof html == "object" && html != null) {
if (isArray(html)) {
forEach(html, function (shtml) {
htmlAdd(shtml);
});
} else if (isElement(html)) {
dom.appendChild(html);
} else {
dom.appendChild(createElement(html));
}
} else if (typeof html == "function") {
var fnres = html.call(ioption, dom);
if (fnres) {
var domsun = createElement(checkOption(fnres));
if (domsun) dom.appendChild(domsun);
}
}
};
htmlAdd(ioption.html);
// 绑定数据后的操作
if (typeof option.binded == "function") {
option.binded.call(ioption, dom);
}
return dom;
};
return createElement(checkOption(option));
}
用法示例:
// 对象参数用法示例:
newElement({
tag: "div",
id: "demo-1",
class: "demo1 demo2" || ["demo3", "demo4"],
style: "width: 20em; height: 10em; background: blue;" || { width: "100px", height: "200px", background: "yellow" },
attr: {
id: "demo_attr_id", // 被上层的 id 覆盖
class: "demo_attr_class", // 与上层的 class 合并且此类名前置
"data-demo": "attr demo"
},
prop: {
dataProp: 123
},
event: {
click: function () { console.log("clicked"); },
"mouseover.self": function () { console.log("mouseovered"); }
},
html: [
"111 <br>",
[222, "<p>333</p>"],
{
tag: "p",
style: "color: red",
html: ["p: ", function () { return "span.span-son[color='green']{span-son}" }],
event: {
"click.stop": function () { console.log("p clicked"); }
}
}
],
beforeCreate: function () { console.log("创建元素前, 作用域为输入源配置", this); },
created: function (dom) { console.log("创建元素后, 作用域为输入源配置, 参数为创建的元素", this, dom); },
beforeBind: function (dom) { console.log("绑定数据前, 作用域为已格式化的配置, 参数为创建的元素", this, dom); },
binded: function (dom) { console.log("绑定数据后, 作用域为已格式化的配置, 参数为创建的元素", this, dom); }
})
// 标签选择器表达式参数用法示例:
newElement("h1#demo-2.demoA.demoB[align='center']{Demo h1}")
// 标签形式的HTML代码参数用法示例:
newElement("<h2 style='color: blue;'>Demo h2</h2>")