Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

jquery clone form fields and increment id

I have a block of form elements which I would like to clone and increment their ID's using jQuery clone method. I have tried a number of examples but a lot of them only clone a single field.

My block is structured as such:

<div id="clonedInput1" class="clonedInput">   <div>     <div>       <label for="txtCategory" class="">Learning category <span class="requiredField">*</span></label>       <select class="" name="txtCategory[]" id="category1">         <option value="">Please select</option>       </select>     </div>    <div>      <label for="txtSubCategory" class="">Sub-category <span class="requiredField">*</span></label>      <select class="" name="txtSubCategory[]" id="subcategory1">        <option value="">Please select category</option>      </select>    </div>    <div>      <label for="txtSubSubCategory">Sub-sub-category <span class="requiredField">*</span></label>      <select name="txtSubSubCategory[]" id="subsubcategory1">        <option value="">Please select sub-category</option>      </select>    </div> </div> 

Obviously elements are lined up a lot better but you get the idea.

I would like to keep the id structure i.e. category1, subcategory1 etc as I use these to dynamically display select options based on the parent selection so if its possible to have each cloned block like category1/category2/category3 etc that would be great.

like image 921
puks1978 Avatar asked Nov 05 '11 04:11

puks1978


2 Answers

HTML

<div id="clonedInput1" class="clonedInput">     <div>         <label for="txtCategory" class="">Learning category <span class="requiredField">*</span></label>         <select class="" name="txtCategory[]" id="category1">             <option value="">Please select</option>         </select>     </div>     <div>         <label for="txtSubCategory" class="">Sub-category <span class="requiredField">*</span></label>         <select class="" name="txtSubCategory[]" id="subcategory1">             <option value="">Please select category</option>         </select>     </div>     <div>         <label for="txtSubSubCategory">Sub-sub-category <span class="requiredField">*</span></label>         <select name="txtSubSubCategory[]" id="subsubcategory1">             <option value="">Please select sub-category</option>         </select>     </div>     <div class="actions">         <button class="clone">Clone</button>          <button class="remove">Remove</button>     </div> </div> 

JavaScript - Jquery v1.7 and earlier

var regex = /^(.+?)(\d+)$/i; var cloneIndex = $(".clonedInput").length;  $("button.clone").live("click", function(){     $(this).parents(".clonedInput").clone()         .appendTo("body")         .attr("id", "clonedInput" +  cloneIndex)         .find("*").each(function() {             var id = this.id || "";             var match = id.match(regex) || [];             if (match.length == 3) {                 this.id = match[1] + (cloneIndex);             }     });     cloneIndex++; }); 

There is only one silly part :) .attr("id", "clonedInput" + $(".clonedInput").length) but it works ;)

JAvascript - JQuery recent (supporting .on())

var regex = /^(.+?)(\d+)$/i; var cloneIndex = $(".clonedInput").length;  function clone(){     $(this).parents(".clonedInput").clone()         .appendTo("body")         .attr("id", "clonedInput" +  cloneIndex)         .find("*")         .each(function() {             var id = this.id || "";             var match = id.match(regex) || [];             if (match.length == 3) {                 this.id = match[1] + (cloneIndex);             }         })         .on('click', 'button.clone', clone)         .on('click', 'button.remove', remove);     cloneIndex++; } function remove(){     $(this).parents(".clonedInput").remove(); } $("button.clone").on("click", clone);  $("button.remove").on("click", remove); 

working example here

like image 70
Milan Jaric Avatar answered Sep 28 '22 17:09

Milan Jaric


Another option would be to use a recursive function:

// Accepts an element and a function function childRecursive(element, func){     // Applies that function to the given element.     func(element);     var children = element.children();     if (children.length > 0) {         children.each(function (){             // Applies that function to all children recursively             childRecursive($(this), func);         });     } } 

Then you can make a function or three for setting the attributes and values of your yet-to-be-cloned form fields:

// Expects format to be xxx-#[-xxxx] (e.g. item-1 or item-1-name) function getNewAttr(str, newNum){     // Split on -     var arr = str.split('-');     // Change the 1 to wherever the incremented value is in your id     arr[1] = newNum;     // Smash it back together and return     return arr.join('-'); }  // Written with Twitter Bootstrap form field structure in mind // Checks for id, name, and for attributes. function setCloneAttr(element, value){     // Check to see if the element has an id attribute     if (element.attr('id') !== undefined){         // If so, increment it         element.attr('id', getNewAttr(element.attr('id'),value));     } else { /*If for some reason you want to handle an else, here you go*/ }     // Do the same with name...     if(element.attr('name') !== undefined){         element.attr('name', getNewAttr(element.attr('name'),value));     } else {}     // And don't forget to show some love to your labels.     if (element.attr('for') !== undefined){         element.attr('for', getNewAttr(element.attr('for'),value));     } else {} }  // Sets an element's value to '' function clearCloneValues(element){     if (element.attr('value') !== undefined){         element.val('');     } } 

Then add some markup:

<div id="items">     <input type="hidden" id="itemCounter" name="itemCounter" value="0">     <div class="item">         <div class="control-group">             <label class="control-label" for="item-0-name">Item Name</label>             <div class="controls">                 <input type="text" name="item-0-name" id="item-0-name" class="input-large">             </div>         </div><!-- .control-group-->         <div class="control-group">             <label for="item-0-description" class="control-label">Item Description</label>             <div class="controls">                 <input type="text" name="item-0-description" id="item-0-description" class="input-large">             </div>         </div><!-- .control-group-->     </div><!-- .item --> </div><!-- #items -->  <input type="button" value="Add Item" id="addItem"> 

And then all you need is some jQuery goodness to pull it all together:

$(document).ready(function(){     $('#addItem').click(function(){         //increment the value of our counter         $('#itemCounter').val(Number($('#allergyCounter').val()) + 1);         //clone the first .item element         var newItem = $('div.item').first().clone();         //recursively set our id, name, and for attributes properly         childRecursive(newItem,              // Remember, the recursive function expects to be able to pass in             // one parameter, the element.             function(e){                 setCloneAttr(e, $('#itemCounter').val());         });         // Clear the values recursively         childRecursive(newItem,              function(e){                 clearCloneValues(e);             }         );         // Finally, add the new div.item to the end         newItem.appendTo($('#items'));     }); }); 

Obviously, you don't necessarily need to use recursion to get everything if you know going in exactly what things you need to clone and change. However, these functions allow you to reuse them for any size of nested structure with as many fields as you want so long as they're all named with the right pattern.

There's a working jsFiddle here.

like image 41
Craig Burton Avatar answered Sep 28 '22 18:09

Craig Burton