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.
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:
Example
Source
$this->widget('ext.editable.EditableField', array(
'type' => 'text',
'model' => $model,
'attribute' => 'user_name',
'url' => $this->createUrl('user/update'), //url for submit data
));
Example
Source
$this->widget('ext.editable.EditableField', array(
'type' => 'textarea',
'model' => $model,
'attribute' => 'user_comment',
'url' => $this->createUrl('user/update'),
'placement' => 'right'
));
Example
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')
));
Example
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)Makes editable several attributes of single model, shown as name-value table. Just define editable section inside attribute config.
Only safe attributes become editable.
| User Name | yiiuser |
|---|---|
| Group | |
| Sex | |
| Status | Banned |
| Date of birth | |
| Comment | No comments.. |
| Created | 2012-09-09 01:26:06 |
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');
}
}
Makes editable one column in GridView. Grid stays editable after ajax sorting and pagination.
Parameters are defined in editable section of column config.
Example:
| User Name | Sex | Status | Date of birth | Comment |
|---|---|---|---|---|
| superuser | Created | Awesome user! | ||
| yiiuser | Banned | No comments.. | ||
| user3 | Deleted |
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',
)
),
),
));
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.
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).
| Name | Description |
|---|---|
| onBeforeUpdate | Fired 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. |
| onAfterUpdate | Fired 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;
}'
));
These options are passed as configuration array into EditableField widget or into editable section of EditableDetailView and EditableColumn.
| Name | Type | Default | Description |
|---|---|---|---|
| Common | |||
| model | object | required | Model instance to apply editable |
| attribute | string | required | Attribute name |
| type | string | text | Editable type. Can be text, select, textarea, date |
| url | string | null | Url to submit value |
| title | string | null | Popover title. If null it will be generated from attribute label |
| emptytext | string | Empty | Text shown if value is empty |
| text | string | null | Widget's text. If null will be attribute value |
| value | string | null | Internal value. If null equals to text. Required for select type |
| placement | string | top | Popover placement. Can be top, right, bottom, left |
| inputclass | string | span2 | Input css class. Can be span1...span4 to change input size |
| autotext | string | auto | Wether to set widget's text by attribute value. Usefull for select and date types. Can be auto, always, never |
| options | array | null | Array with full configuration of editable plugin |
| encode | boolean | true | Wether to html-encode text on output |
| enabled | boolean | null | Wether editable enabled. If null editable will appear only for safe attributes |
| Text & Textarea | |||
| placeholder | string | null | Placeholder to be shown inside input |
| Select | |||
| source | string|array | null | Source for dropdown values. If string - used as url to make ajax request, otherwise used as array |
| prepend | string|array | null | This values or text will be automatically inserted into begining of dropdown |
| Date | |||
| format | string | yyyy-mm-dd | Format to receive and send date to server. combination of d, m and y |
| viewformat | string | null | Format to display date in widget. If null equals to format |
| language | string | null | Language for datepicker. If null will be taken from Yii application language |
| weekStart | int | 0 | Day of the week start. 0 for Sunday - 6 for Saturday |
| startView | string | 0, month | The 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. |
| Name | Description |
|---|---|
| validate | Javascript 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. |
| success | Javascript 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. |
| error | Javascript 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. |
| Name | Description |
|---|---|
| onInit | Javascript function called when widget has been initialized. E.g.:
'onInit' => 'js: function(e, editable) {
alert("Initialized with value: " + editable.value);
}'
|
| onUpdate | Javascript function called when widget has been updated after successful ajax submit. E.g.:
'onUpdate' => 'js: function(e, editable) {
alert("new value: " + editable.value);
}'
|
| onRender | Javascript 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.
|
| onShown | Javascript function called when popover was shown.
'onShown' => 'js: function(e, editable) {
console.log("popover shown with input: ", editable.$input);
}'
|
| onHidden | Javascript function called when popover was closed.
'onHidden' => 'js: function(e, editable) {
console.log("popover is hidden.");
}'
|