克隆/删除输入字段 – 保持元素ID唯一

我目前正在处理在表单内生成动态输入字段。 我有一个复杂的例子,它使用复选框和选择框。 它有两种类型的元素: main_itemssub_items 。 如上所述,我可以通过clone函数动态地添加一些jquery来输入输入字段,该函数复制具有唯一id属性的一组新输入字段。 但是我对两件事情有很大的困难:首先,保持id对每个复制的元素都是唯一的,特别是对于选择框。 其次,我只能得到第一个下拉菜单才能用于第一个项目,但我还没有想出办法为其他项目做这个。 的jsfiddle

 $('#btnAdd').click(function () { var num = $('.clonedSection').length; var newNum = num + 1; var newSection = $('#pq_entry_' + num).clone().attr('id', 'pq_entry_' + newNum); newSection.find('input[type="text"]').val(''); newSection.find('select').val(''); newSection.find('input[type="checkbox"]').prop('checked', false); //hide sub item newSection.find('.sub-item').hide(); //change the input element selectors to use name newSection.find('input[name^="first_item_"]').attr('id', 'main_item_' + newNum).attr('name', 'main_item_' + newNum); newSection.find('input[name^="second_item_"]').attr('id', 'second_item_' + newNum).attr('name', 'second_item_' + newNum); newSection.find('input[name^="item_count_"]').attr('id', 'item_count_' + newNum).attr('name', 'item_count_' + newNum); newSection.find('input[name^="sub_item_"]').attr('id', 'sub_item_' + newNum).attr('name', 'sub_item_' + newNum); newSection.find('input[name^="other_item_"]').attr('id', 'other_item_' + newNum).attr('name', 'other_item_' + newNum); newSection.insertAfter('#pq_entry_' + num).last(); $('#btnDel').click(function () { var num = $('.clonedSection').length; // how many "duplicatable" input fields we currently have $('#pq_entry_' + num).remove(); // remove the last element // enable the "add" button $('#btnAdd').prop('disabled', ''); // if only one element remains, disable the "remove" button if (num - 1 == 1) $('#btnDel').prop('disabled', 'disabled'); }); }); $('#btnDel').prop('disabled', 'disabled'); //Generate Dropdown $('#item_count_1').change(function() { var option = $(this).val(); showFields(option); return false; }); function showFields(option){ var content = ''; for (var i = 1; i <= option; i++){ content += '

--- Select ---applesbananamango
'; } $('#item_names_1').html(content); }

HTML

 
    • ---Select--- 1 2

那么,我们来谈谈如何构建基本的GUI应用程序。 在我们继续之前,我想让你知道下面的代码可以用Knockout / Angular中的~20 LoC编写,但我选择不这样做,因为那不会真正教给任何人任何东西。

那么,我们来谈谈GUI。

这一切归结为两件事。

  • 演示文稿 – 这是您的HTML,CSS以及用户直接与之交互的内容。
  • 数据 – 这是您的实际数据和逻辑。

我们希望它们分开 ,以便它们可以独立行动。 我们想要实际表示用户在JavaScript对象中看到的内容,以便它可以维护,可测试可读等等。 有关详细信息,请参阅关注点分离 。

让我们从数据开始。

那么,每个东西在你的应用程序中有什么作用?

  • 第一项 ,无论是真还是假
  • Sub Item为true或false,但如果First Item不为true,则永远不为true。
  • 第二项是真还是假。
  • 作为数字的项目
    • 这些食物中的一种都是苹果,香蕉或芒果

最直观的是从那里开始。

 // our item, like we've just described it :) function Thing(){ //we use this as an object constructor. this.firstItem = false; this.subItem = false; this.secondItem = false; this.numItems = 0; this.items = []; // empty list of items } 

嗯,这是一件事,我们现在可以使用new Thing()创建它们,然后设置它们的属性,例如thing.firstItem = true

但我们没有Thing我们有东西。 东西只是一个(有序的)一堆东西。 有序集合通常由JavaScript中的数组表示,因此我们可以:

 var stuff = []; // our list var thing = new Thing(); // add a new item stuff.push(thing); // add the thing we just created to our list 

我们当然也可以在提交时将此信息传达给PHP。 另一种方法是提交一个JSON对象并在PHP中读取它(这很好!),或者我们可以将它序列化为表格参数 (如果你对该问题中的方法有任何问题 – 请告诉我)。

现在我只是有一堆物品……而且很头疼。

相当精明。 到目前为止,您只有对象,您没有在任何地方指定它们的行为。 我们有’数据’层,但我们还没有任何表示层。 我们首先要删除所有ID并添加行为。

输入模板!

我们不想克隆现有的对象,而是希望采用’cookie cutter’的方式来创建新元素的外观。 为此,我们将使用模板。 让我们首先提取“项目列表”在HTML模板中的显示方式。 基本上,给你的HTML它是这样的:

  

现在让我们创建一个’哑’方法,用于在屏幕上显示模板。

 var template; function renderItem(){ template = template || $("[data-template=item]").html(); var el = $("
").html(template); return el; // a new element with the template }

[这是我们的第一个jsfiddle演示演示]( http://jsfiddle.net/RLRtv/ ,只是添加了三个项目,没有任何行为到屏幕上。阅读代码,看到你理解它,不要害怕询问你不明白的位:)

将它们绑在一起

接下来,我们将添加一些行为,当我们创建一个项目时,我们会将它耦合到一个Thing 。 因此,我们可以采用单向数据绑定方式(视图中的更改反映在模型中)。 如果你感兴趣的话我们可以稍后实现另一个绑定方向,但它不是原始问题的一部分,所以为了简洁起见,我们现在就跳过它。

 function addItem(){ var thing = new Thing(); // get the data var el = renderItem(); // get the element el. // WHOOPS? How do I find the things, you removed all the IDs!?!? } 

那么,我们在哪里被困? 我们需要将行为附加到我们的模板,但普通的HTML模板没有钩子,所以我们必须手动完成。 让我们首先使用“数据绑定”属性更改我们的模板。

  

查看我们添加的所有data-bind属性? 让我们尝试选择那些。

 function addItem() { var thing = new Thing(); // get the data var el = renderItem(); // get the element //wiring el.find("[data-bind=firstItem]").change(function(e){ thing.firstItem = this.checked; if(thing.firstItem){//show second item el.find("[data-bind=subItem]").show(); //could be made faster by caching selectors }else{ el.find("[data-bind=subItem]").hide(); } }); el.find("[data-bind=subItem] :checkbox").change(function(e){ thing.subItem = this.checked; }); return {el:el,thing:thing} } 

在这个小提琴中,我们已经为第一个项目和子项目添加了属性,他们已经更新了元素。

让我们继续为第二个属性做同样的事情。 它几乎是相同的,直接绑定。 另外,有几个库会自动为您执行此操作 – 例如Knockout

这是设置所有绑定的另一个小提琴 ,这结束了我们的表示层,我们的数据层及其绑定。

 var template; function Thing() { //we use this as an object constructor. this.firstItem = false; this.subItem = false; this.secondItem = false; this.numItems = 0; this.items = []; // empty list of items } function renderItem() { template = template || $("[data-template=item]").html(); var el = $("
").html(template); return el; // a new element with the template } function addItem() { var thing = new Thing(); // get the data var el = renderItem(); // get the element el.find("[data-bind=firstItem]").change(function (e) { thing.firstItem = this.checked; if (thing.firstItem) { //show second item el.find("[data-bind=subItem]").show(); //could be made faster by caching selectors } else { el.find("[data-bind=subItem]").hide(); } }); el.find("[data-bind=subItem] :checkbox").change(function (e) { thing.subItem = this.checked; }); el.find("[data-bind=secondItem]").change(function (e) { thing.secondItem = this.checked; if (thing.secondItem) { el.find("[data-bind=detailsView]").show(); } else { el.find("[data-bind=detailsView]").hide(); } }); var $selectItemTemplate = el.find("[data-bind=items]").html(); el.find("[data-bind=items]").empty(); el.find("[data-bind=numItems]").change(function (e) { thing.numItems = +this.value; console.log(thing.items); if (thing.items.length < thing.numItems) { for (var i = thing.items.length; i < thing.numItems; i++) { thing.items.push("initial"); // nothing yet } } thing.items.length = thing.numItems; console.log(thing.items); el.find("[data-bind=items]").empty(); // remove old items, rebind thing.items.forEach(function(item,i){ var container = $("
").html($selectItemTemplate.replace("{number}",i+1)); var select = container.find("select"); select.change(function(e){ thing.items[i] = this.value; }); select.val(item); el.find("[data-bind=items]").append(container); }) }); return { el: el, thing: thing } } for (var i = 0; i < 3; i++) { var item = addItem(); window.item = item; $("body").append(item.el); }

按钮

有趣的是,现在我们完成了繁琐的部分,按钮是小菜一碟。

让我们添加“添加”按钮

   

和JavaScript:

 var stuff = []; $("[data-action='add']").click(function(e){ var item = addItem(); $("body").append(item.el); stuff.push(item); }); 

男孩, 这很容易 。

好的,所以删除应该很难,对吧?

HTML:

  

JS:

 $("[data-action='remove']").click(function(e){ var item = stuff.pop() item.el.remove(); }); 

好的, 所以这很可爱。 那么我们如何获取数据呢? 让我们创建一个按钮,显示屏幕上的所有项目?

  

和JS

 $("[data-action='alertData']").click(function(e){ var things = stuff.map(function(el){ return el.thing;}); alert(JSON.stringify(things)); }); 

哇! 我们在模型层中实际表示了我们的数据。 我们可以用它做任何我们想做的事,这很可爱。

如果我想将其作为表单提交,该怎么办? $.param救援。

  

和JS:

 $("[data-action='asFormData']").click(function(e){ var things = stuff.map(function(el){ return el.thing;}); alert($.param({data:things})); }); 

虽然这种格式不是很好,但PHP(或任何其他流行技术)很乐意在服务器端读取。

所以把它包起来

  • 将数据与数据分开
  • 如果你有JS逻辑 - 有一个单一的事实来源 - JavaScript对象
  • 考虑更多地阅读它,了解像KnockoutJS或AngularJS这样的常见框架,这些框架对这个问题有一些有趣的简洁解决方案(以假设为代价)。
  • 阅读有关UI架构的更多信息 这对于初学者来说是一个很好的(但很难)资源
  • 避免重复ID,它们很糟糕 - 而你在那里不会在你的dom中存储数据。
  • 不要害怕提问 - 这就是你学习的方式。
  • 你可以在这里轻松摆脱jQuery。

我的方法是:

首先,正确使用

  

不是

  

通过第一种方式执行,您可以确保标签是可点击的 ,就像您单击复选框一样,保持可访问性


另一方面,有太多的魔法。 只需使用适合的data-xxx属性:

 
    ....

所以你可以通过data-id属性找到一个元素:

 var myFirstSection = $("ul.pq_entry[data-id=1]"); 

通过这样做,在许多元素中根本不需要设置id属性,因为您可以简单地使用class并通过遍历DOM来查找单个项。 例如, main_item变为:

   

如果由于某种原因你需要在克隆的第3部分找到这个项目,你可以这样做:

 var mySection = 3; $("ul.pq_entry[data-id=" + mySection + "] .menu_item").someFancyMethod(...); 

克隆节时,可以动态分配data-xxx属性,如下所示:

 var myNewId = myOldId + 1; $clonedSection.data("id", myNewId); 

然后,我将使用名称数组,如main_item[]因此您不需要在名称中手动指定id,但是您必须将此方法限制为仅在克隆部分中出现一次的元素。

名称数组意味着当您从表单中检索值时,从服务器端(例如,在PHP中使用$ _POST),您将获得它们在表单中出现的确切顺序的值数组。 就像任何语言的常规数组一样,您可以访问(例如PHP中)的部分中的项目:

 $_POST['main_item'][0] // for section 1 $_POST['main_item'][1] // for section 2 ... and so on 

尝试分解代码以便更好地管理。

对于上述情况,

HTML

将可恢复的html块隐藏在模板中:

 

CSS一小部分CSS,以确保我们的模板永远不会出现在屏幕上

 .form-template { display:none; } .form-area li, #main-panel li { list-style-type: none; } .hidden { display:none; } 

JS

从配置对象开始,轻松管理属性

 var config = {}; config.formSectionIdPrefix = "pq_entry_"; config.firstCheckBoxIdPrefix = "first_item_"; config.firstCheckBoxNamePrefix = "main_item_"; config.checkBoxSubItem1IdPrefix = "sub_item_"; config.checkBoxSubItem1NamePrefix = "sub_item_"; config.checkBoxSubItem2IdPrefix = "second_item_"; config.checkBoxSubItem2NamePrefix = "main_item_"; config.selectItem1IdPrefix = "item_count_"; config.selectItem2IdPrefix = "item_names_"; config.dependantSelectIdPrefix = "item_"; 

缓存对FormSectionTemplate,SelectDropdownTemplate和FormArea的引用

 var $formTemplate = $(".form-template"); var $selectTemplate = $(".select-template"); var $formArea = $(".form-area"); 

并且可能是一个索引变量来跟踪Id增量

 var index = 0; 

有一个帮助方法getFormTemplate执行以下操作:

克隆形成部分

将事件附加到克隆的表单部分

克隆部分的增量ID(更多内容进一步向下)

  function getFormTemplate() { var $newTemplate = $formTemplate.children().clone(true); var $formSectionWithEvents = attachEvents( $newTemplate ); var $formSectionWithUpdatedAttributes = incrementAttributes( $formSectionWithEvents ); return $formSectionWithUpdatedAttributes; } 

将事件附加到克隆的表单部分attachEvents

 function attachEvents( $formSection ) { var $mainCheckBoxes = $formSection.find( ".main-item" ); var $selectBox = $formSection.find( ".medium" ); var $dependantSelectSection = $formSection.find( ".dependant-select" ); $mainCheckBoxes.on("click", function() { var $this = $( this ); var $subItem = $this.siblings(".sub-item"); if ( $this.is(":checked") ) { $subItem.show(); } else { $subItem.hide(); } }); $selectBox.on("change", function() { var option = $(this).val(); var $dependantSelect = getSelectField( option ); $dependantSelectSection.children().remove(); $dependantSelectSection.append( $dependantSelect ); }); return $formSection; } 

增加克隆表单部分的ID。

嗯,有很多方法可以接近它(这在很大程度上取决于你的咖啡因含量)

在下面的位中,我们正在寻找所有带有data-custom-attributes的元素

迭代所有这些元素,找出我们应该在config部分中查找的id和name键,然后分配附加index增量器的那些值。

 function incrementAttributes( $formSection ) { index = index + 1; var $customAttributeElements = $formSection.find("[data-custom-attributes]"); $customAttributeElements.each( function() { var $this = $(this); var idNamePrefix = $this.attr( "data-id" ); var namePrefix = $this.attr( "data-name" ); var idName = config[idNamePrefix] + index; var name = config[namePrefix] + index; $this.attr( "id", idName ); $this.attr( "name", name ); }); return $formSection; } 

获取从属选择字段(由选择下拉列表中的onchange事件进行了tirggered)

它只是从父选择框中获取值,并使用config对象的前缀将其分配给克隆的选择框的ID等。

 function getSelectField( indexValue ) { var $selectItem = $selectTemplate.find("select").clone(); var selectElementIdPrefix = $selectItem.attr("data-id"); var selectElementId = config[selectElementIdPrefix] + indexValue; $selectItem.attr( "id", selectElementId ); return $selectItem; } 

把它全部收集起来

 $("#btnAdd").on("click", function(e) { e.preventDefault(); var $formSection = getFormTemplate(); $formArea.append($formSection); }); $("#btnDel").on("click", function(e) { e.preventDefault(); $formArea.children().last().remove(); if ( index > 0 ) { index = index - 1; } }); 

事件中唯一提到的是#btnDel递减索引以确保下一个表单部分插入附加了正确的ID。

JS小提琴: http : //jsfiddle.net/Varinder/3VT2w/3/

编辑

刚注意到上面的小提琴中有一些HTML标签不匹配(固定)

下拉选择应根据选择添加1个或更多子下拉菜单。

可以通过将$selectBox上的change事件change为以下内容来完成:

 $selectBox.on("change", function() { var option = $(this).val(); var optionInt = parseInt( option ); $dependantSelectSection.children().remove(); for ( var i = 0; i < optionInt; i++ ) { var $dependantSelect = getSelectField( option ); $dependantSelectSection.append( $dependantSelect ); } }); 

更新小提琴: http : //jsfiddle.net/Varinder/3VT2w/4/

编辑2

使用增量添加子选择项名称:

 $selectBox.on("change", function() { var option = $(this).val(); var optionInt = parseInt( option ); $dependantSelectSection.children().remove(); for ( var i = 1; i <= optionInt; i++ ) { var $dependantSelect = getSelectField( option ); $dependantSelectSection.append( "item" + i ); $dependantSelectSection.append( $dependantSelect ); } }); 

更新小提琴: http : //jsfiddle.net/Varinder/3VT2w/5/