3

I have a invoice table and an invoice_item table. Each Invoice hasMany invoiceItem.

When creating an invoice the user is presented with a form with the invoice form fields and also a row with invoiceItem form fields.

What I want to do is to have a "add new item" link that dynamically (jQuery, AJAX) adds a new row of the item fields. User should be able to add as many rows as they want and each new row should appear below the last row.

Of course the row field attributes must also be correct so that the data can easily be inserted with the saveAll method.

What is the best and most proper way to accomplish this with CakePHP? I am using CakePHP 2.4.7.

user3504209
  • 41
  • 1
  • 4

3 Answers3

1

Here's how I did it with data containing a hidden id, a label and an input field, all wrapped up in a fieldset. You could use actual tables to wrap it up.

Here's the generated HTML for two sets of fields and the link to click to add a row:

   <fieldset>
   <input type="hidden" name="data[VmfrDesignatedIncome][0][id]" value="668" id="VmfrDesignatedIncome0Id"/>
   <div class="input text">
   <label for="VmfrDesignatedIncome0Designation">Designation</label>
   <input name="data[VmfrDesignatedIncome][0][designation]" value="blah" maxlength="512" type="text" id="VmfrDesignatedIncome0Designation"/></div>
   </fieldset>

    <fieldset>
    <input type="hidden" name="data[VmfrDesignatedIncome][1][id]" value="669" id="VmfrDesignatedIncome1Id"/>
    <div class="input text">
    <label for="VmfrDesignatedIncome1Designation">Designation</label>  

    <input name="data[VmfrDesignatedIncome][1][designation]" value="blah2" maxlength="512" type="text" id="VmfrDesignatedIncome1Designation"/></div>
    </fieldset>

     <a href="#" id="addrow">Add row</a>

and here's the Javascript which clones the last fieldset on the page, and then modifies the id, name and field values to increase the number in them by one. Note that the selectors have to accurately select each label or field using the > child selector.

/* As the strings to the function below may have [ or ]
** we want to stop it being treated as a regexp
*/
RegExp.quote = function(str) {
     return str.replace(/([.?*+^$[\]\\(){}|-])/g, "\\$1");
 };

function findNumberAddOne(attributeString) {
    /*Finds the number in the given string
    ** and returns a string with that number increased by one
    */
    var re = new RegExp("(.*)([0-9])(.*)");
    var nPlusOne = attributeString.replace(re, "$2")+"+1";
    var newstr = attributeString.replace(re, "$1")+eval(nPlusOne)+attributeString.replace(re, "$3");
    return newstr;
}

$(document).ready(function() {
/* Duplicate the last set of designated income fields and increase the relevants id/name etc.
** so that it works as a new row in the table when saved*/
    $('#addrow').click(function() { 
        $('fieldset:last').clone().insertAfter('fieldset:last');
        $('fieldset:last > input').attr('id',findNumberAddOne($('fieldset:last > input').attr('id')));
        $('fieldset:last > input').attr('value',''); /*Blank out the actual value*/
        $('fieldset:last > input').attr('name',findNumberAddOne($('fieldset:last > input').attr('name')));
        $('fieldset:last > div > label').attr('for',findNumberAddOne($('fieldset:last > div > label').attr('for')));
        $('fieldset:last > div > input').attr('id',findNumberAddOne($('fieldset:last > div > input').attr('id')));
        $('fieldset:last > div > input').attr('value',''); /*Blank out the actual value*/
        $('fieldset:last > div > input').attr('name',findNumberAddOne($('fieldset:last > div > input').attr('name')));
    });
});
paulmorriss
  • 2,579
  • 25
  • 30
0

Please check Dynamic form input fields in CakePHP tutorial. I used same for CakePHP 2.x and also for CakePHP 3.x. One problem in this tutorial is dynamic field ID creation. Each time it create same ID for same dynamic fields, like create GradeSubject for both Grade.0.subject and Grade.1.subject fields. If you need dynamic ID for each fields, you can modify this tutorial as like below.

According to tutorial just change View/Elements/grades.ctp content as:

<?php
    $key = isset($key) ? $key : '{{ key }}';
    // I changed <%= key %> to {{ key }}
?>
<tr>
    <td>
        <?php echo $this->Form->hidden("Grade.{$key}.id") ?>
        <?php echo $this->Form->text("Grade.{$key}.subject",array("id"=>"Grade{$key}Subject")); ?>
    </td>   
    <td>
        <?php echo $this->Form->select("Grade.{$key}.grade", array(
            'A+',
            'A',
            'B+',
            'B',
            'C+',
            'C',
            'D',
            'E',
            'F'
        ), array(
            'empty' => '-- Select grade --',
            "id"=>"Grade{$key}Grade"
        )); ?>
    </td>
    <td class="actions">
        <a href="#" class="remove">Remove grade</a>
    </td>
</tr>

And also change your add.ctp javascript code as:

<script>
    $(document).ready(function() {
        //I changed undescore default template settings
        _.templateSettings = {
          interpolate: /\{\{(.+?)\}\}/g
        }

        var
            gradeTable = $('#grade-table'),
            gradeBody = gradeTable.find('tbody'),
            gradeTemplate = _.template($('#grade-template').remove().text()),
            numberRows = gradeTable.find('tbody > tr').length;

        gradeTable
            .on('click', 'a.add', function(e) {
                e.preventDefault();

                $(gradeTemplate({key: numberRows++}))
                    .hide()
                    .appendTo(gradeBody)
                    .fadeIn('fast');
            })
            .on('click', 'a.remove', function(e) {
                    e.preventDefault();

                $(this)
                    .closest('tr')
                    .fadeOut('fast', function() {
                        $(this).remove();
                    });
            });

            if (numberRows === 0) {
                gradeTable.find('a.add').click();
            }
    });
</script>
monsur.hoq
  • 1,135
  • 16
  • 25
  • What changes are required to get this working in CakePHP 3 please? – pmelon Jul 25 '16 at 09:10
  • @pmelon no basic changes required for CakePHP 3. No just arrange input fields as per your demand. – monsur.hoq Jul 27 '16 at 16:56
  • I am struggling (I am not a PHP coder by trade) to get this to work - it's all fine but no grades information is saved. I assume it is because of my controller. Could anyone please post an example CakePHP 3 controller for the http://waltherlalk.com/blog/dynamic-form-input-fields tutorial? – pmelon Sep 13 '16 at 11:19
-1

the simplest idea got in my mind

1- make your items input with name array (<input type="text" name="items[]"/>)

2- create a button with class "addmore"

3- use jquery clone to duplicate the input on the click event on that button

I think this will help

Mohamed Melouk
  • 182
  • 2
  • 11