最近学长布置的任务,要求在页面中可以主动的、动态的进行一张表单的创建,因为还涉及到 form 表单内各种 id 不能冲突的问题,所以还是废了点时间的,而且国内 baidu 搜关键词也没怎么检索到很符合需求的答案,所以准备仔细的梳理一下。当然不只是表单,准备的说是在页面上动态创建节点,所以可借鉴性还是挺大的,等我忘了这个方法就回来翻博客也挺好

同时,虽然只占很小的一部分但是最好还是不公开学长的源码,所以这里用的是我测试 “动态创建节点” 和 “变换 id 以避免 id 冲突” 时用的代码,加上补充时使用部分使用到项目中的非完全的 js 代码

此次虽然只有 "动态创建节点" 和 "避免 id 冲突" 两个大题,其实也把 "如何进行节点定位" 嵌进两个内容里面了,毕竟结合实例更好理解,我日后复习能复习的清楚点 orz

# 1. 动态创建节点

前端源码参照了 51CTO,如果不涉及有关 "子节点的相关操作" 的话,其实看这个也够了,但是如果需要对子节点操作,比如 id 冲突、特定子节点样式 / 值更改,则还需要继续学习。

先简述一下上面那个链接用到的 js 方法:

首先,用 getElementById 来获取被添加了点击事件 add_jietu () 的节点

var add_jietu = document.getElementById('add_jietu'); //add_jietu 对应的是元素的 id

随后,使用主角 cloneNode 进行节点的复刻(如果不是复制当前节点,还要涉及其他步骤,这里先一点点整理):

var node_clone = add_jietu.cloneNode();

如果复制节点不需要 id,则应把 id 删掉,以防止与前面节点的 id 冲突问题(id 需唯一):

node_clone.removeAttribute('id');

最后提取想要复制的节点的父节点,以便将新的复刻节点添加到父节点下,即复刻节点与原来的节点拥有同一个父节点:

var nodeFather = add_jietu.parentNode;
nodeFather.appendChild(node_clone);

于是我们就可以手动添加节点啦,点击 span 标签,效果是下面这个样子的:

截图2

之后是刚才提到的,不是复制当前节点的话如何进行被克隆节点的定位,就像我遇到的需求:我点击的是我的 button 按钮,但需要复制的是表单,这时就需要使用 parent (),children (),siblings (),利用事件监听节点和被复制节点的 div 容器关系进行定位,这部分应该是 Jquery 的语法,我一直对两者的区分不是很透彻(用的时候都混在一起用)。下面是我对学长项目进行功能添加时编写的一部分代码,无法 po 出包括前端在内的完整代码,所以只能文字讲解:

//yaqin
 // 测试动态表单 onclick 是否有效
 function submit_contract(obj) {
     var btnx = $(obj).parent();
     //var requestGroup = btnx.parent();
     var formGroup = btnx.siblings("div[id^='form-group']");
        
     //var lia = formGroup.children ("li.a1"); // 扩展,li 标签 namme 为 a1 的子节点
     //alert(lia.first().text() + lia.eq(1).text());
     var lib = formGroup.children("li.b1");
 }

首先,函数参数 obj 即为 button 按钮点击事件时传回来的参数,在前端 button 标签中需添加 onclick="submit_contract(this)"this 即为 button 本身,利用 $(obj) 可以将 dom 节点转换为 jquery 对象,随后我们可以使用 parent () 函数提取父节点:

var btnx = $(obj).parent();

但单就我的例子来说:我的表单是 "button 的父节点的同宗节点",故需要用到 siblings() 函数,但同时与 button 的父节点互为同宗节点的有很多,我还需要通过表达式进行筛选:

  • 这里 siblings 函数内参数中的 div 表示要求同宗节点标签是 div
  • [id^='form-group'] 表示限制其 id 以 form-group 开头;id 换成 name 就是对 name 属性进行筛选,^= 也可以换成别的,和正则表达式差不多,参考链接
表达式含义
attribute^=valueattr 属性的值以 value 作为开头
attribute$=valueattr 属性的值以 value 作为结尾
attribute*=valueattr 属性的值中包含 value
attribute!=valueattr 属性中不含有 value 值

通过上述的筛选,我变精准的找到了我要复制的表单 form-group [数字],有数字因为涉及到 id 冲突,也是为什么我需要用表达式进行筛选而不是直接 "="

var formGroup = btnx.siblings("div[id^='form-group']");

# 2. 更换节点 id—— 避免 id 冲突

还记得上面的节点 node_clone 吗,其实改变 id 很简单,因为可以直接获取到 id 属性修改,这里我们定义全局变量 i,每执行一次点击事件就让 ++i ,并将 i 值嵌入我们的 id 属性值中,就避免了 id 冲突啦!这个思路十分的程序猿

var i = 0; // 重点在这里
 function add_jietu()
 {
   var add_jietu = document.getElementById('add_jietu' + i);
   var nodeFather = add_jietu.parentNode;
   var node_clone = add_jietu.cloneNode(true);
   ++i;
   node_clone.id = "add_jietu" + i; // 重点在这里
   ……

那么问题又来了!我不仅要改复制节点的 id,复制节点还有子节点呀,他们的 id 怎么办?这个时候就可以用 getElementsByTagName 进行筛选啦!

getElementsByTagName(value) 会返回标签为 value 的子节点的 List,之后按数组的形式一个个修改 id 就可以啦!注意下标是根据筛选结果走的,不同标签的筛选结果需要从新计数:

newFormNode.id = "request-group" + i;
newFormNode.getElementsByTagName('input')[0].id = "consumersubject" + i;
newFormNode.getElementsByTagName('input')[1].id = "consumerobject" + i;
newFormNode.getElementsByTagName('input')[2].id = "consumerstarttime" + i;
newFormNode.getElementsByTagName('button')[1].id = "submit_provider" + i;

需要注意的是 getElementsByTagName 方法是 dom 节点,即 javascript 的语法,如果用 $(obj) 将 dom 转换成 jq 对象了的话,使用 parent()children() 函数就可以了,但是还差个小尾巴 ↓

随后学长的项目又给我出了点难题,论子节点的子节点要修改 id 怎么办?而且子节点很多,用的标签都是一样的,偏偏需要改 id 的只有那么几个,我们还是用 children () 获取的子节点:

var lib = formGroup.children("li.b1");

其中 li.b1 表示 li 标签 name 为 b1 的子节点,那 children 返回的是数组,如何获取指定节点呢?这里我也是使用的坐标定位,但不是单纯的把下标加在后面,jquery 有自己的获取指定第几个节点的函数:

var lib = formGroup.children("li.b1");
var consumersubject = lib.first();
var consumerobject = lib.eq(1);
var consumerstarttime = lib.eq(2);

first()last() 分别表示 List <节点> 中的第一个节点和最后一个节点, eq(value) 表示的是第 value 个节点,之后再在此基础上进行子节点的筛选,就能找到子节点的子节点并对他进行操作啦!

比如下面我需要获取 "子节点的子节点" 的 value 值:

var lib = formGroup.children("li.b1");
var consumersubject = lib.first().children("input[id^='consumersubject']").val();
var consumerobject = lib.eq(1).children("input[id^='consumerobject']").val();
var consumerstarttime = lib.eq(2).children("input[id^='consumerstarttime']").val();

参考链接:https://stackoverflow.com/questions/46078328/change-ids-of-child-elements-in-javascript-when-cloning-an-element

# 结语

做这个动态添加表单的功能时,感觉就是疯狂的脑筋急转弯,因为真的有点绕,写着写着就记不住自己定位到哪里了,就像这次学长的那个表单其实是 "button 的父节点的同宗节点的子节点的子节点",更何况我还是边学习边写,真的很绕 hhhhh

之后我总结下来的经验是:

  • 一定要先对 div 容器有个大致的了解,比如 div 和 div 之间的关系啥的,最好是先把缩进调正确了,后期会省很多时间(偷偷来一句不在乎缩进的同僚是真的屑)
  • 不要都写在一行,对于我们初学者,可以先不用在乎变量创建的多少,参照我那个任务,老老实实按当前节点 —— 父节点 —— 同宗节点 —— 子节点 —— 子节点来创建,这样调试也容易知道自己试哪里出的问题。
  • 虽然根本不是什么复杂问题但还是可以用高内聚低耦合的思想看,说白了就是复杂问题拆成简单问题

整理了半天总算是捋顺了,最后夹杂一下私货,祝自己生日快乐🎉

更新于 阅读次数

请我喝[茶]~( ̄▽ ̄)~*

斐雅琴 微信支付

微信支付

斐雅琴 支付宝

支付宝

斐雅琴 贝宝

贝宝