Quantcast
Channel: bootstrap-fileinput – Krajee Web Tips
Viewing all articles
Browse latest Browse all 5

Advanced upload using Yii2 FileInput widget

$
0
0

The web tip on uploading file in Yii 2 using FileInput widget has seen a lot of web hits. As a result, this in turn has generated more feedback and queries by many users (both offline and online). As a request by many folks, I am creating a follow-up web tip to the earlier web tip. In the former web tip, you learnt how to create a new record and upload a file using the FileInput widget by Krajee. This web tip expands the scenario for advanced usage and include the following additional functionalities:

  • optimize and enhance the model
  • update the record and replace the saved image.
  • delete the saved image

The following parameters are useful to setup for global access:

  • Yii::$app->params['uploadPath'] as suggested in the earlier web tip. This will store the server path for all your file uploads. You can copy and store a default placeholder image in this folder, to display an image, whenever no file is uploaded e.g. default_user.jpg.
  • Yii::$app->params['uploadUrl']. This will store the Url pointing to server location for your file uploads. Note your web server must be configured to allow the uploadPath access.

For example, your configuration can be:

Yii::$app->params['uploadPath'] = Yii::$app->basePath . '/uploads/';
Yii::$app->params['uploadUrl'] = Yii::$app->urlManager->baseUrl . '/uploads/';

About FileInput

The FileInput widget is based on the bootstrap-fileinput plugin by Krajee. Here are the advanced steps for you to configure the widget for upload:

Model

It is recommended to optimize code for your model and controller. Instead of overloading the controller, we will now enhance our model code from the previous web tip. We will now include these new additional methods:

  • getImageFile: Will return the uploaded/stored image file name with complete path
  • getImageUrl: Will return the uploaded/stored image url accessible via browser front end.
  • uploadImage: Will process upload of the image and return a yii\web\UploadedFile instance. If no image was uploaded this method will return false.
  • deleteImage: Will process deletion of already existing image in database and return true if image file was found – else return false.

Accordingly set any additional model scenarios or validation rules for your attributes as needed.

namespace common\models;

use yii\db\ActiveRecord;

/**
* Class Person
* @package common\models
* @property int $id unique person identifier
* @property string $name person / user name
* @property array $avatar generated filename on server
* @property string $filename source filename from client
*/
class Person extends ActiveRecord
{
    /**
    * @var mixed image the attribute for rendering the file input
    * widget for upload on the form
    */
    use yii\web\UploadedFile;
    public $image;

    public function rules()
    {
        return [
            [['name', 'avatar', 'filename', 'image'], 'safe'],
            [['image'], 'file', 'extensions'=>'jpg, gif, png'],
        ];
    }

    /**
     * fetch stored image file name with complete path 
     * @return string
     */
    public function getImageFile() 
    {
        return isset($this->avatar) ? Yii::$app->params['uploadPath'] . $this->avatar : null;
    }

    /**
     * fetch stored image url
     * @return string
     */
    public function getImageUrl() 
    {
        // return a default image placeholder if your source avatar is not found
        $avatar = isset($this->avatar) ? $this->avatar : 'default_user.jpg';
        return Yii::$app->params['uploadUrl'] . $avatar;
    }

    /**
    * Process upload of image
    *
    * @return mixed the uploaded image instance
    */
    public function uploadImage() {
        // get the uploaded file instance. for multiple file uploads
        // the following data will return an array (you may need to use
        // getInstances method)
        $image = UploadedFile::getInstance($this, 'image');

        // if no image was uploaded abort the upload
        if (empty($image)) {
            return false;
        }

        // store the source file name
        $this->filename = $image->name;
        $ext = end((explode(".", $image->name)));

        // generate a unique file name
        $this->avatar = Yii::$app->security->generateRandomString().".{$ext}";

        // the uploaded image instance
        return $image;
    }

    /**
    * Process deletion of image
    *
    * @return boolean the status of deletion
    */
    public function deleteImage() {
        $file = $this->getImageFile();

        // check if file exists on server
        if (empty($file) || !file_exists($file)) {
            return false;
        }

        // check if uploaded file can be deleted on server
        if (!unlink($file)) {
            return false;
        }

        // if deletion successful, reset your file attributes
        $this->avatar = null;
        $this->filename = null;

        return true;
    }
}

Controller

Great. With the model now ready, your controller code can be leaner. Setup your controller action for create, update, and delete.

use Yii;
use common\models\Person;
use yii\web\UploadedFile;

class PersonController extends \yii\web\Controller
{
    /**
     * Creates a new Person model.
     * If creation is successful, the browser will be redirected to the 'view' page.
     * @return mixed
     */
    public function actionCreate()
    {
        $model = new Person;

        if ($model->load(Yii::$app->request->post())) {
            // process uploaded image file instance
            $image = $model->uploadImage();

            if ($model->save()) {
                // upload only if valid uploaded file instance found
                if ($image !== false) {
                    $path = $model->getImageFile();
                    $image->saveAs($path);
                }
                return $this->redirect(['view', 'id'=>$model->id]);
            } else {
                // error in saving model
            }
        }
        return $this->render('create', [
            'model'=>$model,
        ]);
    }

    /**
     * Updates an existing Person model.
     * If update is successful, the browser will be redirected to the 'view' page.
     * @param integer $id
     * @return mixed
     */
    public function actionUpdate($id)
    {
        $model = $this->findModel($id);
        $oldFile = $model->getImageFile();
        $oldAvatar = $model->avatar;
        $oldFileName = $model->filename;

        if ($model->load(Yii::$app->request->post())) {
            // process uploaded image file instance
            $image = $model->uploadImage();

            // revert back if no valid file instance uploaded
            if ($image === false) {
                $model->avatar = $oldAvatar;
                $model->filename = $oldFileName;
            }

            if ($model->save()) {
                // upload only if valid uploaded file instance found
                if ($image !== false && unlink($oldFile)) { // delete old and overwrite
                    $path = $model->getImageFile();
                    $image->saveAs($path);
                }
                return $this->redirect(['view', 'id'=>$model->_id]);
            } else {
                // error in saving model
            }
        }
        return $this->render('update', [
            'model'=>$model,
        ]);
    }    

    /**
     * Deletes an existing Person model.
     * If deletion is successful, the browser will be redirected to the 'index' page.
     * @param integer $id
     * @return mixed
     */
    public function actionDelete($id)
    {
        $model = $this->findModel($id);

        // validate deletion and on failure process any exception 
        // e.g. display an error message 
        if ($model->delete()) {
            if (!$model->deleteImage()) {
                Yii::$app->session->setFlash('error', 'Error deleting image');
            }
        }
        return $this->redirect(['index']);
    }

    /**
     * Finds the Person model based on its primary key value.
     * If the model is not found, a 404 HTTP exception will be thrown.
     * @param integer $id
     * @return Person the loaded model
     * @throws NotFoundHttpException if the model cannot be found
     */
    protected function findModel($id)
    {
        if (($model = Person::findOne($id)) !== null) {
            return $model;
        } else {
            throw new NotFoundHttpException('The requested page does not exist.');
        }
    }  
}

View

Your view pretty much remains the same as earlier except that you now add functionality for preview, update, and delete. You may also wish to add any error block to display any error messages.

Edit your _form.php or your other view file where you are rendering the create and image upload form.

use kartik\helpers\Html; // or yii\helpers\Html
use kartik\widgets\ActiveForm; // or yii\widgets\ActiveForm
use kartik\widgets\FileInput;

$form = ActiveForm::begin([
    'options'=>['enctype'=>'multipart/form-data'] // important
]);
echo $form->field($model, 'filename');

// display the image uploaded or show a placeholder
// you can also use this code below in your `view.php` file
$title = isset($model->filename) && !empty($model->filename) ? $model->filename : 'Avatar';
echo Html::img($model->getImageUrl(), [
    'class'=>'img-thumbnail', 
    'alt'=>$title, 
    'title'=>$title
]);

// your fileinput widget for single file upload
echo $form->field($model, 'image')->widget(FileInput::classname(), [
    'options'=>['accept'=>'image/*'],
    'pluginOptions'=>['allowedFileExtensions'=>['jpg','gif','png']
]);

/**
* uncomment for multiple file upload
echo $form->field($model, 'image[]')->widget(FileInput::classname(), [
    'options'=>['accept'=>'image/*', 'multiple'=>true],
    'pluginOptions'=>['allowedFileExtensions'=>['jpg','gif','png']
]);
*/

// render the submit button
echo Html::submitButton($model->isNewRecord ? 'Upload' : 'Update', [
    'class'=>$model->isNewRecord ? 'btn btn-success' : 'btn btn-primary']
);

// render a delete image button 
if (!$model->isNewRecord) { 
    echo Html::a('Delete', ['/person/delete', 'id'=>$model->id], ['class'=>'btn btn-danger']);
}

ActiveForm::end();

Thus, you should be able to use the FileInput for advanced cases of upload. Basically, completely manage your files for insert, update, and delete. As mentioned earlier, in case you are using multiple file input, the $image variable in controller will return an array of files, which you must loop through. Its important for multiple file uploads to follow an array naming convention as mentioned in this web tip.

The post Advanced upload using Yii2 FileInput widget appeared first on Krajee Web Tips.


Viewing all articles
Browse latest Browse all 5

Trending Articles