Ok, here is how I solved this (a bad solution though).
In this example I use an Address model. Here is the structure of the files:
models/Address.php
controllers/AddressController.php
views/address/create-batch.php
views/address/_form-batch.php
views/address/_form-batch/additional.php
[b]
Controller Logic (AddressController.php):[/b]
/**
* Creates a new array of Address models for batch insert.
* If creation is successful, the browser will be redirected to the 'index' page.
* @return mixed
*/
public function actionCreateBatch()
{
//loading one initial model to array
$model = array();
$model[] = new Address();
//creating dynamic models from post
$postData = Yii::$app->request->post('Address');
if ($postData !== null && is_array($postData)) {
$model = array();
foreach ($postData as $i => $single) {
$model[$i] = new Address();
}
}
//end of creating dynamic models post
//validating + saving models
if(Address::loadMultiple($model, Yii::$app->request->post()) && Address::validateMultiple($model)) {
foreach ($model as $item) {
$item->save(false);
}
return $this->redirect(['index']);
}
//end of validation + saving models
return $this->render('create-batch', [
'model' => $model,
]);
}
/**
* Adds an Additional model to the batch form
* The post data received is from the AJAX call of the Add Another Button
* @throws HttpException
*/
public function actionAddAdditionalRow()
{
if(\Yii::$app->getRequest()->getIsAjax()) {
//this is the post data from the ajax request
$postData = Yii::$app->request->post('Address');
if (empty($postData) || !is_array($postData)) {
throw new HttpException(500, 'An internal server error has occured.');
}
$model = array();
//creating existing model instances + setting attributes
foreach ($postData as $i => $single) {
$model[$i] = new Address();
$model[$i]->setAttributes($single);
}
//end of creating existing model instances + setting attributes
//creating an additional empty model instance
$model[] = new Address();
// it has to be renderAjax in order to include the script validation files
echo $this->renderAjax('_form-batch/additional', array("model" => $model));
exit;
} else {
throw new HttpException(404, 'Unable to resolve the request: address/add-additional-row');
}
}
create-batch.php:
<?php
use yii\helpers\Html;
/**
* @var yii\web\View $this
* @var app\models\Address $model
*/
$this->title = 'Create Address';
$this->params['breadcrumbs'][] = ['label' => 'Addresses', 'url' => ['index']];
$this->params['breadcrumbs'][] = $this->title;
?>
<div class="address-create">
<h1><?= Html::encode($this->title) ?></h1>
<?= $this->render('_form-batch', [
'model' => $model,
]) ?>
</div>
[b]
_form-batch.php:[/b]
<?php
use yii\helpers\Html;
use yii\widgets\ActiveForm;
/**
* @var yii\web\View $this
* @var app\models\Address $model
* @var yii\widgets\ActiveForm $form
*/
?>
<div id="div-address-form" class="address-form">
<?php $form = ActiveForm::begin(); ?>
<div class="row">
<?php foreach ($model as $i => $single): ?>
<div class="col-md-4">
<?= $form->field($single, "[$i]city")->textInput(['maxlength' => 35]) ?>
<?= $form->field($single, "[$i]address_line_one")->textInput(['maxlength' => 255]) ?>
<?= $form->field($single, "[$i]adress_line_two")->textInput(['maxlength' => 255]) ?>
<?= $form->field($single, "[$i]address_line_three")->textInput(['maxlength' => 255]) ?>
<?= $form->field($single, "[$i]telephone")->textInput(['maxlength' => 20]) ?>
</div>
<?php endforeach; ?>
</div>
<div class="form-group">
<button type="button" id="button-add-another" class="btn btn-primary">Add Another</button>
</div>
<div class="form-group">
<?= Html::submitButton('Create', ['class' => 'btn btn-success']) ?>
</div>
<?php ActiveForm::end(); ?>
</div>
<?php
//The AJAX request for the Add Another button. It updates the #div-address-form div.
$this->registerJs("
$(document).on('click', '#button-add-another', function(){
$.ajax({
url: '" . \Yii::$app->urlManager->createUrl(['address/add-additional-row']) . "',
type: 'post',
data: $('#" . $form->id . "').serialize(),
dataType: 'html',
success: function(data) {
$('#div-address-form').html(data);
},
error: function() {
alert('An error has occured while adding a new block.');
}
});
});
"); ?>
_form-batch/additional.php:
<?php
use yii\helpers\Html;
use yii\widgets\ActiveForm;
/**
* Updates the #div-address-form through AJAX. (called from address/add-additional-row)
* @var yii\web\View $this
* @var app\models\Address $model
* @var yii\widgets\ActiveForm $form
*/
?>
<!-- Pay attention to the action URL otherwise it would default to address/add-additional-row -->
<?php $form = ActiveForm::begin(['action' => \Yii::$app->urlManager->createUrl(['address/create-batch'])]); ?>
<div class="row">
<?php foreach ($model as $i => $single): ?>
<div class="col-md-4">
<?= $form->field($single, "[$i]city")->textInput(['maxlength' => 35]) ?>
<?= $form->field($single, "[$i]address_line_one")->textInput(['maxlength' => 255]) ?>
<?= $form->field($single, "[$i]adress_line_two")->textInput(['maxlength' => 255]) ?>
<?= $form->field($single, "[$i]address_line_three")->textInput(['maxlength' => 255]) ?>
<?= $form->field($single, "[$i]telephone")->textInput(['maxlength' => 20]) ?>
</div>
<?php endforeach; ?>
</div>
<div class="form-group">
<button type="button" id="button-add-another" class="btn btn-primary">Add Another</button>
</div>
<div class="form-group">
<?= Html::submitButton('Create', ['class' => 'btn btn-success']) ?>
</div>
<?php ActiveForm::end(); ?>
Overall, this is how it works.
1.) index.php?r=address/create-batch is called (AddressController).
2.) In actionCreateBatch(), an array of Address models is created, with only one model added to the array in the beginning.
3.) _form-batch.php is rendered with the Address models cycled in foreach.
4.) The Add Another button calls actionAddAdditionalRow() through AJAX and sends the serialized form through post.
5.) In actionAddAdditionalRow(), a new array of Address Models is created based on the serialized form and the attributes are set.
6.) An additional empty Address model is added to the array.
7.) actionAddAdditionalRow() renders additional.php, therefore replacing the #div-address-form div with the new form.
8.) Check the screenshots.
Now, even though this works, here are some reasons why this is a bad solution.
1.) The code repeats. The form code in _form-batch.php and additional.php is the same.
2.) Scripts are added after the AJAX call and therefore they overlap with the initial scripts added before the </body>
3.) If renderPartial is used instead of renderAjax and the scripts are not included, client-side validation doesn’t work for the additional blocks.
4.) Every time the "Add Additional" button is clicked, the validation messages disappear.
So help me out guys, I know I have overcomplicated my code but it was the only solution I could come up with. Can you improve it?