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

tec*_*t82 10 javascript jquery

我目前正在处理在表单内生成动态输入字段.我有一个复杂的例子,它使用复选框和选择框.它有两种类型的元素:main_itemssub_items.如上所述,我可以通过一个clone函数动态添加输入字段,该函数复制具有唯一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);
}
Run Code Online (Sandbox Code Playgroud)

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>
Run Code Online (Sandbox Code Playgroud)

Ben*_*aum 20

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

那么,我们来谈谈GUI.

这一切归结为两件事.

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

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

让我们从数据开始.

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

  • 一个第一个项目,无论是真的还是假的
  • 一个子项目真或假,但从来没有真正的,如果第一项是不正确的.
  • 一个第二个项目是真或假.
  • 作为数字的项目
    • 这些食物中的一种都是苹果,香蕉或芒果

最直观的是从那里开始.

// 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
}
Run Code Online (Sandbox Code Playgroud)

嗯,这是一件事,我们现在可以创建它们,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
Run Code Online (Sandbox Code Playgroud)

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

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

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

输入模板!

我们不想克隆现有的对象,而是想要一种'cookie cutter'方式来创建新元素的外观.为此,我们将使用模板.让我们首先提取"项目列表"在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>
Run Code Online (Sandbox Code Playgroud)

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

var template;
function renderItem(){
    template = template || $("[data-template=item]").html();
    var el = $("<div></div>").html(template);
    return el; // a new element with the template
} 
Run Code Online (Sandbox Code Playgroud)

[这是我们的第一个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!?!?
}
Run Code Online (Sandbox Code Playgroud)

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

<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>
Run Code Online (Sandbox Code Playgroud)

查看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}
}
Run Code Online (Sandbox Code Playgroud)

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

让我们继续为第二个属性做同样的事情.它几乎是相同的,直接绑定.在旁注中,有几个库会自动为您执行此操作 - 例如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 = $("<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);
}
Run Code Online (Sandbox Code Playgroud)

按钮

有趣的是,现在我们完成了繁琐的部分,按钮是一块蛋糕.

我们添加"添加"按钮

 <input type='button' value='add' data-action='add' />
Run Code Online (Sandbox Code Playgroud)

和JavaScript:

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

男孩,这很容易.

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

HTML:

<input type='button' value='remove' data-action='remove' />
Run Code Online (Sandbox Code Playgroud)

JS:

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

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

<input type='button' value='show' data-action='alertData' />
Run Code Online (Sandbox Code Playgroud)

和JS

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

哇!我们在模型层中实际表示了我们的数据.我们可以做任何我们想要的东西,这很可爱.

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

<input type='button' value='formData' data-action='asFormData' />
Run Code Online (Sandbox Code Playgroud)

和JS:

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

而且虽然这种格式不是很好看它的东西,PHP(或任何其他流行的技术)将在服务器端欣然读取.

所以把它包起来

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

  • 只是旁注 - 如果您对此有任何疑问或想要讨论方面我和其他一些人经常在[Stack Overflow Chat JavaScript Room](http://chat.stackoverflow.com/rooms/17/ javascript).很好地构建UI很难学 - 如果您有任何疑问请告诉我,这就是我学会更好地解释这一点的方法.如果有什么,我的学生将来会欣赏它. (3认同)