You are viewing revision #6 of this wiki article.
This version may not be up to date with the latest version.
You may want to view the differences to the latest version or see the changes made in this revision.
Problem Statement ¶
How do you read and handle tabular data submission via form in Yii framework 2.0? 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.
Solution ¶
If you are coming over from Yii 1 - the concepts of tabular data handling remain the same as mentioned in the Yii 1.x guide - collecting tabular input section.
The only differences in Yii 2 is that its much simpler due to available functions in the Model class for loading and validating models.
OPTION 1: USING A PREBUILT SOLUTION ¶
You can use a prebuilt solution to render and manage tabular data. You can use the TabularForm widget.
OPTION 2: DOING IT YOURSELF ¶
You can use the same concepts as mentioned in the guide link mentioned before - here are the changes you need to do for Yii 2:
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'].
Batch Update Controller Action ¶
use yii\base\Model;
public function actionBatchUpdate()
{
// retrieve items to be updated in a batch mode assuming each item
// is of model class 'Item'.
// Note the getItemsToUpdate method is an example method, where you
// fetch the valid models to update in your tabular form. You need
// to write such a method OR directly call your code to get the models.
$items=$this->getItemsToUpdate();
if (Model::loadMultiple($items, Yii::$app->request->post()) &&
Model::validateMultiple($items)) {
$count = 0;
foreach ($items as $item) {
// populate and save records for each model
if ($item->save()) {
// do something here after saving
$count++;
}
}
Yii::$app->session->setFlash('success', "Processed {$count} records successfully.");
return $this->redirect(['index']); // redirect to your next desired page
} else {
return $this->render('update', [
'items' => $items,
]);
}
}
Your View File Sample ¶
<div class="form">
<?php $form = ActiveForm::begin(); ?>
<table>
<tr><th>Name</th><th>Price</th><th>Count</th><th>Description</th></tr>
<?php foreach($items as $i=>$item): ?>
<tr>
<td><?= $form->field($item,"[$i]name"); ?></td>
<td><?= $form->field($item,"[$i]price"); ?></td>
<td><?= $form->field($item,"[$i]count"); ?></td>
<td><?= $form->field($item,"[$i]description"); ?></td>
</tr>
<?php endforeach; ?>
</table>
<?= Html::submitButton('Save'); ?>
<?php ActiveForm::end(); ?>
</div><!-- form -->
Model File
Hi,
Can you please give me example for implementing getItemsToUpdate() function.
$items=$this->getItemsToUpdate();
Thanks,
Thani.
Inline validation for dynamic instances
How can I configure my code so that dynamic instances are validated inline, before the first submit? Does Yii offer some solution to it or must I create my own with AJAX and yiiActiveForm.js?
When validation fails, errors are not rendered
Great tutorial. Although when validation for given field (e.g.
name
is required) fails, even after re-rendering, the errors are not shown.<?= $form->field($item,"[$i]name"); ?>
Ended with error
I ended up with error:
About getItemsToUpdate method
It is nice the mention about
getItemsToUpdate
method in the comment in the last update of this article, but I think, it would be better and more informative to regard an example(s) of defining it.Thank you for the last update.
Should not that for loop be inside a transaction?
If any of the save inside the iteration fail, you need to everything gets undone right? shouldn't you need to enclose the for loop in a transaction?
What to do with: $items=$this->getItemsToUpdate();
@Thani, @Said Bakr
I to was really confused by the omission of this detail around:
Re $items=$this->getItemsToUpdate();
Just then it dawned on my what to do and how to use it, the documentation does talk about it here, but it is not very clear what it is saying.
http://www.yiiframework.com/doc-2.0/guide-input-tabular-input.html#updating-a-fixed-set-of-records
Basically for an update you obviously need to get a list of the records that need updating so you will already be doing something like this:
$items = Item::find()->where(['probably_a_foreign_key_id'] => $some_id)->indexBy('id')->all();
What I failed to realise is that loadMultiple is not after an empty array but rather a array of your existing items you are hoping to update, and that is probably why the indexBy is important. Once you have got your matching records, you pass that to loadMultiple followed by the $_POST form details that will be used to update your existing models, makes sense when I think about it.
The indexBy is very important I believe to make sure you are updating the records correctly when doing massive assignment and looping over each model that takes place when using loadMultiple.
Hope this helps.
If you have any questions, please ask in the forum instead.
Signup or Login in order to comment.