Search code examples
jqueryhtmlhtml-tableclonerows

Cloning multiple table rows using jQuery


I want to have a form that contains a table of fields and some of the fields I would like the user to be able to duplicate. Imagine an invoice that has some header fields (i.e. Invoice Date, Customer Name, etc) and then there are multiple detail lines (i.e. Product, Qty, Price) for which there may be any number of, and finally some footer fields (i.e. Tax, Total, etc). I want to initially show the form with one detail line and let the user click on a button to add more detail lines, and I'd like to do this using jQuery.

I've found multiple examples that show how to clone a single table row, but I would like to make something that is more flexible; I want to be able to specify several table rows and have them cloned when a button is clicked.

So far I've got the following code:

<form action="" method="post">
  <table border="0" align="center" cellpadding="2" cellspacing="1" id="invoice">
    <tr>
      <td>Name:</td>
      <td><input name="name" type="text" id="name" size="50" maxlength="100"></td>
    </tr>
    <tbody class="multi">
      <tr>
        <td>Product:</td>
        <td><input name="product[]" type="text" size="50" maxlength="50"></td>
      </tr>
      <tr>
        <td>Qty:</td>
        <td><input name="qty[]" type="text" size="5" maxlength="5"></td>
      </tr>
      <tr>
        <td>Price:</td>
        <td><input name="price[]" type="text" size="10" maxlength="10"></td>
      </tr>
    </tbody>
    <tr>
      <td>Tax:</td>
      <td><input name="tax" type="text" id="tax" size="10" maxlength="10"></td>
    </tr>
      <td>&nbsp;</td>
      <td><input type="button" id="addline" value="Add Another Line">
        <input name="Submit" type="submit" value="Submit Invoice"></td>
    </tr>
  </table>
</form>

This HTML has been shorted for this example but you will see that I specify the table rows that are to be cloned by putting them in a tbody with a class of "multi". I'll be using PHP to handle the submitted form, hence the []'s on the end of the fields that are to be duplicated (this will use PHP's array handling features). There may be any number of table rows above and below the rows that are to be duplicated, but I've only shown one for brevity.

I then have some JavaScript that looks like this:

$(document).ready(function($) {
  $('#addline').click(function() {
    $('#invoice > tbody.multi').clone().insertAfter('#invoice > tbody.multi');
  });
});

This is meant to select the whole block of rows that I want duplicated and then clone it and insert it immediately after the duplicate block.

This works, but the problem I have is that after clicking the Add Line button I end up with two tbody blocks, both with the class of "multi". So the second time the Add Line button is clicked I get both tbody blocks cloned, ending up with four tbody's!

What is the best way to fix this? Should I attempt to change the class of the newly duplicated tbody so it won't be included in future duplication? Or is there a better way to handle this entirely?

Remember, I want to be able to duplicate several table rows at once, not just one row as I've seen in lots of examples. Also I've seen in some examples that they include all of the table and form HTML that is to be duplicated within the jQuery code, which seems like an untidy way to do things to me. And finally it would be nice to clear the values of any duplicated fields and to be able to delete duplicated blocks.


Solution

  • If you want multiple <tbody>s (which is fine and keeps the HTML nicely organized), then just mix in :last to select only the last tbody.multi when cloning and again to append the clone at the end:

    $('#addline').click(function() {
        $('#invoice > tbody.multi:last').clone().insertAfter('#invoice > tbody.multi:last');
    });​
    

    Then you can clear the cloned inputs with a simple val call:

    $('#addline').click(function() {
        var clone = $('#invoice > tbody.multi:last').clone();
        clone.find('input').val('');
        clone.insertAfter('#invoice > tbody.multi:last');
    });
    

    Demo: http://jsfiddle.net/aeYED/1/