我目前正在处理在表单中生成动态输入字段。我有一个复杂的例子,它使用复选框和选择框。它有两种类型的元素: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 += '<div id="item_'+i+'"><label>Item # '+i+'</label><br /><label>Item Name:</label> <select id="item_name_'+i+'" name="item_name_'+i+'" class="course_list"><option value="" >--- Select ---</option><option value="apples" >apples</option><option value="banana" >banana</option><option value="mango" >mango</option></select></div>';
}
$('#item_names_1').html(content);
}
HTML
<ul id="pq_entry_1" class="clonedSection">
<li style="list-style-type: none;">
<input id="first_item_1" class="main-item" name="main_item_1" type="checkbox"><label>First Item</label>
</li>
<li style="list-style-type: none;">
<input id="second_item_1" class="main-item" name="main_item_1" type="checkbox"><label>Second Item</label>
</li>
<ul class="sub-item" style='display: none;'>
<li style="list-style-type: none;">
<label>
How many items:
<small>required</small>
</label>
<select id="item_count_1" name="item_count_1" class="medium" required>
<option value="">---Select---</option>
<option value="1">1</option>
<option value="2">2</option>
</select>
</li>
<li style="list-style-type: none;">
<div id="item_name_1"></div>
</li>
</ul>
</ul>
答案 0 :(得分:20)
所以,让我们谈谈如何构建基本的GUI应用程序。在我们开始之前,我希望您知道下面的代码可以用Knockout / Angular中的~20 LoC编写,但我选择不这样做,因为它不会真正教给任何人任何东西。
所以,让我们谈谈GUI。
我们希望分开它们,以便它们可以独立行动。我们想要实际表示用户在JavaScript对象中看到的内容,以便它可以维护,可测试可读等等。有关详细信息,请参阅Separation of Concerns。
那么,每个东西在你的应用程序中有什么作用?
最直观的是在那里开始 。
// 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
。
但我们没有 a 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中读取它(这很好!),或者我们可以serialize it as form params(如果您对该问题中的方法有任何问题 - 请告诉我)。
非常精明。到目前为止,您只有对象,您没有在任何地方指定它们的行为。我们有我们的数据&#39;图层,但我们还没有任何表示层。我们首先要删除所有ID并添加行为。
我们不想克隆现有的物品,而是希望拥有一个“切割器”。创建新元素外观的方法。为此,我们将使用模板。让我们首先提取您的商品列表&#39;查看HTML模板。基本上,考虑到你的HTML,它就像:
<script type='text/template' data-template='item'>
<ul class="clonedSection">
<li style="list-style-type: none;">
<label><input class="main-item" type="checkbox" />First Item</label>
<ul class="sub-item" style="display: none;">
<li style="list-style-type: none;">
<label><input type="checkbox" />Sub Item</label>
</li>
</ul>
</li>
<li style="list-style-type: none;">
<label>
<input class="main-item" type="checkbox" />Second Item</label>
<ul class="sub-item" style='display: none;'>
<li style="list-style-type: none;">
How many items:
<select class="medium" required>
<option value="">---Select---</option>
<option value="1">1</option>
<option value="2">2</option>
</select>
</li>
<li style="list-style-type: none;"><div></div></li>
</ul>
</li>
</ul>
</script>
现在让我们创造一个“愚蠢的”。在屏幕上显示模板的方法。
var template;
function renderItem(){
template = template || $("[data-template=item]").html();
var el = $("<div></div>").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模板没有钩子,所以我们必须手动完成。让我们首先使用&#39;数据绑定来改变我们的模板。属性。
<script type='text/template' data-template='item'>
<ul class="clonedSection">
<li style="list-style-type: none;">
<label>
<input class="main-item" data-bind = 'firstItme' type="checkbox" />First Item</label>
<ul class="sub-item" data-bind ='subItem' style="display: none;">
<li style="list-style-type: none;">
<label>
<input type="checkbox" />Sub Item</label>
</li>
</ul>
</li>
<li style="list-style-type: none;">
<label>
<input class="main-item" data-bind ='secondItem' type="checkbox" />Second Item</label>
<ul class="sub-item" style='display: none;'>
<li style="list-style-type: none;">How many items:
<select class="medium" data-bind ='numItems' required>
<option value="">---Select---</option>
<option value="1">1</option>
<option value="2">2</option>
</select>
</li>
<li style="list-style-type: none;">
<div data-bind ='items'>
</div>
</li>
</ul>
</li>
</ul>
</script>
查看我们添加的所有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}
}
在this fiddle我们已经为第一个项目和子项目添加了属性,他们已经更新了元素。
让我们继续为第二个属性做同样的事情。它几乎是一样的,直接绑定。另外,有几个库会自动为您执行此操作 - Knockout for example
这里是设置了所有绑定的another fiddle,这结束了我们的表示层,我们的数据层及其绑定。
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 = $("<div></div>").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 = $("<div></div>").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);
}
有趣的是,既然我们完成了繁琐的部分,按钮就是小菜一碟。
让我们添加&#34;添加&#34;按钮
<input type='button' value='add' data-action='add' />
和JavaScript:
var stuff = [];
$("[data-action='add']").click(function(e){
var item = addItem();
$("body").append(item.el);
stuff.push(item);
});
男孩,that was easy。
好的,所以删除应该很难,对吧?
HTML:
<input type='button' value='remove' data-action='remove' />
JS:
$("[data-action='remove']").click(function(e){
var item = stuff.pop()
item.el.remove();
});
好的,so that was pretty sweet.那么我们如何获取数据呢?让我们创建一个按钮,显示屏幕上的所有项目?
<input type='button' value='show' data-action='alertData' />
和JS
$("[data-action='alertData']").click(function(e){
var things = stuff.map(function(el){ return el.thing;});
alert(JSON.stringify(things));
});
Woah!我们在模型层中实际表示了我们的数据。我们可以随心所欲地做任何事情,这非常可爱。
如果我想将其作为表单提交,该怎么办? $.param
救援。
<input type='button' value='formData' data-action='asFormData' />
和JS:
$("[data-action='asFormData']").click(function(e){
var things = stuff.map(function(el){ return el.thing;});
alert($.param({data:things}));
});
而且while this format is not very nice PHP(或任何其他流行技术)很乐意在服务器端阅读。
答案 1 :(得分:1)
我的方法是:
首先,正确使用<label>
<label><input ... /> My label</label>
和不
<input><label>...</label>
通过第一种方式,确保标签可点击,就像点击复选框一样,保留了辅助功能
另一方面,字符串魔法过多。只需使用适合的data-xxx
属性:
<ul class='pq_entry' data-id='1'>
....
</ul>
所以你可以通过data-id
属性找到一个元素:
var myFirstSection = $("ul.pq_entry[data-id=1]");
通过这样做,在许多元素中根本不需要设置id
属性,因为您可以简单地使用class
并通过遍历DOM来查找单个项目。
例如,main_item
变为:
<input class="main-item" name="main_item[]" type="checkbox">
如果由于某种原因你需要在克隆的第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
答案 2 :(得分:0)
尝试分解代码以便更好地管理。
对于上述情况,
<强> HTML 强>
在模板中隐藏了可恢复的html块:
<div class="form-template"> <!-- will pull form section template from here -->
<ul data-custom-attributes="" data-id="formSectionIdPrefix" class="form-section">
<li>
<input data-custom-attributes="" data-id="firstCheckBoxIdPrefix" data-name="firstCheckBoxNamePrefix" class="main-item checkbox1" type="checkbox" />
<label>First Item</label>
<ul class="sub-item" style="display:none;">
<li>
<input type="checkbox" />
<label>Sub Item</label>
</li>
<li>
<input class="main-item" data-id="checkBoxSubItem2IdPrefix" data-name="checkBoxSubItem2NamePrefix" type="checkbox" />
<label>Second Item</label>
<ul class="sub-item" style="display:none;">
<li>
<label>How many items:</label>
<select data-custom-attributes="" data-id="selectItem1IdPrefix" data-name="selectItem1IdPrefix" class="medium" required>
<option value="">---Select---</option>
<option value="1">1</option>
<option value="2">2</option>
</select>
</li>
<li style="list-style-type: none;">
<div data-custom-attributes="" class="dependant-select" data-id="selectItem2IdPrefix"></div>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</div>
<div class="select-template hidden"> <!-- will pull dependant select template -->
<select class="course_list" data-id="dependantSelectIdPrefix">
<option value="">-- select --</option>
<option value="apple">apples</option>
<option value="apple">bananas</option>
</select>
</div>
<div class="form-area"> <!-- main area to append form sections to -->
</div>
<div class="form-area-controls"> <!-- form controls -->
<input type='button' class="button tiny radius" id='btnAdd' value='Add Another' />
<input type='button' class="button tiny radius alert" id='btnDel' value='Delete Last' />
</div>
<强> 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和名称密钥,然后分配附加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
事件处理)
它只是从父选择框中获取值,并使用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个或更多子项下拉列表。
可以通过将change
上的$selectBox
事件更改为以下内容来完成:
$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 );
}
});