草庐IT

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

coder 2024-05-15 原文

我目前正在使用在表单内部生成动态输入字段的方法。我有一个使用复选框和选择框的复杂示例。它具有两种类型的元素: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 += '<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>

最佳答案

因此,让我们谈谈如何构建基本的GUI应用程序。在我们继续之前,我希望您知道以下代码可以在Knockout / Angular的〜20 LoC中编写,但我选择不这样做,因为那样做实际上不会教任何人任何东西。

因此,让我们谈谈GUI。

归结为两点。

  • 演示文稿-这是您的HTML,css和用户直接与其交互的内容。
  • 数据-这是您的实际数据和逻辑。

  • 我们希望将它们分开,以便它们可以独立行动。我们想要用户在JavaScript对象中看到的内容的真实表示,以便它是可维护的,可测试的可读性等等。有关更多信息,请参见Separation of Concerns

    让我们从数据开始。

    那么,您的应用程序中每件事都有什么?
  • 第一项,是对还是错
  • 子项为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中读取它(这很好!),或者我们可以serialize it as form params(如果您对该问题中的方法有任何疑问,请告诉我)。

    现在我只有一堆东西...和头痛。

    相当机敏。到目前为止,您只有对象,没有在任何地方指定它们的行为。我们有我们的“数据”层,但是我们还没有任何表示层。我们将摆脱所有ID并添加行为。

    输入模板!

    与其克隆现有对象,我们不如希望使用一种“ scrapy 切割器”方法来创建新元素的外观。为此,我们将使用模板。让我们从提取“项目列表”到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/,它仅添加了三个项目,而未在屏幕上显示任何行为。阅读代码,了解并理解这些内容,不要害怕询问您不了解的内容:)

    将它们绑定(bind)在一起

    接下来,我们将添加一些行为,当我们创建一个项目时,我们将其耦合到Thing。因此,我们可以采用一种方式进行数据绑定(bind)( View 中的更改反射(reflect)在模型中)。如果您有兴趣,我们可以在以后实现绑定(bind)的另一个方向,但这不是原始问题的一部分,因此,为简便起见,我们现在跳过它。
    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模板对此没有钩子(Hook),因此我们必须手动进行。让我们首先使用“数据绑定(bind)”属性更改模板。
    <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中,我们已将属性添加到第一项和子项,并且它们已经更新了元素。

    让我们继续对第二个属性做同样的事情。几乎完全相同,直接绑定(bind)。附带说明,有几个库会自动为您执行此操作-Knockout for example

    这是another fiddle,其中设置了所有绑定(bind),从而结束了我们的表示层,数据层及其绑定(bind)。
    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);
    }
    

    按钮

    有趣的是,既然我们已经完成了乏味的部分,那么按钮就是小菜一碟。

    让我们添加“添加”按钮
     <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(或任何其他流行的技术)可以在服务器端轻松阅读的内容。

    所以总结一下
  • 将演示文稿与数据
  • 分开
  • 如果您具有JS逻辑-拥有单一的事实来源-JavaScript对象
  • 考虑多读一些文章,了解诸如KnockoutJS或AngularJS之类的通用框架,这些框架对于该问题的解决方案较为繁琐(以假设为代价)。
  • 了解有关UI架构的更多信息。 This is a good (but hard for beginners) resource
  • 避免重复的ID,因为它们很不好-当您在那里时,请不要在您的dom中存储数据。
  • 不要害怕问问题-这就是您的学习方法。
  • 您可以在这里轻松摆脱jQuery。
  • 关于javascript - 克隆/删除输入字段-保持元素ID唯一,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/21489518/

    有关javascript - 克隆/删除输入字段-保持元素ID唯一的更多相关文章

    1. ruby-on-rails - 如何从 format.xml 中删除 <hash></hash> - 2

      我有一个对象has_many应呈现为xml的子对象。这不是问题。我的问题是我创建了一个Hash包含此数据,就像解析器需要它一样。但是rails自动将整个文件包含在.........我需要摆脱type="array"和我该如何处理?我没有在文档中找到任何内容。 最佳答案 我遇到了同样的问题;这是我的XML:我在用这个:entries.to_xml将散列数据转换为XML,但这会将条目的数据包装到中所以我修改了:entries.to_xml(root:"Contacts")但这仍然将转换后的XML包装在“联系人”中,将我的XML代码修改为

    2. ruby - 我可以使用 Ruby 从 CSV 中删除列吗? - 2

      查看Ruby的CSV库的文档,我非常确定这是可能且简单的。我只需要使用Ruby删除CSV文件的前三列,但我没有成功运行它。 最佳答案 csv_table=CSV.read(file_path_in,:headers=>true)csv_table.delete("header_name")csv_table.to_csv#=>ThenewCSVinstringformat检查CSV::Table文档:http://ruby-doc.org/stdlib-1.9.2/libdoc/csv/rdoc/CSV/Table.html

    3. ruby-on-rails - 如何验证非模型(甚至非对象)字段 - 2

      我有一个表单,其中有很多字段取自数组(而不是模型或对象)。我如何验证这些字段的存在?solve_problem_pathdo|f|%>... 最佳答案 创建一个简单的类来包装请求参数并使用ActiveModel::Validations。#definedsomewhere,atthesimplest:require'ostruct'classSolvetrue#youcouldevencheckthesolutionwithavalidatorvalidatedoerrors.add(:base,"WRONG!!!")unlesss

    4. ruby-on-rails - form_for 中不在模型中的自定义字段 - 2

      我想向我的Controller传递一个参数,它是一个简单的复选框,但我不知道如何在模型的form_for中引入它,这是我的观点:{:id=>'go_finance'}do|f|%>Transferirde:para:Entrada:"input",:placeholder=>"Quantofoiganho?"%>Saída:"output",:placeholder=>"Quantofoigasto?"%>Nota:我想做一个额外的复选框,但我该怎么做,模型中没有一个对象,而是一个要检查的对象,以便在Controller中创建一个ifelse,如果没有检查,请帮助我,非常感谢,谢谢

    5. ruby - 我可以使用 aws-sdk-ruby 在 AWS S3 上使用事务性文件删除/上传吗? - 2

      我发现ActiveRecord::Base.transaction在复杂方法中非常有效。我想知道是否可以在如下事务中从AWSS3上传/删除文件:S3Object.transactiondo#writeintofiles#raiseanexceptionend引发异常后,每个操作都应在S3上回滚。S3Object这可能吗?? 最佳答案 虽然S3API具有批量删除功能,但它不支持事务,因为每个删除操作都可以独立于其他操作成功/失败。该API不提供任何批量上传功能(通过PUT或POST),因此每个上传操作都是通过一个独立的API调用完成的

    6. ruby - 为什么 SecureRandom.uuid 创建一个唯一的字符串? - 2

      关闭。这个问题需要detailsorclarity.它目前不接受答案。想改进这个问题吗?通过editingthispost添加细节并澄清问题.关闭8年前。Improvethisquestion为什么SecureRandom.uuid创建一个唯一的字符串?SecureRandom.uuid#=>"35cb4e30-54e1-49f9-b5ce-4134799eb2c0"SecureRandom.uuid方法创建的字符串从不重复?

    7. ruby-on-rails - 在 Rails 和 ActiveRecord 中查询时忽略某些字段 - 2

      我知道我可以指定某些字段来使用pluck查询数据库。ids=Item.where('due_at但是我想知道,是否有一种方法可以指定我想避免从数据库查询的某些字段。某种反拔?posts=Post.where(published:true).do_not_lookup(:enormous_field) 最佳答案 Model#attribute_names应该返回列/属性数组。您可以排除其中一些并传递给pluck或select方法。像这样:posts=Post.where(published:true).select(Post.attr

    8. ruby - 如何安全地删除文件? - 2

      在Ruby中是否有Gem或安全删除文件的方法?我想避免系统上可能不存在的外部程序。“安全删除”指的是覆盖文件内容。 最佳答案 如果您使用的是*nix,一个很好的方法是使用exec/open3/open4调用shred:`shred-fxuz#{filename}`http://www.gnu.org/s/coreutils/manual/html_node/shred-invocation.html检查这个类似的帖子:Writingafileshredderinpythonorruby?

    9. ruby - 在哈希的键数组中追加元素 - 2

      查看我的Ruby代码:h=Hash.new([])h[0]=:word1h[1]=h[1]输出是:Hash={0=>:word1,1=>[:word2,:word3],2=>[:word2,:word3]}我希望有Hash={0=>:word1,1=>[:word2],2=>[:word3]}为什么要附加第二个哈希元素(数组)?如何将新数组元素附加到第三个哈希元素? 最佳答案 如果您提供单个值作为Hash.new的参数(例如Hash.new([]),完全相同的对象将用作每个缺失键的默认值。这就是您所拥有的,那是你不想要的。您可以改用

    10. ruby-on-rails - 标准化文件名的字符串,删除重音和特殊字符 - 2

      我正在尝试找到一种方法来规范化字符串以将其作为文件名传递。到目前为止我有这个:my_string.mb_chars.normalize(:kd).gsub(/[^\x00-\x7F]/n,'').downcase.gsub(/[^a-z]/,'_')但第一个问题:-字符。我猜这个方法还有更多问题。我不控制名称,名称字符串可以有重音符、空格和特殊字符。我想删除所有这些,用相应的字母('é'=>'e')替换重音符号,并将其余的替换为'_'字符。名字是这样的:“Prélèvements-常规”“健康证”...我希望它们像一个没有空格/特殊字符的文件名:“prelevements_routin

    随机推荐