克隆/删除输入字段 – 保持元素ID唯一
我目前正在处理在表单内生成动态输入字段。 我有一个复杂的例子,它使用复选框和选择框。 它有两种类型的元素: main_items
和sub_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/