Note: this extension is replaced with X-editable. Please follow link to learn more.

Yii-Bootstrap-Editable
Yii extension for Bootstrap Editable plugin

This extension joins Yii framework with Bootstrap Editable plugin allowing in-place editing with Twitter Bootstrap Form and Popover. It includes three widgets and one component that together give you extremely fast and easy way for creating editable interfaces.

Download stable (1.0.0)

EditableField (widget)

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

1. Text

Example

Username: superuser
Fullname: John (demonstrate validation error)

Source

$this->widget('ext.editable.EditableField', array(
    'type'      => 'text',
    'model'     => $model,
    'attribute' => 'user_name',
    'url'       => $this->createUrl('user/update'),  //url for submit data
));

2. Textarea

Example

Comment:
Awesome
user!

Source

$this->widget('ext.editable.EditableField', array(
    'type'      => 'textarea',
    'model'     => $model,
    'attribute' => 'user_comment',
    'url'       => $this->createUrl('user/update'),
    'placement' => 'right'
));

3. Select

Example

User group:

Source

$this->widget('ext.editable.EditableField', array(
    'type'      => 'select',
    'model'     => $model,
    'attribute' => 'group_id',
    'url'       => $this->createUrl('user/update'),
    'source'    => CHtml::listData(Group::model()->findAll(), 'group_id', 'group_name')
));

4. Date

Example

Date of birth:

Source

$this->widget('ext.editable.EditableField', array(
    'type'          => 'date',
    'model'         => $model,
    'attribute'     => 'user_dob',
    'url'           => $this->createUrl('user/update'),
    'viewformat'    => 'dd/mm/yyyy',
    'placement'     => 'right'
));
↓ Scroll to list of all possible options (end of this page)

For more details about options you can refer to Bootstrap-editable documentation

EditableDetailView (widget)

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
Sex
StatusBanned
Date of birth
CommentNo comments..
Created2012-09-09 01:26:06
2. New record
User Name
Group
Sex
Status
Date of birth
Comment
CreatedNot set

Source (equal for both cases)

$this->widget('ext.editable.EditableDetailView', array(
    'id' => 'user-details',
    'data' => $model,
    'url' => $this->createUrl('user/update'),  //common submit url for all editables
    'attributes'=>array(
        'user_name',
        array(  //select loaded from database
            'name' => 'group_id',
            'editable' => array(
                'type' => 'select',
                'source' => CHtml::listData(Group::model()->findAll(), 'group_id', 'group_name')
             )
        ),
        array(  //select loaded from array
            'name' => 'user_sex',
            'editable' => array(
                'type'    => 'select',
                'source'  => array(0 => 'Male', 1 => 'Female'),
                'prepend' => 'Hidden'
            )
        ),             
        array(  //select loaded from ajax. Text is colored by 'onRender' event
            'name' => 'user_status',
            'value' => Chtml::value($model, 'status.status_text'),
            'editable' => array(
                'type'   => 'select',
                'source' => $this->createUrl('user/getstatuslist'),
                'onRender' => 'js: function(e, edt) {
                      var colors = {1: "green", 2: "blue", 3: "red", 4: "gray"};
                      $(this).css("color", colors[edt.value]);    
                 }',                
            )
        ),
        array(
            'name' => 'user_dob',
            'editable' => array(
                'type' => 'date',
                'viewformat' => 'dd/mm/yyyy'
               )
        ),
        'user_comment',
        'created_at',  //not editable as attribute is not safe
    )
));

For new record you just pass to widget instance of new model, e.g. $model = new User;. Additionally you may create hidden div to show messages and button to save new user (once!):

<div id="msg" class="alert hide"></div>
<button id="save-btn" class="btn btn-primary hide">Save</button>

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

if($model->isNewRecord) {
    Yii::app()->clientScript->registerScript('new-user', '
    $("#save-btn").show();
    $("#save-btn").click(function() {
        $("#user-details .editable").editable("submit", {
            url: "'.$this->createUrl('user/create').'",
            success: function() {
                $("#msg").removeClass("alert-error").addClass("alert-success")
                         .html("User created!").show();
                $("#save-btn").hide();
            },
            error: function(data) {
                var msg = "";
                if(data.errors) { //validation error
                    $.each(data.errors, function(k, v) { msg += v+"<br>"; });
                } else if(data.responseText) { //ajax error
                    msg = data.responseText;
                }
                $("#msg").removeClass("alert-success").addClass("alert-error")
                         .html(msg).show();         
             }
        });
    });
    ');   
}

Server-side action user/create:

public function actionCreate()
{
    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 (widget)

Makes editable one column in GridView. Grid stays editable after ajax sorting and pagination.
Parameters are defined in editable section of column config.

Example:

Source

$this->widget('bootstrap.widgets.TbGridView', array(
    'id' => 'user-grid',
    'itemsCssClass'=>'table table-striped table-bordered table-condensed',
    'dataProvider'=> $dataProvider,
    'columns'=>array(
        array(
           'class' => 'ext.editable.EditableColumn',
           'name' => 'user_name',
           'headerHtmlOptions' => array('style' => 'width: 110px'),
           'editable' => array(
                  'url'        => $this->createUrl('user/update'),
                  'placement'  => 'right',
                  'inputclass' => 'span3',
              )               
        ),
        
        array( 
              'class' => 'ext.editable.EditableColumn',
              'name' => 'user_sex',
              //we need not to set value, it will be auto-taken from source
              'headerHtmlOptions' => array('style' => 'width: 60px'),
              'editable' => array(
                  'type'    => 'select',
                  'url'     => $this->createUrl('user/update'),
                  'source'  => array(0 => 'Male', 1 => 'Female'),
              )
         ),            
        
        array( 
              'class' => 'ext.editable.EditableColumn',
              'name' => 'user_status',
              'value' => 'CHtml::value($data, "status.status_text")', //we need to set value because source is url
              'headerHtmlOptions' => array('style' => 'width: 100px'),
              'editable' => array(
                  'type'     => 'select',
                  'url'      => $this->createUrl('user/update'),
                  'source'   => $this->createUrl('user/getstatuslist'),
                  'onRender' => 'js: function(e, editable) {
                      var colors = {1: "green", 2: "blue", 3: "red", 4: "gray"};
                      $(this).css("color", colors[editable.value]);    
                  }'                       
              )
         ),
      
         array( 
              'class' => 'ext.editable.EditableColumn',
              'name' => 'user_dob',
              'headerHtmlOptions' => array('style' => 'width: 100px'),
              'editable' => array(
                  'type'          => 'date',
                  'viewformat'    => 'dd.mm.yyyy',
                  'url'           => $this->createUrl('user/update'),
                  'placement'     => 'right',
              )
         ), 
         
         array( 
            'class' => 'ext.editable.EditableColumn',
            'name' => 'user_comment',
            'editable' => array(
                'type'      => 'textarea',
                'url'       => $this->createUrl('user/update'),
                'placement' => 'left',
            )
          ),           
    ),
));

EditableSaver (component)

This component is a server-side part for all widgets. It allows to easily handle ajax requests submited by editables.
For instance, all previous demo are submiting to route user/update and action is looks like:

public function actionUpdate()
{
    Yii::import('ext.editable.EditableSaver'); //or you can add import 'ext.editable.*' to config
    $es = new EditableSaver('User');  // 'User' is classname of model to be updated
    $es->update();
}

Inside update() method attribute value is being validated and saved to database column.

Please note that EditableSaver performs only update of existing records! Creation of new record is shown in examples above, for more details please refer to Bootstrap-editable docs.

Model scenario will be set to "editable" so that you can define attribute rules specifically for in-place editing.
If you want to update additional attributes you can use onBeforeUpdate event:

public function actionUpdate()
{
    $es = new EditableSaver('User');
    $es->onBeforeUpdate = function($event) {
        $event->sender->setAttribute('updated_at', date('Y-m-d H:i:s'));
    };
    $es->update();
}

You should use setAttribute() method of EditableSaver to set attribute values inside events. This allows to update only changed attributes of model (not all).

Events

NameDescription
onBeforeUpdateFired before update but after validation of attribute. To cancel update you can add custom error to model:
$event->sender->model->addError('', 'My custom error');
Message will be shown inside widget.
onAfterUpdateFired after successful update of attribute.

This component does not echo anything to browser because by default editable widgets work in RESTfull way and look only into HTTP response status:
HTTP = 200 means update is ok, otherwise error is shown in widget. If you want to exchange with server-side in more advanced mode (e.g. via JSON) you should catch exceptions thrown by EditableSaver and transform it into JSON response, e.g.

public function actionUpdate()
{
    $es = new EditableSaver('User');
    try {
        $es->update();
    } catch(CException $e) {
        echo CJSON::encode(array('success' => false, 'msg' => $e->getMessage()));
        return;
    }
    echo CJSON::encode(array('success' => true));
}

For that case you also need to define success handler in widget to show error message:

$this->widget('ext.editable.EditableField', array(
    'type'      => 'text',
    'model'     => $model,
    'attribute' => 'user_name',
    'url'       => $this->createUrl('user/update'),
    'success'   => 'js: function(data) {
        if(typeof data == "object" && !data.success) return data.msg;
    }'    
));

Options

These options are passed as configuration array into EditableField widget or into editable section of EditableDetailView and EditableColumn.

NameTypeDefaultDescription
Common
modelobjectrequiredModel instance to apply editable
attributestringrequiredAttribute name
typestringtextEditable type. Can be text, select, textarea, date
urlstringnullUrl to submit value
titlestringnullPopover title. If null it will be generated from attribute label
emptytextstringEmptyText shown if value is empty
textstringnullWidget's text. If null will be attribute value
valuestringnullInternal value. If null equals to text. Required for select type
placementstringtopPopover placement. Can be top, right, bottom, left
inputclassstringspan2Input css class. Can be span1...span4 to change input size
autotextstringautoWether to set widget's text by attribute value. Usefull for select and date types.
Can be auto, always, never
optionsarraynullArray with full configuration of editable plugin
encodebooleantrueWether to html-encode text on output
enabledbooleannullWether editable enabled. If null editable will appear only for safe attributes
Text & Textarea
placeholderstringnullPlaceholder to be shown inside input
Select
sourcestring|arraynullSource for dropdown values. If string - used as url to make ajax request, otherwise used as array
prependstring|arraynullThis values or text will be automatically inserted into begining of dropdown
Date
formatstringyyyy-mm-ddFormat to receive and send date to server. combination of d, m and y
viewformatstringnullFormat to display date in widget. If null equals to format
languagestringnullLanguage for datepicker. If null will be taken from Yii application language
weekStartint0Day of the week start. 0 for Sunday - 6 for Saturday
startViewstring0, monthThe view that the datepicker should show when it is opened. Accepts values of 0 or 'month' for month view (the default), 1 or 'year' for the 12-month overview, and 2 or 'decade' for the 10-year overview.

Options: javascript methods

NameDescription
validateJavascript function for client-side validation. If returns string - means validation not passed and string showed as error. E.g.:
'validate'   => 'js: function(value) {
    if($.trim(value) == "") return "This field is required";
}'
Independently of that model's validation rule will be applied on server-side.
successJavascript function applied after receiving ajax response. If returns string - means error occured and string is shown as error. E.g.:
'success'   => 'js: function(data) {
    if(typeof data == "object" && !data.success) return data.msg;  
}'
Usefull for dealing with JSON response.
errorJavascript function applied after failed ajax submit. If returns string - it will be shown as error. E.g.:
'error'   => 'js: function(xhr) {
    if(xhr.status == 500) return "Internal server error";  
}'
Usefull for custom error handling.

Options: javascript events

NameDescription
onInitJavascript function called when widget has been initialized. E.g.:
'onInit' => 'js: function(e, editable) {
    alert("Initialized with value: " + editable.value);
}'
onUpdateJavascript function called when widget has been updated after successful ajax submit. E.g.:
'onUpdate' => 'js: function(e, editable) {
    alert("new value: " + editable.value);
}'
onRenderJavascript function called when widget has been rendered (first time and every time after update). This event can replace both onInit and onUpdate. For example, if you want to customize output formatting depending on value, you can do like this:
'onRender' => 'js: function(e, editable) {
    var colors = {1: "green", 2: "blue", 3: "red", 4: "gray"};
    $(this).css("color", colors[editable.value]);    
}'
Property e.isInit defines wether it is initial render or render after update.
onShownJavascript function called when popover was shown.
'onShown' => 'js: function(e, editable) {
   console.log("popover shown with input: ", editable.$input);
}'
onHiddenJavascript function called when popover was closed.
'onHidden' => 'js: function(e, editable) {
   console.log("popover is hidden.");
}'
For more details you could watch project on github or create an issue.
Your feedback / contribution will be appreciated!