javascript - Cloning/Removing input fields - keeping element id unique -


i working generating dynamic input field inside form. have complex example uses checkboxes , select boxes. has 2 type of elements: main_items , sub_items. mentioned, can add input fields dynamically jquery through clone function replicates new set of input fields unique id attributes. having great difficulty 2 things: first, keeping id’s unique each element duplicated, select boxes. second, have been able first drop down menu work first item have not figured out way other items. 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 input element selectors 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 have         $('#pq_entry_' + num).remove(); // remove last element          // enable "add" button         $('#btnadd').prop('disabled', '');          // if 1 element remains, disable "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 = '';     (var = 1; <= 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> 

so, let's talk how build basic gui applications. before proceed i'd know code below can written in ~20 loc in knockout/angular chose not because wouldn't teach anything.

so, let's talk gui.

it boils down 2 things.

  • presentation - html, css , whatever user directly interacts with.
  • data - actual data , logic.

we want separate them can act independently. want actual representation of user sees in javascript object it'll maintainable, testable readable , on , on. see separation of concerns more information.

let's start data.

so, each thing have in application?

  • a first item, either true or false
  • a sub item either true or false, never true if first item isn't true.
  • a second item either true or false.
  • a number of items number
    • each of these items apple, banana or mango

the intuitive thing start right there.

// our item, we've described :)  function thing(){ //we use object constructor.     this.firstitem = false;     this.subitem = false;     this.seconditem = false;     this.numitems = 0;     this.items = []; // empty list of items } 

well, that's thing, can create them new thing() , set properties, example thing.firstitem = true.

but don't have a thing have stuff. stuff (ordered) bunch of things. ordered collection commonly represented array in javascript, can have:

var stuff = []; // our list var thing = new thing(); // add new item stuff.push(thing); // add thing created our list 

we can of course communicate php when submitting. 1 alternative submitting json object , reading in php (this nice!), alternatively can serialize form params (if have trouble methods in question - let me know).

now have bunch of objects... , headache.

quite astute. far have objects, did not specify behavior anywhere. have our 'data' layer, don't have presentation layer yet. we'll start getting rid of ids , add behavior in.

enter templates!

instead of cloning existing objects we'll want have 'cookie cutter' way create looks of new elements. we'll use template. let's start extracting how 'item list' looks html template. basically, given html it's like:

<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> 

now let's create 'dumb' method showing template on screen.

var template; function renderitem(){     template = template || $("[data-template=item]").html();     var el = $("<div></div>").html(template);     return el; // new element template }  

[here's our first jsfiddle presentation demo](http://jsfiddle.net/rlrtv/, adds 3 items, without behavior screen. read code, see understand , don't afraid ask bits don't understand :)

binding them together

next, we'll add behavior in, when create item, we'll couple thing. can 1 way data binding (where changes in view reflect in model). can implement other direction of binding later if you're interested it's not part of original question brevity let's skip now.

function additem(){     var thing = new thing(); // data     var el = renderitem(); // element     el. // whoops? how find things, removed ids!?!? } 

so, stuck? need append behavior our template normal html templates not have hook have manually. let begin altering our template 'data binding' properties.

<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> 

see data-bind attributes added? let's try selecting on those.

function additem() {     var thing = new thing(); // data     var el = renderitem(); // 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 made faster 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} } 

in this fiddle we've added properties first item , sub item , update elements.

let's proceed same second attribute. it's pretty more of same, binding directly. on side note there several libraries automatically - knockout example

here another fiddle bindings set in, concluded our presentation layer, our data layer , binding.

var template;  function thing() { //we use 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; // new element template }  function additem() {     var thing = new thing(); // data     var el = renderitem(); // 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 made faster 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) {             (var = thing.items.length; < 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     } }  (var = 0; < 3; i++) {     var item = additem();     window.item = item;     $("body").append(item.el); } 

the buttons

the fun thing is, we're done tedious part, buttons piece of cake.

let's add "add" button

 <input type='button' value='add' data-action='add' /> 

and javascript:

var stuff = []; $("[data-action='add']").click(function(e){      var item = additem();      $("body").append(item.el);      stuff.push(item); }); 

boy, that easy.

ok, remove should pretty hard, right?

html:

<input type='button' value='remove' data-action='remove' /> 

js:

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

ok, so pretty sweet. how our data? let's create button shows items on screen?

<input type='button' value='show' data-action='alertdata' /> 

and js

$("[data-action='alertdata']").click(function(e){     var things = stuff.map(function(el){ return el.thing;});     alert(json.stringify(things)); }); 

woah! have actual representation of our data in our model layer. can whatever want it, that's pretty sweet.

what if want submit form? $.param rescue.

<input type='button' value='formdata' data-action='asformdata' /> 

and js:

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

and while format not nice it's php (or other popular technology) gladly read on server side.

so wrap up

  • separate presentation data
  • if have js logic - have single source of truth - javascript objects
  • consider reading more, learn common frameworks knockoutjs or angularjs have interesting less verbose solutions problem (at cost of assumptions).
  • read more ui architecture. this (but hard beginners) resource
  • avoid duplicate ids, they're bad - while you're there don't store data in dom.
  • don't afraid ask question - how learn.
  • you can pretty rid of jquery here.

Comments

Popular posts from this blog

html - Sizing a high-res image (~8MB) to display entirely in a small div (circular, diameter 100px) -

java - IntelliJ - No such instance method -

identifier - Is it possible for an html5 document to have two ids? -