CakePHP Dynamic Input And saveAssociated

Dynamic input is very useful for any project that required dynamic number of inputs for example student can register many subjects and each subject has its own grades. How many subjects and grades input that we as developer should prepare? One student maybe registers one subject or more. Using the dynamic input concept can solve the problem.

Let assume that we have the table set as below:

students

In add new student page, developer need to create an input for grades as shown below:

<fieldset>
    <legend><?php echo __('Grades');?></legend>
		<table cellpadding="0" cellspacing="0" class="table table-striped" id="grade-table">
        <thead>
            <tr>
                <th>Subject</th>
                <th>Grade</th>
                <th>&nbsp;</th>
            </tr>
        </thead>
        <tbody>
		<?php if (!empty($this->request->data['Grade'])) :?>
					<?php for ($key = 0; $key < count($this->request->data['Grade']); $key++) :?>
						<?php echo $this->element('grades', array('key' => $key));?>
					<?php endfor;?>
				<?php endif;?>
		</tbody>
        <tfoot>
            <tr>
                <td colspan="2"></td>
                <td class="actions">
                    <a href="#" class="add btn btn-success btn-xs">Add grade</a>
                </td>
            </tr>
        </tfoot>
    </table>
</fieldset>

<script id="grade-template" type="text/x-underscore-template">
    <?php echo $this->element('grades');?>
</script>


Next, create new file known as grades.ctp in …/View/Elements. It should contain these codes:

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


Load the underscore.js CDN at the beginning of the add new student page as shown below:

<?php echo $this->Html->script(array(
	'https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.9.1/underscore-min.js'
)); ?>


If your CakePHP package does not include jQuery, please add the following code to call the jQuery CDN with underscore CDN as shown:

<?php echo $this->Html->script(array(
    'cdnjs.cloudflare.com/ajax/libs/jquery/1.11.2/jquery.min.js',
    'https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.9.1/underscore-min.js'
)); ?>


Add the following code to create an event handler at the end of the add new student page.

<script>
$(document).ready(function() {
    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>


Figure 1: Dynamic input in student registration (add.ctp) page


Figure 2: View Dynamic input in view.ctp page