Editable

Creates single editable element. Use it when you want to edit data not related with CModel.

Example

Widget source

<?php
    $this->widget('editable.Editable', array(
        'type'      => 'text',
        'name'      => 'user_name',
        'pk'        => 1,
        'text'      => 'John',
        'url'       => $this->createUrl('site/updateUser'), 
        'title'     => 'Enter user name',
        'placement' => 'right'
    ));
?>

EditableField

Makes editable single attribute of model. Attribute should be safe (e.g. defined in rules() method of model)
It can be one of following types:

1. Text

Example
(try empty value to test validation)

Widget source

<?php
    $this->widget('editable.EditableField', array(
        'type'      => 'text',
        'model'     => $user,
        'attribute' => 'user_name',
        'url'       => $this->createUrl('site/updateUser'), 
        'placement' => 'right',
    ));
?>

In model User.php

public function rules()
{
    return array(
        array('user_name', 'required'), //make user_name safe and required
        ...
    );
}

2. Textarea

Example

Widget source

<?php
    $this->widget('editable.EditableField', array(
        'type'        => 'textarea',
        'model'       => $user,
        'attribute'   => 'user_comment',
        'url'         => $this->createUrl('site/updateUser'), 
        'placement'   => 'right',
        'showbuttons' => 'bottom',
    ));
?>

3. Select (load items from array)

Example

Widget source

<?php
    $this->widget('editable.EditableField', array(
        'type'      => 'select',
        'model'     => $user,
        'attribute' => 'user_status',
        'url'       => $this->createUrl('site/updateUser'), 
        'source'    => Editable::source(Status::model()->findAll(), 'status_id', 'status_text'),
        //or you can use plain arrays:
        //'source'    => Editable::source(array(1 => 'Status1', 2 => 'Status2')),
        'placement' => 'right',
    ));
?>

4. Select (load items from url)

Example

Widget source

<?php
    $this->widget('editable.EditableField', array(
        'type'      => 'select',
        'model'     => $user,
        'attribute' => 'user_status_ext',
        'url'       => $this->createUrl('site/updateUser'), 
        'source'    => $this->createUrl('site/getStatusList'), 
        //you can also use js function returning string url
        //'source'    => 'js: function() { return "?r=site/getStatusList"; }',
        'placement' => 'right',
    ));
?>

Action site/GetStatusList

public function actionGetStatusList()
{
    echo CJSON::encode(Editable::source(Status::model()->findAll(), 'status_id', 'status_text')); 
}

5. Date

Example

Widget source

<?php
    $this->widget('editable.EditableField', array(
        'type'        => 'date',
        'model'       => $user,
        'attribute'   => 'user_dob',
        'url'         => $this->createUrl('site/updateUser'),         
        'placement'   => 'right',
        'format'      => 'yyyy-mm-dd', //format in which date is expected from model and submitted to server
        'viewformat'  => 'dd/mm/yyyy', //format in which date is displayed
    ));
?>

In model rules()

   array('user_dob', 'default', 'value' => null), //make user_dob safe and convert "" to null

i18n:
For Bootstrap datepicker you should include lang file from here and set option language

$this->widget('editable.EditableField', array(
    ...
    'options'     => array(
        'datepicker' => array('language' => 'ru') 
    )   
));
Yii::app()->clientScript->registerScriptFile(Yii::app()->baseUrl.'/js/bootstrap-datepicker.ru.js', CClientScript::POS_END);

For jQuery UI datepicker you just should include lang file from here.

Yii::app()->clientScript->registerScriptFile(Yii::app()->baseUrl.'/js/jquery.ui.datepicker-ru.js', CClientScript::POS_END);

6. Datetime (bootstrap only!)

Example

Widget source

    <?php
    $this->widget('editable.EditableField', array(
        'type'        => 'datetime',
        'model'       => $user,
        'attribute'   => 'user_visit',
        'url'         => $this->createUrl('site/updateUser'),         
        'placement'   => 'right',
        'format'      => 'yyyy-mm-dd hh:ii:ss', //database datetime format
        'viewformat'  => 'dd/mm/yyyy hh:ii', //format for display
    ));
?>    

In model rules()

      array('user_visit', 'default', 'value' => null), //make attribute safe and convert "" to null
    

7. Combodate

Example

Widget source

<?php
    $this->widget('editable.EditableField', array(
        'type'        => 'combodate',
        'model'       => $user,
        'attribute'   => 'user_reserve',
        //define value directly as it should match the format of combodate: Y-m-d H:i --> YYYY-MM-DD HH:mm 
        'value'       => isset($user->user_visit) ? date('Y-m-d H:i', strtotime($user->user_visit)) : '', 
        'url'         => $this->createUrl('site/updateUser'),
        'placement'   => 'right',      
        'format'      => 'YYYY-MM-DD HH:mm', //in this format date sent to server  
        'viewformat'  => 'MMM DD, YYYY HH:mm', //in this format date is displayed
        'template'    => 'DD / MMM / YYYY HH:mm', //template for dropdowns
        'combodate'   => array('minYear' => 1980, 'maxYear' => 2013), 
    ));
?>

In model rules()

  array('user_reserve', 'default', 'value' => null), //make attribute safe and convert "" to null

8. Checklist

Example

Widget source

<?php
    $this->widget('editable.EditableField', array(
        'type'      => 'checklist',
        'model'     => $user,
        'attribute' => 'user_params',
        'placement' => 'right',
        'url'       => $this->createUrl('site/updateUser'),
        'source'    => array('pretty', 'awesome', 'smart', 'ambitious'), 
    ));
?>

In model User.php

public function rules()
{
    return array(
        array('user_params', 'implodeParams'), //define custom validator to implode checked values into string
        ...
    );
}

public function implodeParams($attribute) 
{
    if(is_array($this->$attribute)) {
        $this->$attribute = implode(',', $this->$attribute);
    }
}

Value is stored in db as comma separated string of values, e.g. "1,3,4".

9. Select2

Example

Widget source

<?php
    $tags = array(
      array('id' => 1, 'text' => 'php'),
      array('id' => 2, 'text' => 'html'),
      array('id' => 3, 'text' => 'css'),
      array('id' => 4, 'text' => 'javascript'),
    );
    
    $this->widget('editable.EditableField', array(
        'type'      => 'select2',
        'model'     => $user,
        'attribute' => 'user_tags',
        'url'       => $this->createUrl('site/updateUser'), 
        'source'    => $tags,
        'placement' => 'right',
        'select2'   => array(
           'multiple' => true
        )
    ));
?>

In model rules()

public function rules()
{
    return array(
        array('user_tags', 'implodeParams'), //define custom validator to implode checked values into string
        ...
    );
}
public function implodeParams($attribute) 
{
    if(is_array($this->$attribute)) {
        $this->$attribute = implode(',', $this->$attribute);  //in db it is stored as string e.g. '1,3,4'
    }
}
To learn about controller action for these widgets please see EditableSaver.

EditableDetailView

Makes editable several attributes of single model, shown as name-value table. Just define editable section inside attribute config. Only safe attributes become editable.

1. Existing record

User Nameyiiuser
Group
Status
Date of birth
LanguageFrance
CommentNo comments..
Created2012-09-09 01:26:06

2. New record

User Name
Group
Status
Date of birth
LanguageNot set
Comment
CreatedNot set

Source (equal for both cases)

<?php
    $this->widget('editable.EditableDetailView', array(
    'data'       => $user,
    
    //you can define any default params for child EditableFields 
    'url'        => $this->createUrl('site/updateUser'), //common submit url for all fields
    'params'     => array('YII_CSRF_TOKEN' => Yii::app()->request->csrfToken), //params for all fields
    'emptytext'  => 'no value',
    //'apply' => false, //you can turn off applying editable to all attributes
      
    'attributes' => array(
        array(
            'name' => 'user_name',
            'editable' => array(
                'type'       => 'text',
                'inputclass' => 'input-large',
                'emptytext'  => 'special emptytext',                
                'validate'   => 'function(value) {
                    if(!value) return "User Name is required (client side)"
                }'
            )
        ),
        array( //select loaded from database
            'name' => 'group_id',
            'editable' => array(
                'type'   => 'select',
                'source' => Editable::source(Group::model()->findAll(), 'group_id', 'group_name')
             )
        ),
        array( //select loaded from ajax.
            'name' => 'user_status',
            'editable' => array(
                'type'   => 'select',
                'source' => $this->createUrl('site/getStatuses'),
            )
        ),
        array(
            'name' => 'user_dob',
            'editable' => array(
                'type'       => 'date',
                'viewformat' => 'dd/mm/yyyy'
            )
        ),
        array( //edit related record
            'name' => 'profile.language',
            'editable' => array(
                'url'  => array('site/updateProfile') //related record requires own submit url
            )
        ),        
        'user_comment',
        'created_at', //will not be editable as attribute is not safe
    )
    ));
?>

For new record you just pass to widget instance of new model, e.g. $user = new User;.

When you click on Save button all values are collected, validated and submitted to server (you can find more details here).
Onclick handler can be set as following:

<? if($user->isNewRecord) {
        Yii::app()->clientScript->registerScript('new-user', '
            $("#save-btn").click(function() {
                $(this).parent().parent().find(".editable").editable("submit", {
                    url: "'.$this->createUrl('site/createUser').'",
                    data: '.CJSON::encode(array('YII_CSRF_TOKEN' => Yii::app()->request->csrfToken)).',
                    ajaxOptions: { dataType: "json" },                    
                    success: function(data, config) {
                        if(data && data.id) {
                            $(this).editable("option", "pk", data.id);
                            $(this).removeClass("editable-unsaved");
                            $("#msg").removeClass("alert-error").addClass("alert-success")
                                     .html("User created! Now you can update it.").show();
                            $("#save-btn").hide();
                        } else {
                            config.error.call(this, data && data.errors ? data.errors : "Unknown error");
                        }
                    },
                    error: function(errors) {
                        var msg = "";
                        if(errors && errors.responseText) { 
                            msg = errors.responseText;
                        } else {
                            $.each(errors, function(k, v) { msg += v+"<br>"; });
                        } 
                        $("#msg").removeClass("alert-success").addClass("alert-error")
                                 .html(msg).show();         
                     }
                });
            });
        ');   
    }
?>

Server-side action to create new record:

public function actionCreateUser()
{
    if(Yii::app()->request->isPostRequest) {
        $model = new User;
        $model->attributes = $_POST;
        if($model->save()) {
            echo CJSON::encode(array('id' => $model->primaryKey));
        } else {
            $errors = array_map(function($v){ return join(', ', $v); }, $model->getErrors());
            echo CJSON::encode(array('errors' => $errors));
        }
    } else {
      throw new CHttpException(400, 'Invalid request');  
    }
}

EditableColumn

Makes editable one column in GridView. Grid stays editable after ajax sorting and pagination. Parameters are defined in editable section of column config.
Since 1.1 works with both CActiveDataProvider and CArrayDataProvider.

Example

Source

<? $this->widget('bootstrap.widgets.TbGridView', array(
    'id' => 'usergrid',
    'itemsCssClass' => 'table-bordered items',
    'dataProvider' => $dataProvider,
    'columns'=>array(
        array(
           'class' => 'editable.EditableColumn',
           'name' => 'user_name',
           'headerHtmlOptions' => array('style' => 'width: 110px'),
           'editable' => array(    //editable section
                  'apply'      => '$data->user_status != 4', //can't edit deleted users
                  'url'        => $this->createUrl('site/updateUser'),
                  'placement'  => 'right',
              )               
        ),
        
        array( 
              'class' => 'editable.EditableColumn',
              'name' => 'user_status',
              'headerHtmlOptions' => array('style' => 'width: 100px'),
              'editable' => array(
                  'type'     => 'select',
                  'url'      => $this->createUrl('site/updateUser'),
                  'source'   => $this->createUrl('site/getStatuses'),
                  'options'  => array(    //custom display 
                     'display' => 'js: function(value, sourceData) {
                          var selected = $.grep(sourceData, function(o){ return value == o.value; }),
                              colors = {1: "green", 2: "blue", 3: "red", 4: "gray"};
                          $(this).text(selected[0].text).css("color", colors[value]);    
                      }'
                  ),
                 //onsave event handler 
                 'onSave' => 'js: function(e, params) {
                      console && console.log("saved value: "+params.newValue);
                 }',
                 //source url can depend on some parameters, then use js function:
                 /*
                 'source' => 'js: function() {
                      var dob = $(this).closest("td").next().find(".editable").text();
                      var username = $(this).data("username");
                      return "?r=site/getStatuses&user="+username+"&dob="+dob;
                 }',
                 'htmlOptions' => array(
                     'data-username' => '$data->user_name'
                 )
                 */
              )
         ),
      
         array( 
              'class' => 'editable.EditableColumn',
              'name'  => 'user_dob',
              'headerHtmlOptions' => array('style' => 'width: 100px'),
              'editable' => array(
                  'type'          => 'date',
                  'viewformat'    => 'dd.mm.yyyy',
                  'url'           => $this->createUrl('site/updateUser'),
                  'placement'     => 'right',
              )
         ), 
         
         array( 
            'class' => 'editable.EditableColumn',
            'name' => 'user_comment',
            'editable' => array(
                'type'      => 'textarea',
                'url'       => $this->createUrl('site/updateUser'),
                'placement' => 'left',
            )
          ),  
         
         //editable related attribute with sorting.
         //see http://www.yiiframework.com/wiki/281/searching-and-sorting-by-related-model-in-cgridview  
         array( 
            'class' => 'editable.EditableColumn',
            'name' => 'virtual_field',
            'value' => 'CHtml::value($data, "profile.language")',
            'editable' => array(
                'type'      => 'text',
                'attribute' => 'profile.language',
                'url'       => $this->createUrl('site/updateProfile'),
                'placement' => 'left',
            )
        ), 
    ),
)); 
?>

ListView

You can use usual CListView with editable items.

Example

Displaying 1-3 of 5 results.
Username: superuser
Status:
Username: yiiuser
Status:
Username: user3
Status:

Source views / listview

<?php 
$widget = $this->widget('bootstrap.widgets.TbListView', array(
    'id'           => 'listview',
    'dataProvider' => $dataProvider,
    'itemView'     => 'listviewitem',
));
 
//for update editables after ajax sort and pagination
Editable::attachAjaxUpdateEvent($widget); 
?>

Source views / listviewitem

<div style="float: left; width: 30%; border: solid 1px gray; margin: 10px; padding: 3px">

Username:
<?php
$this->widget('editable.EditableField', array(
    'type'         => 'text',
    'model'        => $data,
    'attribute'    => 'user_name',
    'url'          => $this->createUrl('site/updateUser'),
    'placement'    => 'right',
    'liveTarget'   => $widget->id, //for live update
    ));
?>
<br>
Status:
<?php
$this->widget('editable.EditableField', array(
    'type'         => 'select',
    'model'        => $data,
    'attribute'    => 'user_status',
    'url'          => $this->createUrl('site/updateUser'),
    'source'       => $this->createUrl('site/getStatuses'),
    'placement'    => 'right',
    'liveTarget'   => $widget->id, //for live update
    ));
  
?>

</div>
<? if(($index+1) % 3 == 0): ?>
<div style="clear: both"></div>
<? endif; ?>

Options

These options are passed as configuration array into EditableField or into editable section of EditableDetailView and EditableColumn.
Please refer to X-editable docs for the most detailed description (options marked with x-editable).

NameTypeDefaultDescriptionRelated
applybooleannullwhether to apply 'editable' js plugin to element. Only safe attributes become editable.Yii
attributestringrequiredattribute name.Yii
cssFilemixed'jquery-ui.css'for jQuery UI only. The theme CSS file name. By default Yii's jquery UI css used.Yii
disabledbooleanfalsewill editable be initially disabled. It means editable plugin will be applied to element, but you should call .editable('enable') method to activate it. To totally disable applying 'editable' to element use apply option.x-editable
emptytextstringnulltext shown on empty field. If null - default X-editable value is used: Emptyx-editable
encodebooleantruewhether to HTML encode text on outputYii
formatstringnullformat to send date on server. If null - default X-editable value is used: yyyy-mm-dd.x-editable
htmlOptionsarrayarray()HTML options of element. In EditableColumn htmlOptions are PHP expressions so you can use $data to bind values to particular cell, e.g. `'data-categoryID' => '$data->categoryID'`.Yii
inputclassstringnullcss class of input. If null - default X-editable value is used: input-mediumx-editable
liveSelectorstringnulljQuery selector of elements to wich will be applied editable.

Usefull in combination of liveTarget when you want to keep field(s) editble after ajaxUpdate

Yii
liveTargetstringnullDOM id of target where afterAjaxUpdate handler will call live update of editable elementYii
modestringnullmode of input: inline | popup. If not set - default X-editable value is used: popup.x-editable
modelCActiveRecordrequiredActiveRecord to be updated.Yii
name*stringnullname of fieldx-editable
optionsarrayarray()all config options of x-editable. See full list here.Yii
paramsarraynulladditional params to send on serverx-editable
pk*mixednullprimary keyx-editable
placementstringnullplacement of popup. Can be left, top, right, bottom. If null - default X-editable value is used: topx-editable
sendstringnullStrategy for sending data on server. Can be auto|always|never. When 'auto' data will be sent on server only if pk and url defined, otherwise new value will be stored locally.x-editable
showbuttonsstringnullvisibility of buttons. Can be boolean false|true or string bottom.x-editable
sourcemixednullsource data for select, checklist. Can be string (url) or array in format: array( array("value" => 1, "text" => "abc"), ...)x-editable
text*stringnulltext to be shown as element contentYii
themestring'base'for jQuery UI only. The JUI theme name.Yii
themeUrlstringfor jQuery UI only. The root URL that contains JUI theme folders. If not set, default Yii's theme will be used.Yii
titlestringnulltitle of popup. If null - will be generated automatically from attribute label. Can have token {label} inside that will be replaced with actual attribute label.Yii
typestringnulltype of editable widget. Can be text, textarea, select, date, checklist, etc.x-editable
urlstringnullurl to submit value. Can be string or array containing Yii route, e.g. array('site/updateUser')x-editable
valuemixednullinitial value. If not set - will be taken from textx-editable
viewformatstringnullformat to display date in element. If null - equals to format option.x-editable

* - required only for raw Editable class. Not used for other classes as CModel provides these data automatically.

Callbacks

NameDescriptionRelated
validateA javascript function that will be invoked to validate value.

Example:

'validate' => 'js: function(value) {
    if($.trim(value) == "") return "This field is required";
}'
x-editable
successA javascript function that will be invoked to process successful server response.

Example:

'success' => 'js: function(response, newValue) {
    if(!response.success) return response.msg;
}'
x-editable
displayA javascript function that will be invoked to custom display value.

Example:

'display' => 'js: function(value, sourceData) {
     var escapedValue = $("<div>").text(value).html();
     $(this).html("<b>"+escapedValue+"</b>");
}'
x-editable

Events

Please see also Events tab of X-editable docs.

NameDescriptionRelated
onInitA javascript function that will be invoked when editable element is initializedx-editable
onShownA javascript function that will be invoked when editable form is shown Example:
'onShown' => 'js: function() {
    var $tip = $(this).data("editableContainer").tip();
    $tip.find("input").val("overwriting value of input.");
}'
x-editable
onSaveA javascript function that will be invoked when new value is saved Example:
'onSave' => 'js: function(e, params) {
    alert("Saved value: " + params.newValue);
}'
x-editable
onHiddenA javascript function that will be invoked when editable form is hidden Example:
'onHidden' => 'js: function(e, reason) {
   if(reason === "save" || reason === "cancel") {
       //auto-open next editable
       $(this).closest("tr").next().find(".editable").editable("show");
   }
}'
x-editable
For any questions feel free to open issue on github.
Your feedback / contribution will be appreciated!