Based on David Carr's excessively simple technique to duplicate form sections using JavaScript, here is my enhanced but still minimalist take, adding support for:
- select multiple
- starting the form with several sections already
- easy backend usage
This changes indices so they start at 0.
Duplicate Form Sections
//define template var template = $('#sections .section:first').clone(); //define counter var sectionsCount = jQuery('div.section:last').data('number'); function incrementIdentifier(id, sectionsCount) { if (id.substr(-1) == '0') { id = id.substr(0, id.length - 1); } return id + sectionsCount; } //add new section $('body').on('click', '.addsection', function() { //increment sectionsCount++; //loop through each input var section = template.clone().find(':input').each(function(){ //set id to store the updated section number newId = incrementIdentifier(this.id, sectionsCount); //update for label $(this).prev().attr('for', newId); //update id this.id = newId; var baseName = this.name; var array = false; if (baseName.substr(-2) == '[]') { array = true; baseName = baseName.substr(0, baseName.length - 2); } baseName = incrementIdentifier(baseName, sectionsCount); if (array) { baseName = baseName + '[]'; } this.name = baseName; }).end() //inject new section .appendTo('#sections'); jQuery('input[name=lastSection]').val(sectionsCount); return false; }); //remove section $('#sections').on('click', '.remove', function() { //fade out section $(this).parent().fadeOut(300, function(){ //remove parent element (main section) $(this).parent().parent().empty(); return false; }); return false; });
As a bonus, the PHP function I quickly crafted to ease processing the resulting $_REQUEST:
<?php /** * Treat $_REQUEST when working with duplicate-form-sections.js * $_REQUEST['numberedParameters'] contains the base name of the parameters to be converted into arrays * $_REQUEST['lastSection'] is the number of the last section (the number of sections minus 1) * Alters neither $_GET nor $_POST * @author Philippe Cloutier * @license MIT * @return boolean false on failure */ function numberedParametersToArrays() { if (! isset($_REQUEST['lastSection'])) { return false; } $numberedParameters = $_REQUEST['numberedParameters']; $lastSection = $_REQUEST['lastSection']; foreach ($numberedParameters as $numberedParameter) { $_REQUEST[$numberedParameter] = array(); for ($i = 0; $i <= $lastSection; $i++) { if (isset($_REQUEST[$numberedParameter . $i])) { $_REQUEST[$numberedParameter][] = $_REQUEST[$numberedParameter . $i]; } else { $_REQUEST[$numberedParameter][] = array(); } // We could unset the numbered parameters. } } return true; } ?>
And here is an adjusted usage example:
<form> <div id="sections"> <div class="section" data-number="0"> <fieldset> <legend>User</legend> <p> <label for="firstName">First Name:</label> <input name="firstName0" id="firstName0" value="" type="text" /> </p> <p> <label for="lastName">Last Name:</label> <input name="lastName0" id="lastName0" value="" type="text" /> </p> <p> Friends: <select name="friends1[]" id="friends1" size="2" multiple> <option>Alice</option> <option>Bob</option> </select> </p> <p><a href="#" class='remove'>Remove Section</a></p> </fieldset> </div> </div> <p><a href="#" class='addsection'>Add Section</a></p> <input type="hidden" name="numberedParameters[]" value="firstName"> <input type="hidden" name="numberedParameters[]" value="lastName"> <input type="hidden" name="numberedParameters[]" value="friends"> <input type="hidden" name="lastSection" value="0"> </form>
Note that there are similar solutions elsewhere. I am not saying this one is better than these.