Search code examples
yii2yii2-advanced-appyii2-modelyii2-useryii2-validation

Yii2 How to keep modified data when rules fail


I am going mad trying to figure this out. I have a form which loads data from a current user in my database. When I click on submit, the rules are fired and I get my error message, however the new value I entered is overwritten with the old value from the database.

My Controller:

$model = new UserForm();
  
if ($model->load(\Yii::$app->request->post())) {
   if($model->editUser()) {
      return $this->redirect(['/user/user']);
   }
}
        
$rowId = \Yii::$app->getRequest()->getQueryParam('rowId');
$user = User::findOne(['id' => $rowId]);
$optionTitleList = ArrayHelper::map(OptionsTitle::find()->all(), 'id', 'description');
        
return $this->render('/user/user/edit-user',
       [
         'model' => $model,
         'user' => $user,
         'optionTitleList' => $optionTitleList,
       ]);

My Form.

<div class="col-sm-9">
<?= $form->field($model, 'userTitleId')->dropDownList(
        $optionTitleList,
        ['value' => $user->user_title_id,
         'class' => 'form-select form-select-sm border-primary']
      )->label(false)
?>
</div>
<div class="col-sm-9">
<?= $form->field($model, 'userFirstname')->textInput(
        ['value' => $user->user_firstname,
         'placeholder' => 'Enter first name',
         'class' => 'form-control form-control-sm border-primary',
         'maxlength' => '40'])->label(false) 
?>
</div>

As you can see, I hook $model onto the fields for the submit and I use $user to set all current values as is in the DB. I also use $optionTitleList to get a list of Titles from the DB.

Now when I for instance set my title to Miss and I remove the mandatory Firstname value and then submit the form, the validation rules kick in telling me that Firstname is required. However my title is then reset from Miss to the original value from the DB and my Firstname is entered again also from the DB value. How do I retain the values I have entered? I cannot find any example where something like this has been done.


Solution

  • The reason why your edited values are overwritten with the stored values is because you are setting 'value' in your textInput() call to the value stored in DB.

    If you don't set 'value' the actual value from model is used instead and that's what you want.

    So, you should modify your controller's action to first load the user from DB. Then is should set defaults to your UserForm instance. And after that it should load edited data from post.

    $model = new UserForm();
    $rowId = \Yii::$app->getRequest()->getQueryParam('rowId');
    $user = User::findOne(['id' => $rowId]);
    
    // Set up default values
    // I've used null safe operator to deal with case 
    // where user with given ID does not exists. 
    // You can check value returned by User::findOne()
    // and throw `yii\web\NotFoundHttpException` instead.
    $model->userTitleId = $user?->user_title_id;
    $model->userFirstname = $user?->user_firstname
      
    if ($model->load(\Yii::$app->request->post())) {
       if($model->editUser()) {
          return $this->redirect(['/user/user']);
       }
    }
            
    $optionTitleList = ArrayHelper::map(
        OptionsTitle::find()->all(),
        'id',
        'description'
    );
            
    return $this->render(
        '/user/user/edit-user',
        [
            'model' => $model,
            'user' => $user,
            'optionTitleList' => $optionTitleList,
        ]
    );
    

    Then in your view template just leave out 'value' option.

    <div class="col-sm-9">
    <?= $form->field($model, 'userTitleId')->dropDownList(
        $optionTitleList,
        ['class' => 'form-select form-select-sm border-primary']
    )->label(false);
    ?>
    </div>
    <div class="col-sm-9">
    <?= $form->field($model, 'userFirstname')->textInput([
        'placeholder' => 'Enter first name',
        'class' => 'form-control form-control-sm border-primary',
        'maxlength' => '40',
    ])->label(false) 
    ?>
    </div>