Collecting Tabular Input

Sometimes we want to collect user input in a batch mode. That is, the user can enter the information for multiple model instances and submit them all at once. We call this tabular input because the input fields are often presented in an HTML table.

To work with tabular input, we first need to create or populate an array of model instances, depending on whether we are inserting or updating the data. We then retrieve the user input data from the $_POST variable and assign it to each model. A slight difference from single model input is that we retrieve the input data using $_POST['ModelClass'][$i] instead of $_POST['ModelClass'].

public function actionBatchUpdate()
{
    // retrieve items to be updated in a batch mode
    // assuming each item is of model class 'Item'
    $items=$this->getItemsToUpdate();
    if(isset($_POST['Item']))
    {
        $valid=true;
        foreach($items as $i=>$item)
        {
            if(isset($_POST['Item'][$i]))
                $item->attributes=$_POST['Item'][$i];
            $valid=$item->validate() && $valid;
        }
        if($valid)  // all items are valid
            // ...do something here
    }
    // displays the view to collect tabular input
    $this->render('batchUpdate',array('items'=>$items));
}

Having the action ready, we need to work on the batchUpdate view to display the input fields in an HTML table.

<div class="form">
<?php echo CHtml::beginForm(); ?>
<table>
<tr><th>Name</th><th>Price</th><th>Count</th><th>Description</th></tr>
<?php foreach($items as $i=>$item): ?>
<tr>
<td><?php echo CHtml::activeTextField($item,"[$i]name"); ?></td>
<td><?php echo CHtml::activeTextField($item,"[$i]price"); ?></td>
<td><?php echo CHtml::activeTextField($item,"[$i]count"); ?></td>
<td><?php echo CHtml::activeTextArea($item,"[$i]description"); ?></td>
</tr>
<?php endforeach; ?>
</table>
 
<?php echo CHtml::submitButton('Save'); ?>
<?php echo CHtml::endForm(); ?>
</div><!-- form -->

Note in the above that we use "[$i]name" instead of "name" as the second parameter when calling CHtml::activeTextField.

If there are any validation errors, the corresponding input fields will be highlighted automatically, just like the single model input we described earlier on.

$Id$

Total 2 comments

#15615 report it
idutta at 2013/11/28 03:34pm
Make sure $this->getItemsToUpdate returns the records in the right order

In the above example, you must implement the method **getItemsToUpdate ** carefully to return the records to be updated in the correct order. One possible implementation will be as follows:

public function getItemsToUpdate() {
        // Create an empty list of records
        $items = array();
 
        // Iterate over each item from the submitted form
        if (isset($_POST['Item']) && is_array($_POST['Item'])) {
            foreach ($_POST['Item'] as $item) {
                // If item id is available, read the record from database 
                if ( array_key_exists('id', $item) ){
                    $items[] = MyModel::model()->findByPk($item['id']);
                }
                // Otherwise create a new record
                else {
                    $items[] = new MyModel();
                }
            }
        }
        return $items;
    }
#7034 report it
lunatic77 at 2012/02/20 11:40am
take caution to use the PK as the index

When fetching the items to use in the view iteration, one should set the criteria index to the attribute that is the PK. Example:

$items = Item::model()->findAll(array('index'=>'id'));

This will ensure that, in the following iteration

foreach($items as $i=>$item)

$i will be the primary key that relates to the $item.

Otherwise, the key of the array that findAll() returns will be sequentially assigned, and therefore it would be useless to know which record you're really updating at the controller level.

Leave a comment

Please to leave your comment.