How to setup an editable column to manipulate records in Yii2 grid view

This webtip will explain how in Yii 2 Framework, we can setup the enhanced Krajee EditableColumn to work with the Krajee GridView widget. The objective is to achieve the following functionality using kartik\grid\GridView and kartik\grid\EditableColumn:

  1. configure a grid cell content to be editable
  2. use advanced input widgets to edit content
  3. capture the editable content POSTED via controller and update database via ajax
  4. trigger model validation and validation error messages if needed

NOTE:
Refer this follow-up webtip for rapidly setting up an EditableColumn with EditableColumnAction.

About GridView

Before we start, the GridView is part of the yii2-grid module by Krajee. It is an enhanced grid widget that extends the yii\grid\GridView widget with various new features, plugins and makes it snappy for developers to create grid related user interfaces.

About EditableColumn

This is an enhanced grid column by Krajee, and available as part of the kartik\grid\GridView module/extension. It allows you to quickly create an editable grid cell content by triggering a popover.

The Use Case

Let’s consider the example of editing books in a library. The final output of the grid is seen on the Krajee grid demos page. If you watch the demo, the Book Name and Buy Amount columns are Editable. Let’s consider the following:

  1. We will setup the name and buy_amount attributes to be editable in the grid
  2. We will setup model validation rules for the above two attributes.
  3. We will configure the name to be edited through a HTML text input. In addition, we will also show the book_color input within the name editable form.
  4. We will configure the buy_amount to be edited through the TouchSpin widget from Krajee.
  5. We will setup the grid rendering controller action to also validate the editable submissions and validate posted records to save to the database.

STEP 1: Setup model

Setup any model validation rules for your attributes. For the example we will set name and buy_amount to be mandatory/required. We will also setup buy_amount as a number field and must lie between 0 and 5000.

/**
* Book model class
*/
class Book extends \yii\db\ActiveRecord
{
    /**
     * @inheritdoc
     */
    public function rules()
    {
        return [
            [['name', 'buy_amount'], 'required'],
            [['name', 'buy_amount'], 'safe'],
            [['buy_amount'], 'number', 'min'=>0, 'max'=>5000]
        ];
    }
}

STEP 2: Setup view (grid and editable columns)

We will first setup the view file rendering the grid to show these editable columns the way we desire. We will setup the following configuration:

  1. the name will be edited by a text input and will also allow editing the book color using the ColorInput widget from Krajee. We will use a Closure method to configure editableOptions. The ColorInput can be embedded using the afterInput property in editableOptions.
  2. the buy_amount will be edited by the TouchSpin widget from Krajee. We will configure the editableOptions as an array in this case.
// the grid columns setup (only two column entries are shown here
// you can add more column entries you need for your use case)
$gridColumns = [
// the name column configuration
[
    'class'=>'kartik\grid\EditableColumn',
    'attribute'=>'name',
    'pageSummary'=>true,
    'editableOptions'=> function ($model, $key, $index) {
        return [
            'header'=>'Name',
            'size'=>'md',
            'afterInput'=>function ($form, $widget) use ($model, $index) {
                return $form->field($model, "color")->widget(\kartik\widgets\ColorInput::classname(), [
                    'showDefaultPalette'=>false,
                    'options'=>['id'=>"color-{$index}"],
                    'pluginOptions'=>[
                        'showPalette'=>true,
                        'showPaletteOnly'=>true,
                        'showSelectionPalette'=>true,
                        'showAlpha'=>false,
                        'allowEmpty'=>false,
                        'preferredFormat'=>'name',
                        'palette'=>[
                            ["white", "black", "grey", "silver", "gold", "brown"],
                            ["red", "orange", "yellow", "indigo", "maroon", "pink"],
                            ["blue", "green", "violet", "cyan", "magenta", "purple"],
                        ]
                    ],
                ]);
            }
        ];
    }
],
// the buy_amount column configuration
[
    'class'=>'kartik\grid\EditableColumn',
    'attribute'=>'buy_amount',
    'editableOptions'=>[
        'header'=>'Buy Amount',
        'inputType'=>\kartik\editable\Editable::INPUT_SPIN,
        'options'=>['pluginOptions'=>['min'=>0, 'max'=>5000]]
    ],
    'hAlign'=>'right',
    'vAlign'=>'middle',
    'width'=>'100px',
    'format'=>['decimal', 2],
    'pageSummary'=>true
],
];

// the GridView widget (you must use kartik\grid\GridView)
echo \kartik\grid\GridView::widget([
    'dataProvider'=>$dataProvider,
    'filterModel'=>$searchModel,
    'columns'=>$gridColumns
]);

STEP 3: Modify controller action for validating Editable

The next most important part is to modify your controller action that renders the view file containing the GridView. Let’s say this action is named actionIndex.

use yii\helpers\Json;

public function actionIndex()
{
    // your default model and dataProvider generated by gii
    $searchModel = new BookSearch;
    $dataProvider = $searchModel->search(Yii::$app->request->getQueryParams());

    // validate if there is a editable input saved via AJAX
    if (Yii::$app->request->post('hasEditable')) {
        // instantiate your book model for saving
        $bookId = Yii::$app->request->post('editableKey');
        $model = Book::findOne($bookId);

        // store a default json response as desired by editable
        $out = Json::encode(['output'=>'', 'message'=>'']);

        // fetch the first entry in posted data (there should only be one entry 
        // anyway in this array for an editable submission)
        // - $posted is the posted data for Book without any indexes
        // - $post is the converted array for single model validation
        $posted = current($_POST['Book']);
        $post = ['Book' => $posted];

        // load model like any single model validation
        if ($model->load($post)) {
        // can save model or do something before saving model
        $model->save();

        // custom output to return to be displayed as the editable grid cell
        // data. Normally this is empty - whereby whatever value is edited by
        // in the input by user is updated automatically.
        $output = '';

        // specific use case where you need to validate a specific
        // editable column posted when you have more than one
        // EditableColumn in the grid view. We evaluate here a
        // check to see if buy_amount was posted for the Book model
        if (isset($posted['buy_amount'])) {
        $output = Yii::$app->formatter->asDecimal($model->buy_amount, 2);
        }

        // similarly you can check if the name attribute was posted as well
        // if (isset($posted['name'])) {
        // $output = ''; // process as you need
        // }
        $out = Json::encode(['output'=>$output, 'message'=>'']);
        }
        // return ajax json encoded response and exit
        echo $out;
        return;
    }

    // non-ajax - render the grid by default
    return $this->render('index', [
        'dataProvider' => $dataProvider,
        'searchModel' => $searchModel
    ]);
}

STEP 4: Test the grid and editable

Run your view and test out for any validation or ajax errors. You can view the Krajee grid demo page to see the output of the above configured example.

NOTE:
If you wish to use a prebuilt easier method to setup an Editable action, refer this follow-up webtip for rapidly setting up an EditableColumn with EditableColumnAction.

32 thoughts on “How to setup an editable column to manipulate records in Yii2 grid view

  1. Great article. Thanks for explaining.
    One thing to note is that you need to add this line to the top of the controller:
    use yii\helpers\Json;

  2. Any chance you could provide an example of the controller using editableKey in order to handle tables with composite keys?

    1. For composite keys (e.g. an array) the EditableColumn automatically serializes the key values using php serialize function. You can unserialize this in your controller action and proceed to use a similar approach for the rest of the part.

  3. Can I update another cell in the updated row? Let’s say I have an editable field “staff_hours” and I update that via the editable widget. In the controller, I also recalculate another field “staff_cost” and save it to the db table. Now the gridview cell “staff_cost” is not correct anymore and needs reloading. Don’t know how to trigger that reload.

  4. Have also need to change Formula column value after change Editable field – how i can do this? Thank you!

      1. try to use this, but nothing…

        registerJs(
        ‘$(“document”).ready(function(){
        $(“.kv-editable”).on(“pjax:end”, function() {
        $.pjax.reload({container:”#goods”}); //Reload GridView
        });
        });’
        );
        ?>

        ‘goods’]) ?>
        <?= GridView::widget([
        ….

          1. already, but nothing. (see below my reply) this column must recalc when change other two:

                    [
                    'class'=&gt;'kartik\grid\FormulaColumn', 
                    'header'=&gt;'Наценка, %', 
                    'vAlign'=&gt;'middle',
                    'value'=&gt;function ($model, $key, $index, $widget) { 
                        $p = compact('model', 'key', 'index');
                        return ($widget-&gt;col(3, $p)!=0) ? abs($widget-&gt;col(3, $p) - $widget-&gt;col(2, $p))/$widget-&gt;col(2, $p)*100 : 0;
                    },
                    'hAlign'=&gt;'right', 
                    'format'=&gt;['decimal', 1],
                    // 'pageSummary' =&gt; true,
                    ],
            
      2. set this:
        [code]
        ‘pjax’=>true,
        ‘pjaxSettings’=>[
        ‘refreshGrid’ => true,
        ‘neverTimeout’=>true,
        ],
        [/code]

        i dont understand sorry

    1. i am also use

          $modelid= Yii::$app-&gt;request-&gt;post('editableKey');
          $tovar = $this-&gt;findModel($modelid);
      
      1. The methods as mentioned in the wiki will work for any model class.

        The findModel method (generated by gii) will be restricted in usage… and will only work for the CRUD specific model class.

        In case you need to use editables with different model classes … you need to implicitly run the find or findOne method as shown.

  5. Thanks. This is a very good WEB Tip.
    However a note and a problem:
    1. Instead of “$model = new Book;” use “$model =$this->findModel($_POST[‘editableKey’]);” in the controller
    2. If I pass a validation message, it does not appear anywhere. The showError function in editable.js is using probably bad jQuery selectors. Where should it appear?

    Thanks for any answer.

    1. A note on findModel is mentioned in the other post.

      Validation error sent by controller will appear in the editable popup itself once apply button is pressed – UNLESS you have some code set on editableSuccess/ editableError to override this.

  6. Hello, thank you for great components.
    I can’t find possibility to disable Editable component and render only value for specific rows neither in EditableColumn nor in Editable.
    And i can’t just inherit a column component, due that fact, that it uses DataColumn::renderDataCellContent to render value, and this method will be grandparent’s …
    Or i miss something obvious?

  7. To make using editable easier to setup, would it be possible to add a action to handle the updating, and then all that would have to be added to the controller would be a new action method to refrence the action ?

    1. Yes you can read the documentation for yii2-editable widget before you use the Editable column in grid view. The editable widget contains a property formOptions – where you can set formOptions['action'] to point to any controller action you want to. If not set – the form action defaults to current page action.

      You can set the options for editable widget in EditableColumn via editableOptions. So basically

      editableOptions => ['formOptions' => ['action' => '/path/to/action']]
      
      1. Thank you for your response. Do you have an example of the code necessary to create an action that would update the editable field?

      2. /**
        * work with kartik\editable\Editable
        * @see http://github.com/kartik-v/yii2-editable
        *
        * @author Ben Bi ben@cciza.com
        * @link http://www.cciza.com/
        * @copyright 2014-2016 CCIZA Software LLC
        * @license
        */
        class InlineSaveAction extends \yii\rest\Action {

        public function run() {
            if ($this->checkAccess) {
                call_user_func($this->checkAccess, $this->id);
            }
            $params = Yii::$app->request->post();
        
            if (!isset($params['hasEditable'])) {
                throw new HttpException(404, Yii::t('wac', 'Editable tag parameter not found!'));
            }
        
            $modelClass = $this->modelClass;
            $id = $params['editableKey'];
            $model = $modelClass::findOne($id);
        
            if (!$model) {
                throw new HttpException(404, Yii::t('wac', 'Model not found!'));
            }
            $formName = $model->formName();
        
            $post = [];
            $posted = $params[$formName][$params['editableIndex']];
            $post[$formName] = $posted;
            $responseData = ['output' => '', 'message' => ''];
            if ($model->load($post)) {
                $value = '';
                if (!$model->save()) {
                    $errs = current($model->getErrors());
                    $message = $errs[0];
                    $responseData = ['output' => $value, 'message' => $message];
                }
            }
        
            return \Yii::createObject([
                        'class' => 'yii\web\Response',
                        'format' => \yii\web\Response::FORMAT_JSON,
                        'data' => $responseData,
            ]);
        }
        

        }

  8. $gridColumns = [
    // the name column configuration
    [
    ‘class’ => ‘kartik\grid\EditableColumn’,
    ‘attribute’=>’title’,
    /‘readonly’=>function($model, $key, $index, $widget) {
    return (!$model->status); // do not allow editing of inactive records
    },
    /
    ‘editableOptions’ => [
    ‘header’ => ‘asdasdsd’,
    ‘inputType’ => Editable::INPUT_SPIN,
    ‘options’ => [
    ‘pluginOptions’ => [‘min’=>0, ‘max’=>5000]
    ]
    ],
    ‘hAlign’=>’right’,
    ‘vAlign’=>’middle’,
    ‘width’=>’100px’,
    ‘format’=>[‘decimal’, 2],
    //’pageSummary’ => true
    ],

    this is my grid. in index page gridview is ok. but don’t show editable window. where is my fault? pls reply. thanks

  9. Hi!

    I’m trying to use Editable inside the gridView with DateTimePicker. Having the following configuration:

                [
                    'class' => 'kartik\grid\EditableColumn',
                    'attribute' => 'date_time_game',
                    'editableOptions' => [
                            'header' => 'Game starts at',
                            'inputType' => \kartik\editable\Editable::INPUT_DATETIME,
                            'options' => [
    
                            ]
                    ],
                ],
    

    but getting ‘Invalid or no primary key found for the grid data’ all the time. What’s wrong here?

  10. When I change the value in the popupbox the value doesn’t get saved into the database, not sure what I’m doing wrong…

    Here is my Controller Code:
    public function actionIndex()
    {
    $formmodel = new CarPreparation();
    $searchModel = new AccessoryTypesSearch();
    $dataProvider = $searchModel->search(Yii::$app->request->queryParams);

    // validate if there is a editable input saved via AJAX
    if (Yii::$app->request->post('hasEditable')) {
        // instantiate your book model for saving
        $atid = Yii::$app->request->post('editableKey');
        $model = AccessoryTypes::findOne($atid);
        //$model = $this->findOne($atid);
        // store a default json response as desired by editable
        $out = Json::encode(['output'=>'', 'message'=>'']);
    
        // fetch the first entry in posted data (there should 
        // only be one entry anyway in this array for an 
        // editable submission)
        // - $posted is the posted data for Book without any indexes
        // - $post is the converted array for single model validation
        $post = [];
        $posted = current($_POST['AccessoryTypes']);
        $post['AccessoryTypes'] = $posted;
    
        // load model like any single model validation
        if ($model->load($post)) {
            // can save model or do something before saving model
            $model->save();
    
            // custom output to return to be displayed as the editable grid cell
            // data. Normally this is empty - whereby whatever value is edited by 
            // in the input by user is updated automatically.
            $output = '';
    
            // specific use case where you need to validate a specific
            // editable column posted when you have more than one 
            // EditableColumn in the grid view. We evaluate here a 
            // check to see if buy_amount was posted for the Book model
    
            if (isset($posted['change'])) {
               $output =  Yii::$app->formatter->asInteger($model->change);
            } 
    
            // similarly you can check if the name attribute was posted as well
            // if (isset($posted['name'])) {
            //   $output =  ''; // process as you need
            // } 
            $out = Json::encode(['output'=>$output, 'message'=>'']);
        } 
        // return ajax json encoded response and exit
        echo $out;
        return;
    }
    
        return $this->render('index', [
            'formmodel' => $formmodel,
            'searchModel' => $searchModel,
            'dataProvider' => $dataProvider,
        ]);
    }
    

    Also, if I set refreshGrid option for the editable column after the refresh, clicking on the value does not bring up the popup editable box again….

    1. Ok, I got it to work by doing this:

          $post = [];
          $posted = current($_POST['AccessoryTypes']);
          $post['AccessoryTypes'] = $posted;
      
          // load model like any single model validation
          if ($model->load($post)) {
              // can save model or do something before saving model
              $model->change = $post['AccessoryTypes']['change'];
              $model->save();
      

      But I’m not sure if that’s how you meant to do it….

  11. I have gridview with editable column. I call my gridview with render ajax that set in my controller. But the editable column doesn’t work. Popover of editable column doesn’t show. Can you help me please?

  12. Can get it to show correctly, but the save does not work. When I refresh page the old value is back. Any hints?

    public function actionIndex()
    {
        $searchModel = new HoldSearch();
        $dataProvider = $searchModel->search(Yii::$app->request->queryParams);
    
        if (Yii::$app->request->post('hasEditable')) {
            $HID = Yii::$app->request->post('editableKey');
            $model = Hold::findOne($HID);
    
            $out = Json::encode(['output'=>'', 'message'=>'']);
    
            $post = [];
            $posted = current($_POST['Hold']);
            $post['Hold'] = $posted;
    
            if ($model->load($post)) {
                $model->save();
    
                $output = '';
    
                $out = Json::encode(['output'=>$output, 'message'=>'']);
            }
            echo $out;
            return;
        }
    
        return $this->render('index', [
            'searchModel' => $searchModel,
            'dataProvider' => $dataProvider,
        ]);
    }
    
  13. Thank you for the cool tutorial and all the awesome widgets.
    I want to set up the editable column with an FileInput (Editable::INPUT_FILEINPUT). I can’t load the uploaded file with UploadedFile::getInstance since i don’t know how to get the $_POST data correctly. Do you know how to do so, or have an example?

Comments are closed.