Dear Yii2-framework community.
First of all, I would like to express my appreciation for the work done.
However, since the first version of the framework, I still could not find a fully conventional, “bulletproof” method for solving the problem described below. There were an enormous amount of topics, both here and on SO, but I haven’t found the one, which had an ideal answer.
I would be very happy, if someone could finally help me to solve it, because I face this problem almost in every project.
We have two related models: ContactInfo and ExtraPhoneNumber
public function getExtraPhoneNumbers()
{
return $this->hasMany(ExtraPhoneNumber::className(), ['contact_info_id' => 'id']);
}
The problem is to save parent model ContactInfo with multiple instances of dynamically added child model ExtraPhoneNumber.
What is done (please, feel free to correct me if my approach of adding the row dynamically could be better)
VIEW _form.php:
<div class="contact-info-form">
<?php $form = ActiveForm::begin([
]); ?>
<?= $form->field($model, 'title')->textInput(['maxlength' => true]) ?>
<?= $form->field($model, 'primary_phone')->textInput() ?>
<?php $phoneIndexCounter = 0; foreach($extraPhoneNumbers as $index=>$extraPhoneNumber): ?>
<?php echo $this->render('_extra-phone', [
'model' => $extraPhoneNumber,
'index' => $index,
]); ?>
<?php $phoneIndexCounter++; endforeach; ?>
<?php echo Html::a('<i class="glyphicon glyphicon-plus"></i> Add phone', '#', ['class'=>'add_phone btn btn-success']); ?>
<div class="form-group">
<?= Html::submitButton($model->isNewRecord ? 'Create' : 'Update', ['class' => $model->isNewRecord ? 'btn btn-success' : 'btn btn-primary']) ?>
</div>
<?php ActiveForm::end(); ?>
</div>
<?php $this->registerJs('
$(function(){
var phoneIndexCounter = '.$phoneIndexCounter.';
$(".add_phone").click(function(e){
e.preventDefault();
$.ajax({
url: "/admin/contact-info/add-extra-phone",
type: "get",
data: {
index: phoneIndexCounter++,
},
success: function(data){
$(".add_phone").before(data);
},
error: function(){
alert(Error);
},
});
});
});
');
?>
PARTIAL VIEW _extra-phone.php
<div class="form-group">
<?php
echo Html::activeLabel($model,"[$index]phone");
echo Html::activeTextInput($model,"[$index]phone", ['class' => 'form-control half-width']);
echo Html::error($model,"[$index]phone");
?>
<?php echo Html::a('Delete row', '#', ['class'=>'del_row btn btn-danger', 'onclick' => 'deleteChild(this); return false;']); ?>
</div>
<?php $this->registerJs('
function deleteChild(elm)
{
element=$(elm).parent();
$(element).remove();
};
', \yii\web\View::POS_END); ?>
CONTROLLER ACTION FOR ADDING THE ROW:
public function actionAddExtraPhone($index)
{
$model = new ExtraPhoneNumber();
echo $this->renderPartial('/contact-info/_extra-phone', array(
'model' => $model,
'index' => $index,
), false, true);
}
AND THE MOST DIFFICULT PART - an update action, which doesn’t work:
public function actionUpdate($id)
{
$request = Yii::$app->request->post();
$model = $this->findModel($id);
$extraPhoneNumbers = $model->extraPhoneNumbers;
if($model->load($request) && $model->save())
{
if (Model::loadMultiple($extraPhoneNumbers, $request) && Model::validateMultiple($extraPhoneNumbers)) {
foreach ($extraPhoneNumbers as $extraPhoneNumber) {
$extraPhoneNumber->contact_info_id = $model->id;
$extraPhoneNumber->save(false);
}
} else {
return $this->render('update', [
'model' => $model,
'extraPhoneNumbers' => $extraPhoneNumbers,
]);
}
return $this->redirect(['view', 'id' => $model->id]);
} else {
return $this->render('update', [
'model' => $model,
'extraPhoneNumbers' => $extraPhoneNumbers,
]);
}
}
When I first met the “loadMultiple” function, I’ve decided that this is the Holy Grail and all my dreams come true, but it doesn’t collect the post data for dynamically added model instances. Besides that, my cliendSideValidation doesn’t work for related model.
Furthemore, if parent model has some validation errors, than the data, that was added dynamically, won’t be processed at all and you have to refill it again.
I think, it could be done like:
if($model->load($request) && Model::loadMultiple($extraPhoneNumbers, $request) && $model->save())
but it looks not logical at all and doesn’t solve the main issue.
Please, help me finally understand the situation, so that I could never return and bother you with this issue.
Thank you in advance.