js 多功能高兼容的动态创建DOM元素方法 (含标签属性、内置属性、可修饰事件、多子元素、函数生命周期)

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>")
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值