Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

yii2 - Kartik file input - update

This is the situation: I'm new on Yii2 and wanted to use some file uploader widget within ActiveForm.. so far I've found this excelent one: \kartik\widget\FileInput

With this widget I can manage file upload and then, when enter in edit mode, show the previous uploaded image with the oportunite to replace it.

The problem is that if I press the "Update" button of the form without modifying the image yii says that the image "can't be empty" because I've set the 'required' rule in my model.

like image 624
edrian Avatar asked Mar 27 '15 02:03

edrian


2 Answers

After an awful afternoon and a more productive night, I've encountered a solution that worked for me..

The main problem was that file input don't send its value (name of the file stored in database) when updating. It only sends the image info if browsed and selected through file input..

So, my workaround was creating another "virtual" field for managing file upload, named "upload_image". To achieve this I simple added a public property with this name to my model class: public $upload_image;

I also add the folowing validation to rules method on Model class:

 public function rules()
{
    return [
        [['upload_image'], 'file', 'extensions' => 'png, jpg', 'skipOnEmpty' => true],
        [['image'], 'required'],
    ];
}

Here, 'upload_image' is my virtual column. I added 'file' validation with 'skipOnEmpty' = true, and 'image' is the field on my database, that must be required in my case.

Then, in my view I configured 'upload_image' widget like follows:

    echo FileInput::widget([
        'model' => $model,
        'attribute' => 'upload_image',
        'pluginOptions' => [
            'initialPreview'=>[
                Html::img("/uploads/" . $model->image)
            ],
            'overwriteInitial'=>true
        ]
    ]);

In 'initialPreview' option I asign my image name, stored in '$model->image' property returned from database.

Finally, my controller looks like follow:

public function actionUpdate($id)
{
    $model = $this->findModel($id);
    $model->load(Yii::$app->request->post());

    if(Yii::$app->request->isPost){
        //Try to get file info
        $upload_image = \yii\web\UploadedFile::getInstance($model, 'upload_image');

        //If received, then I get the file name and asign it to $model->image in order to store it in db
        if(!empty($upload_image)){
            $image_name = $upload_image->name;
            $model->image = $image_name;
        }

        //I proceed to validate model. Notice that will validate that 'image' is required and also 'image_upload' as file, but this last is optional
        if ($model->validate() && $model->save()) {

            //If all went OK, then I proceed to save the image in filesystem
            if(!empty($upload_image)){
                $upload_image->saveAs('uploads/' . $image_name);
            }

            return $this->redirect(['view', 'id' => $model->id]);
        }
    }
    return $this->render('update', [
        'model' => $model,
    ]);
}
like image 130
edrian Avatar answered Sep 23 '22 10:09

edrian


I have encountered another solution by creating scenarios. In your case I would modify the rules like this:

public funtion rules() {
    [['image'], 'file'],
    [['image'], 'required', 'on'=> 'create']
}

So the fileupload field will be required only in create action. In update action I have this code:

public function actionUpdate($id)
    {
      $model = $this->findModel($id);

        if ($model->load(Yii::$app->request->post())) {

          $newCover = UploadedFile::getInstance($model, 'image');

          if (!empty($newCover)) {
            $newCoverName = Yii::$app->security->generateRandomString();
            unlink($model->cover);
            $model->cover = 'uploads/covers/' . $newCoverName . '.' . $newCover->extension;
            $newCover->saveAs('uploads/covers/' . $newCoverName . '.' . $newCover->extension);
          }

          if ($model->validate() && $model->save()) {
            return $this->redirect(['view', 'id' => $model->post_id]);
          } else {
            // error saving model
          }

        } else {
          return $this->render('update', [
          'model' => $model,
          ]);
        }

    }

In the update scenario the image filed is not required but the code checks if nothing was uploaded and doesn't change the previous value.

My form file:

<?= $form->field($model, 'image')->widget(FileInput::classname(), [
    'options' => ['accept'=>'image/*'],
    'pluginOptions'=>[
        'allowedFileExtensions'=>['jpg', 'gif', 'png', 'bmp'],
        'showUpload' => true,
        'initialPreview' => [
            $model->cover ? Html::img($model->cover) : null, // checks the models to display the preview
        ],
        'overwriteInitial' => false,
    ],
]); ?>

I think is a little more easier than a virtual field. Hope it helps!

like image 21
JJPunch Avatar answered Sep 24 '22 10:09

JJPunch