Angular-xeditable is a bundle of AngularJS directives that allows you to create
editable elements.
Such technique is also known as click-to-edit or edit-in-place.
It is based on ideas of x-editable but was written from scratch
to use power of angular and support complex forms / editable grids.
Basically it does not depend on any libraries except AngularJS itself.
For themes you may need to include Twitter Bootstrap CSS.
For some extra controls (e.g. datepicker) you may need to include angular-ui bootstrap.
Include Angular.js in your project
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.8/angular.min.js"></script>
Optionally include Bootstrap CSS for theming
<link href="//netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap.min.css" rel="stylesheet">
bower install angular-xeditable
<link href="bower_components/angular-xeditable/dist/css/xeditable.css" rel="stylesheet"> <script src="bower_components/angular-xeditable/dist/js/xeditable.js"></script>
<html ng-app="app">
var app = angular.module("app", ["xeditable"]);
app.run(function(editableOptions) { editableOptions.theme = 'bs3'; // bootstrap3 theme. Can be also 'bs2', 'default' });
<div ng-controller="Ctrl"> <a href="#" editable-text="user.name">{{ user.name || "empty" }}</a> </div>
app.controller('Ctrl', function($scope) { $scope.user = { name: 'awesome user' }; });
{{ debug["text-simple"] | json }}
To make element editable via textbox just add editable-text="model.field"
attribute.
<div ng-controller="TextSimpleCtrl"> <a href="#" editable-text="user.name" o-directive="hello">{{ user.name || 'empty' }}</a> </div>
app.controller('TextSimpleCtrl', function($scope) { $scope.user = { name: 'awesome user' }; });
{{ debug["select-local"] | json }}
To create editable select (dropdown) just set editable-select
attribute pointing to model.
To pass dropdown options you should define e-ng-options
attribute
that works like normal angular ng-options
but is transfered to underlying <select>
from original element.
<div ng-controller="SelectLocalCtrl"> <a href="#" editable-select="user.status" e-ng-options="s.value as s.text for s in statuses"> {{ showStatus() }} </a> </div>
app.controller('SelectLocalCtrl', function($scope, $filter) { $scope.user = { status: 2 }; $scope.statuses = [ {value: 1, text: 'status1'}, {value: 2, text: 'status2'}, {value: 3, text: 'status3'}, {value: 4, text: 'status4'} ]; $scope.showStatus = function() { var selected = $filter('filter')($scope.statuses, {value: $scope.user.status}); return ($scope.user.status && selected.length) ? selected[0].text : 'Not set'; }; });
{{ debug["select-remote"] | json }}
To load select options from remote url you should define onshow
attribute pointing to scope function.
The result of function should be a $http promise, it allows to disable element while loading.
<div ng-controller="SelectRemoteCtrl"> <a href="#" editable-select="user.group" onshow="loadGroups()" e-ng-options="g.id as g.text for g in groups"> {{ user.groupName || 'not set' }} </a> </div>
app.controller('SelectRemoteCtrl', function($scope, $filter, $http) { $scope.user = { group: 4, groupName: 'admin' // original value }; $scope.groups = []; $scope.loadGroups = function() { return $scope.groups.length ? null : $http.get('/groups').success(function(data) { $scope.groups = data; }); }; $scope.$watch('user.group', function(newVal, oldVal) { if (newVal !== oldVal) { var selected = $filter('filter')($scope.groups, {id: $scope.user.group}); $scope.user.groupName = selected.length ? selected[0].text : null; } }); });
{{ debug["html5-inputs"] | json }}
Following HTML5 types are supported via editable-xxx
directive:
Please check browser support before using particular type in your project.
<div ng-controller="Html5InputsCtrl"> <div>Email: <a href="#" editable-email="user.email">{{ user.email || 'empty' }}</a></div> <div>Tel: <a href="#" editable-tel="user.tel" e-pattern="\d{3}\-\d{2}\-\d{2}" e-title="xxx-xx-xx">{{ user.tel || 'empty' }}</a></div> <div>Number: <a href="#" editable-number="user.number" e-min="18">{{ user.number || 'empty' }}</a></div> <div>Range: <a href="#" editable-range="user.range" e-step="5">{{ user.range || 'empty' }}</a></div> <div>Url: <a href="#" editable-url="user.url">{{ user.url || 'empty' }}</a></div> <div>Search: <a href="#" editable-search="user.search">{{ user.search || 'empty' }}</a></div> <div>Color: <a href="#" editable-color="user.color">{{ user.color || 'empty' }}</a></div> <div>Date: <a href="#" editable-date="user.date">{{ user.date || 'empty' }}</a></div> <div>Time: <a href="#" editable-time="user.time">{{ user.time || 'empty' }}</a></div> <div>Datetime: <a href="#" editable-datetime="user.datetime">{{ user.datetime || 'empty' }}</a></div> <div>Month: <a href="#" editable-month="user.month">{{ user.month || 'empty' }}</a></div> <div>Week: <a href="#" editable-week="user.week">{{ user.week || 'empty' }}</a></div> </div>
app.controller('Html5InputsCtrl', function($scope) { $scope.user = { email: 'email@example.com', tel: '123-45-67', number: 29, range: 10, url: 'http://example.com', search: 'blabla', color: '#6a4415', date: null, time: '12:30', datetime: null, month: null, week: null }; });
{{ debug["textarea"] | json }}
To make element editable via textarea just add editable-textarea
attribute
pointing to model in scope. You can also wrap content into <pre>
tags to keep linebreaks.
Data can be submitted by Ctrl + Enter.
<div ng-controller="TextareaCtrl"> <a href="#" editable-textarea="user.desc" e-rows="7" e-cols="40"> <pre>{{ user.desc || 'no description' }}</pre> </a> </div>
app.controller('TextareaCtrl', function($scope) { $scope.user = { desc: 'Awesome user \ndescription!' }; });
{{ debug["checkbox"] | json }}
To make element editable via checkbox just add editable-checkbox
attribute
pointing to model in scope. Set e-title
attribute to define text shown with checkbox.
<div ng-controller="CheckboxCtrl"> <a href="#" editable-checkbox="user.remember" e-title="Remember?"> {{ user.remember && "Remember me!" || "Don't remember" }} </a> </div>
app.controller('CheckboxCtrl', function($scope) { $scope.user = { remember: true }; });
{{ debug["checklist"] | json }}
To create list of checkboxes use editable-checklist
attribute pointing to model.
Also you should define e-ng-options
attribute to set value and display items.
Please note, you should include checklist-model directive into your app: var app = angular.module("app", [..., "checklist-model"]);
.
By default, checkboxes aligned horizontally. To align vertically just add following CSS:
.editable-checklist label {
display: block;
}
<div ng-controller="ChecklistCtrl"> <a href="#" editable-checklist="user.status" e-ng-options="s.value as s.text for s in statuses"> {{ showStatus() }} </a> </div>
app.controller('ChecklistCtrl', function($scope, $filter) { $scope.user = { status: [2, 3] }; $scope.statuses = [ {value: 1, text: 'status1'}, {value: 2, text: 'status2'}, {value: 3, text: 'status3'} ]; $scope.showStatus = function() { var selected = []; angular.forEach($scope.statuses, function(s) { if ($scope.user.status.indexOf(s.value) >= 0) { selected.push(s.text); } }); return selected.length ? selected.join(', ') : 'Not set'; }; });
{{ debug["radiolist"] | json }}
To create list of radios use editable-radiolist
attribute pointing to model.
Also you should define e-ng-options
attribute to set value and display items.
By default, radioboxes aligned horizontally. To align vertically just add following CSS:
.editable-radiolist label {
display: block;
}
<div ng-controller="RadiolistCtrl"> <a href="#" editable-radiolist="user.status" e-ng-options="s.value as s.text for s in statuses"> {{ showStatus() }} </a> </div>
app.controller('RadiolistCtrl', function($scope, $filter) { $scope.user = { status: 2 }; $scope.statuses = [ {value: 1, text: 'status1'}, {value: 2, text: 'status2'} ]; $scope.showStatus = function() { var selected = $filter('filter')($scope.statuses, {value: $scope.user.status}); return ($scope.user.status && selected.length) ? selected[0].text : 'Not set'; }; });
{{ debug["bsdate"] | json }}
Date control is implemented via Angular-ui bootstrap datepicker.
Currently it has only Bootstrap 2 version, Bootstrap 3 version is in progress.
You should include additional ui-bootstrap-tpls.min.js
:
<script src="//cdnjs.cloudflare.com/ajax/libs/angular-ui-bootstrap/0.6.0/ui-bootstrap-tpls.min.js"></script>
Add ui.bootstrap
as module dependency:
var app = angular.module("app", ["xeditable", "ui.bootstrap"]);
And set editable-bsdate
attribute in editable element.
Other parameters can be defined via e-*
syntax, e.g. e-datepicker-popup="dd-MMMM-yyyy"
.
<div ng-controller="BsdateCtrl"> <a href="#" editable-bsdate="user.dob" e-datepicker-popup="dd-MMMM-yyyy"> {{ (user.dob | date:"dd/MM/yyyy") || 'empty' }} </a> </div>
app.controller('BsdateCtrl', function($scope) { $scope.user = { dob: new Date(1984, 4, 15) }; });
{{ debug["bstime"] | json }}
Time control is implemented via Angular-ui bootstrap timepicker.
Currently it has only Bootstrap 2 version, Bootstrap 3 version is in progress.
You should include additional ui-bootstrap-tpls.min.js
:
<script src="//cdnjs.cloudflare.com/ajax/libs/angular-ui-bootstrap/0.6.0/ui-bootstrap-tpls.min.js"></script>
Add ui.bootstrap
as module dependency:
var app = angular.module("app", ["xeditable", "ui.bootstrap"]);
And set editable-bstime
attribute in editable element.
Other parameters can be defined via e-*
syntax, e.g. e-minute-step="10"
.
To get it working with Bootstrap 3 you should add following css:
/* temporary workaround for display editable-bstime in bs3 - up/down symbols not shown */
.editable-bstime .editable-input i.icon-chevron-up:before {
content: '\e113';
}
.editable-bstime .editable-input i.icon-chevron-down:before {
content: '\e114';
}
.editable-bstime .editable-input i.icon-chevron-up,
.editable-bstime .editable-input i.icon-chevron-down {
position: relative;
top: 1px;
display: inline-block;
font-family: 'Glyphicons Halflings';
-webkit-font-smoothing: antialiased;
font-style: normal;
font-weight: normal;
line-height: 1;
}
<div ng-controller="BstimeCtrl"> <a href="#" editable-bstime="user.time" e-show-meridian="false" e-minute-step="10"> {{ (user.time | date:"HH:mm") || 'empty' }} </a> </div>
app.controller('BstimeCtrl', function($scope) { $scope.user = { time: new Date(1984, 4, 15, 19, 20) }; });
{{ debug["typeahead"] | json }}
Typeahead control is implemented via Angular-ui bootstrap typeahead.
Basically it is normal editable-text
control with additional e-typeahead
attributes.
You should include additional ui-bootstrap-tpls.min.js
:
<script src="//cdnjs.cloudflare.com/ajax/libs/angular-ui-bootstrap/0.6.0/ui-bootstrap-tpls.min.js"></script>
Then add ui.bootstrap
as module dependency:
var app = angular.module("app", ["xeditable", "ui.bootstrap"]);
And finally set editable-text
attribute pointing to model and e-typeahead
attribute pointing to typeahead items.
Other parameters can be defined via e-typeahead-*
syntax, e.g. e-typeahead-wait-ms="100"
.
<div ng-controller="TypeaheadCtrl"> <a href="#" editable-text="user.state" e-typeahead="state for state in states | filter:$viewValue | limitTo:8"> {{ user.state || 'empty' }} </a> </div>
app.controller('TypeaheadCtrl', function($scope) { $scope.user = { state: 'Arizona' }; $scope.states = ['Alabama', 'Alaska', 'Arizona', 'Arkansas', 'California', 'Colorado', 'Connecticut', 'Delaware', 'Florida', 'Georgia', 'Hawaii', 'Idaho', 'Illinois', 'Indiana', 'Iowa', 'Kansas', 'Kentucky', 'Louisiana', 'Maine', 'Maryland', 'Massachusetts', 'Michigan', 'Minnesota', 'Mississippi', 'Missouri', 'Montana', 'Nebraska', 'Nevada', 'New Hampshire', 'New Jersey', 'New Mexico', 'New York', 'North Dakota', 'North Carolina', 'Ohio', 'Oklahoma', 'Oregon', 'Pennsylvania', 'Rhode Island', 'South Carolina', 'South Dakota', 'Tennessee', 'Texas', 'Utah', 'Vermont', 'Virginia', 'Washington', 'West Virginia', 'Wisconsin', 'Wyoming']; });
{{ debug["ui-select-simple"] | json }}
Date control is implemented via Angular-ui ui-select.
And set editable-uiselect
attribute in editable element.
Other parameters can be defined via e-*
syntax.
e-datasrc
: data sourcee-valuekey
: which property will present for your valuee-displaynamekey
: which property will present for your display string<div ng-controller="UiSelectSimpleCtrl"> <a href="#" editable-uiselect="user.name" e-style="width: 300px;" e-datasrc="people" e-valuekey="name" e-displaynamekey="email"> {{user.name}} </a> </div>
app.controller('UiSelectSimpleCtrl', function($scope, $http, $q) { $scope.user = { name: 'Adam'}; $scope.people = [ { name: 'Adam', email: 'adam@email.com', age: 10 }, { name: 'Amalie', email: 'amalie@email.com', age: 12 }, { name: 'Wladimir', email: 'wladimir@email.com', age: 30 }, { name: 'Samantha', email: 'samantha@email.com', age: 31 }, { name: 'Estefanía', email: 'estefanía@email.com', age: 16 }, { name: 'Natasha', email: 'natasha@email.com', age: 54 }, { name: 'Nicole', email: 'nicole@email.com', age: 43 }, { name: 'Adrian', email: 'adrian@email.com', age: 21 } ]; });
{{ debug["ui-select-template"] | json }}
Date control is implemented via Angular-ui ui-select.
And set editable-uiselect
attribute in editable element.
Other parameters can be defined via e-*
syntax.
e-template
: template for your control (which will be appended to <ui-select>
element)<div ng-controller="UiSelectTplCtrl"> <a href="#" editable-uiselect="user.name" e-style="width: 300px;" e-template="'userListTpl'"> {{user.name}} </a> </div> <script type="text/ng-template" id="userListTpl"> <ui-select-match placeholder="Enter an user...">{{$select.selected.name}}</ui-select-match> <ui-select-choices repeat="value.name as value in people | uiSelectSearchFilter: {name: $select.search}"> <div ng-bind-html="value.name | highlight:$select.search"></div> <div ng-bind="value.email"></div> </ui-select-choices> </script>
app.controller('UiSelectTplCtrl', function($scope, $http, $q) { $scope.user = { name: 'Adam'}; $scope.people = [ { name: 'Adam', email: 'adam@email.com', age: 10 }, { name: 'Amalie', email: 'amalie@email.com', age: 12 }, { name: 'Wladimir', email: 'wladimir@email.com', age: 30 }, { name: 'Samantha', email: 'samantha@email.com', age: 31 }, { name: 'Estefanía', email: 'estefanía@email.com', age: 16 }, { name: 'Natasha', email: 'natasha@email.com', age: 54 }, { name: 'Nicole', email: 'nicole@email.com', age: 43 }, { name: 'Adrian', email: 'adrian@email.com', age: 21 } ]; });
{{ debug["ui-select-remote"] | json }}
Date control is implemented via Angular-ui ui-select.
And set editable-uiselect
attribute in editable element.
Other parameters can be defined via e-*
syntax.
e-datasrc
: data sourcee-valuekey
: which property will present for your valuee-displaynamekey
: which property will present for your display string<div ng-controller="UiSelectRemoteCtrl"> <a href="#" editable-uiselect="user.name" e-style="width: 300px;" e-template="'addressInputTpl'" onshow="loadPeople()"> {{user.name}} </a> </div> <script type="text/ng-template" id="addressInputTpl"> <ui-select-match placeholder="Enter an user...">{{$select.selected.name}}</ui-select-match> <ui-select-choices repeat="value.name as value in people | uiSelectSearchFilter: {name: $select.search}"> <div ng-bind-html="value.name | highlight:$select.search"></div> <div ng-bind="value.email"></div> </ui-select-choices> </script>
app.controller('UiSelectRemoteCtrl', function($scope, $http, $q) { $scope.user = { name: 'Adam'}; $scope.people = []; $scope.loadPeople = function() { return $scope.people.length ? null : $http.get('/people').success(function(data) { $scope.people = data; $scope.people; }); }; });
{{ debug["custom"] | json }}
This directive let user can custom their input
e-template
: template for your inputonshow
: call your initialize input$modelScope
: is a variable relate to editable scope.<div ng-controller="Custom"> <a href="#" editable-custom="user.name" e-template="'customTpl'" onshow="updateName($modelScope)"> {{user.name}} </a> </div> <script type="text/ng-template" id="customTpl"> <table> <tr> <td> First Name </td> <td> <input type="text" ng-model="firstName" ng-change="updateName($modelScope)" /> </td> </tr> <tr> <td>Last Name</td> <td> <input type="text" ng-model="lastName" ng-change="updateName($modelScope)" /> </td> </td> </table> </script>
app.controller('Custom', function($scope, $http, $q) { $scope.user = { name: 'Adam'}; $scope.firstName="bruno" $scope.lastName = "mars"; $scope.updateName = function(modelScope) { var userName = modelScope.firstName + " " + modelScope.lastName; modelScope.$data = userName; } });
{{ debug["text-customize"] | json }}
To define attributes for input (e.g. style
or placeholder
) you can define them
on the original element with e-*
prefix (e.g. e-style
or e-placeholder
).
When input will appear these attributes will be transfered to it.
<div ng-controller="TextCustomizeCtrl"> <a href="#" editable-text="user.name" e-style="color: green" e-required e-placeholder="Enter name"> {{ (user.name || 'empty') | uppercase }} </a> </div>
app.controller('TextCustomizeCtrl', function($scope) { $scope.user = { name: 'awesome user' }; });
{{ debug["text-btn"] | json }}
To trigger edit-in-place by external button you should define e-form
attribute.
Value of it is the name of variable to be created in scope that allows you to show / hide editor manually.
<div ng-controller="TextBtnCtrl"> <span editable-text="user.name" e-form="textBtnForm"> {{ user.name || 'empty' }} </span> <button class="btn btn-default" ng-click="textBtnForm.$show()" ng-hide="textBtnForm.$visible"> edit </button> </div>
app.controller('TextBtnCtrl', function($scope) { $scope.user = { name: 'awesome user' }; });
{{ debug["select-multiple"] | json }}
Just define e-multiple
attribute that will be transfered to select as multiple
.
<div ng-controller="SelectMultipleCtrl"> <a href="#" editable-select="user.status" e-multiple e-ng-options="s.value as s.text for s in statuses"> {{ showStatus() }} </a> </div>
app.controller('SelectMultipleCtrl', function($scope, $filter) { $scope.user = { status: [2, 4] }; $scope.statuses = [ {value: 1, text: 'status1'}, {value: 2, text: 'status2'}, {value: 3, text: 'status3'}, {value: 4, text: 'status4'} ]; $scope.showStatus = function() { var selected = []; angular.forEach($scope.statuses, function(s) { if ($scope.user.status.indexOf(s.value) >= 0) { selected.push(s.text); } }); return selected.length ? selected.join(', ') : 'Not set'; }; });
{{ debug["validate-local"] | json }}
Validation is performed via onbeforesave
attribute pointing to validation method.
Value is available as $data
parameter. If method returns string - validation failed
and string shown as error message.
<div ng-controller="ValidateLocalCtrl"> <a href="#" editable-text="user.name" onbeforesave="checkName($data)"> {{ user.name || 'empty' }} </a> </div>
app.controller('ValidateLocalCtrl', function($scope) { $scope.user = { name: 'awesome user' }; $scope.checkName = function(data) { if (data !== 'awesome') { return "Username should be `awesome`"; } }; });
{{ debug["validate-remote"] | json }}
You can perform remote validation e.g. checking username in db.
For that define validation method returning $q
promise.
If promise resolves to string validation failed.
<div ng-controller="ValidateRemoteCtrl"> <a href="#" editable-text="user.name" onbeforesave="checkName($data)"> {{ user.name || 'empty' }} </a> </div>
app.controller('ValidateRemoteCtrl', function($scope, $http, $q) { $scope.user = { name: 'awesome user' }; $scope.checkName = function(data) { var d = $q.defer(); $http.post('/checkName', {value: data}).success(function(res) { res = res || {}; if(res.status === 'ok') { // {status: "ok"} d.resolve() } else { // {status: "error", msg: "Username should be `awesome`!"} d.resolve(res.msg) } }).error(function(e){ d.reject('Server error!'); }); return d.promise; }; });
{{ debug["o-validator"] | json }}
To validate a form field you can add o-validator
attribute to validate your input. o-validator
can be inputed by multiple validator(which are seperated by comma).
Ex: <a href="#" o-validator="required,max300chars"
To define your own validation rule, you have to config your rules in editableValidationRules
factory.
Ex:
editableValidationRules.addValidator({
validatorName:'max300chars',
errorMsg: 'Max 300 characters',
validationFunc: max300chars
});
<div ng-controller="OValidatorCtrl"> <a href="#" o-validator="required" onbeforesave="checkname($data)" editable-text="user.name">{{ user.name || 'empty' }}</a> </div>
app.controller('OValidatorCtrl', function($scope) { $scope.user = { name: 'awesome user' }; $scope.isCheckedname = false; $scope.checkname= function() { $scope.isCheckedname = true; } });
{{ debug["onbeforesave"] | json }}
One way to submit data on server is to define onbeforesave
attribute pointing to some method of scope.
Useful when you need to send data on server first and only then update local model (e.g. $scope.user
).
New value can be passed as $data
parameter (e.g. <a ... onbeforesave="updateUser($data)">
).
The main thing is that local model will be updated only if method returns true
or undefined
(or promise resolved to true/undefined
). Commonly there are 3 cases depending on result type:
true
or undefined
:
Success. Local model will be updated automatically and form will close.false
:
Success. But local model will not be updated and form will close. Useful when you want to update local model manually (e.g. server changed values).string
:
Error. Local model will not be updated, form will not close, string will be shown as error message.
Useful for validation and processing errors.<div ng-controller="OnbeforesaveCtrl"> <a href="#" editable-text="user.name" onbeforesave="updateUser($data)"> {{ user.name || 'empty' }} </a> </div>
app.controller('OnbeforesaveCtrl', function($scope, $http) { $scope.user = { id: 1, name: 'awesome user' }; $scope.updateUser = function(data) { console.log(data); return $http.post('/updateUser', {id: $scope.user.id, name: data}); }; });;
{{ debug["onaftersave"] | json }}
Another way to submit data on server is to define onaftersave
attribute pointing to some method of scope.
Useful when you need to update local model first and only then send it to server.
There are no input parameters as data already in local model.
The result type of this method can be following:
string
: success, form will be closedstring
: error, form will not close, string will be shown as error messageNote that you can use both onbeforesave
for validation and onaftersave
for saving data.
<div ng-controller="OnaftersaveCtrl"> <a href="#" editable-text="user.name" onaftersave="updateUser()"> {{ user.name || 'empty' }} </a> </div>
app.controller('OnaftersaveCtrl', function($scope, $http) { $scope.user = { id: 1, name: 'awesome user' }; $scope.updateUser = function() { return $http.post('/updateUser', $scope.user); }; });
To show several editable elements together and submit at once you should wrap them into <form editable-form name="myform" ...>
tag. The name
attribute of form will create variable in scope (normal angular behavior) and editable-form
attribute will add a few methods to that variable:
Use it to toggle editable state of form. For example, you can call myform.$show()
.
Editable form supports 3 additional attributes:
They work nearly the same as for individual editable elements. Use it instead of ng-submit / submit
to get more control over saving process.
When you submit editable form it performs following steps:
onbeforesave
onbeforesave
$scope.user
)onaftersave
onaftersave
Any onbeforesave / onaftersave
can be omited so in simplest case you will just write data to local model.
But in more complex case it becomes usefull:
If you need validation of individual editable elements then you should define onbeforesave
on particular editable element.
The result of child's onbeforesave
is important for next step:
string
: submit will be cancelled, form will stay opened, string will be shown as error messagenot string
: submit will be continuedIf you need to send data on server before writing to local model then you should define form's onbeforesave
.
The result of form's onbeforesave
is important for next step:
true
or undefined
: local model will be updated and form will call onaftersave
false
: local model will not be updated and form will just close (e.g. you update local model yourself)string
: local model will not be updated and form will not close (e.g. server error)If you need to send data on server after writing to local model then you should define form's onaftersave
.
The result of form's onaftersave
is also important for next step:
string
: form will not close (e.g. server error)not string
: form will be closedCommonly you should define onbeforesave
for child elements to perform validation and onaftersave
for whole form to send data on server.
Please have a look at examples.
<div ng-controller="EditableFormCtrl"> <form editable-form name="editableForm" onaftersave="saveUser()"> <div> <!-- editable username (text with validation) --> <span class="title">User name: </span> <span editable-text="user.name" e-name="name" o-validator="required" onbeforesave="checkName($data)">{{ user.name || 'empty' }}</span> </div> <div> <!-- editable status (select-local) --> <span class="title">Status: </span> <span editable-select="user.status" e-name="status" e-ng-options="s.value as s.text for s in statuses"> {{ (statuses | filter:{value: user.status})[0].text || 'Not set' }} </span> </div> <div> <!-- editable group (select-remote) --> <span class="title">Group: </span> <span editable-select="user.group" e-name="group" onshow="loadGroups()" e-ng-options="g.id as g.text for g in groups"> {{ showGroup() }} </span> </div> <div class="buttons"> <!-- button to show form --> <button type="button" class="btn btn-default" ng-click="editableForm.$show()" ng-show="!editableForm.$visible"> Edit </button> <!-- buttons to submit / cancel form --> <span ng-show="editableForm.$visible"> <button type="submit" class="btn btn-primary" ng-disabled="editableForm.$waiting"> Save </button> <button type="button" class="btn btn-default" ng-disabled="editableForm.$waiting" ng-click="editableForm.$cancel()"> Cancel </button> </span> </div> </form> </div>
app.controller('EditableFormCtrl', function($scope, $filter, $http) { $scope.user = { id: 1, name: 'awesome user', status: 2, group: 4, groupName: 'admin' }; $scope.statuses = [ {value: 1, text: 'status1'}, {value: 2, text: 'status2'}, {value: 3, text: 'status3'}, {value: 4, text: 'status4'} ]; $scope.groups = []; $scope.loadGroups = function() { return $scope.groups.length ? null : $http.get('/groups').success(function(data) { $scope.groups = data; }); }; $scope.showGroup = function() { if($scope.groups.length) { var selected = $filter('filter')($scope.groups, {id: $scope.user.group}); return selected.length ? selected[0].text : 'Not set'; } else { return $scope.user.groupName; } }; $scope.checkName = function(data) { if (data !== 'awesome' && data !== 'error') { return "Username should be `awesome` or `error`"; } }; $scope.saveUser = function() { // $scope.user already updated! return $http.post('/saveUser', $scope.user).error(function(err) { if(err.field && err.msg) { // err like {field: "name", msg: "Server-side error for this username!"} $scope.editableForm.$setError(err.field, err.msg); } else { // unknown error $scope.editableForm.$setError('name', 'Unknown error!'); } }); }; });
Name | Status | Group | Edit |
{{ user.name || 'empty' }} | {{ showStatus(user) }} | {{ showGroup(user) }} |
To create editable row in table you should place editable elements in cells with e-form
attribute pointing to form's name. The form itself can appear later (e.g. in last column) but it will work. Don't forget to add
editable-form
attribute to the form. The form behavior is absolutely the same as described in
Editable form section
<div ng-controller="EditableRowCtrl"> <table class="table table-bordered table-hover table-condensed"> <tr style="font-weight: bold"> <td style="width:35%">Name</td> <td style="width:20%">Status</td> <td style="width:20%">Group</td> <td style="width:25%">Edit</td> </tr> <tr ng-repeat="user in users"> <td> <!-- editable username (text with validation) --> <span editable-text="user.name" e-name="name" e-form="rowform" onbeforesave="checkName($data, user.id)" e-required> {{ user.name || 'empty' }} </span> </td> <td> <!-- editable status (select-local) --> <span editable-select="user.status" e-name="status" e-form="rowform" e-ng-options="s.value as s.text for s in statuses"> {{ showStatus(user) }} </span> </td> <td> <!-- editable group (select-remote) --> <span editable-select="user.group" e-name="group" onshow="loadGroups()" e-form="rowform" e-ng-options="g.id as g.text for g in groups"> {{ showGroup(user) }} </span> </td> <td style="white-space: nowrap"> <!-- form --> <form editable-form name="rowform" onbeforesave="saveUser($data, user.id)" ng-show="rowform.$visible" class="form-buttons form-inline" shown="inserted == user"> <button type="submit" ng-disabled="rowform.$waiting" class="btn btn-primary"> save </button> <button type="button" ng-disabled="rowform.$waiting" ng-click="rowform.$cancel()" class="btn btn-default"> cancel </button> </form> <div class="buttons" ng-show="!rowform.$visible"> <button class="btn btn-primary" ng-click="rowform.$show()">edit</button> <button class="btn btn-danger" ng-click="removeUser($index)">del</button> </div> </td> </tr> </table> <button class="btn btn-default" ng-click="addUser()">Add row</button> </div>
app.controller('EditableRowCtrl', function($scope, $filter, $http) { $scope.users = [ {id: 1, name: 'awesome user1', status: 2, group: 4, groupName: 'admin'}, {id: 2, name: 'awesome user2', status: undefined, group: 3, groupName: 'vip'}, {id: 3, name: 'awesome user3', status: 2, group: null} ]; $scope.statuses = [ {value: 1, text: 'status1'}, {value: 2, text: 'status2'}, {value: 3, text: 'status3'}, {value: 4, text: 'status4'} ]; $scope.groups = []; $scope.loadGroups = function() { return $scope.groups.length ? null : $http.get('/groups').success(function(data) { $scope.groups = data; }); }; $scope.showGroup = function(user) { if(user.group && $scope.groups.length) { var selected = $filter('filter')($scope.groups, {id: user.group}); return selected.length ? selected[0].text : 'Not set'; } else { return user.groupName || 'Not set'; } }; $scope.showStatus = function(user) { var selected = []; if(user.status) { selected = $filter('filter')($scope.statuses, {value: user.status}); } return selected.length ? selected[0].text : 'Not set'; }; $scope.checkName = function(data, id) { if (id === 2 && data !== 'awesome') { return "Username 2 should be `awesome`"; } }; $scope.saveUser = function(data, id) { //$scope.user not updated yet angular.extend(data, {id: id}); return $http.post('/saveUser', data); }; // remove user $scope.removeUser = function(index) { $scope.users.splice(index, 1); }; // add user $scope.addUser = function() { $scope.inserted = { id: $scope.users.length+1, name: '', status: null, group: null }; $scope.users.push($scope.inserted); }; });
Name | Status | Group |
{{ user.name || 'empty' }} | {{ showStatus(user) }} | {{ showGroup(user) }} |
To create editable column in table you should place editable elements in cells with e-form
attribute pointing to form's name. The form itself can appear in column header or footer. The form behavior is absolutely the same as described in
Editable form section
<div ng-controller="EditableColumnCtrl"> <table class="table table-bordered table-hover table-condensed"> <tr style="font-weight: bold; white-space: nowrap"> <!-- username header --> <td style="width:40%"> Name <form editable-form name="nameform" onaftersave="saveColumn('name')" ng-show="nameform.$visible"> <button type="submit" ng-disabled="nameform.$waiting" class="btn btn-primary"> save </button> <button type="button" ng-disabled="nameform.$waiting" ng-click="nameform.$cancel()" class="btn btn-default"> cancel </button> </form> <button class="btn btn-default" ng-show="!nameform.$visible" ng-click="nameform.$show()"> edit </button> </td> <!-- status header --> <td style="width:30%"> Status <form editable-form name="statusform" onaftersave="saveColumn('status')" ng-show="statusform.$visible"> <button type="submit" ng-disabled="statusform.$waiting" class="btn btn-primary"> save </button> <button type="button" ng-disabled="statusform.$waiting" ng-click="statusform.$cancel()" class="btn btn-default"> cancel </button> </form> <button class="btn btn-default" ng-show="!statusform.$visible" ng-click="statusform.$show()"> edit </button> </td> <!-- group header --> <td style="width:30%"> Group <form editable-form name="groupform" onaftersave="saveColumn('group')" ng-show="groupform.$visible"> <button type="submit" ng-disabled="groupform.$waiting" class="btn btn-primary"> save </button> <button type="button" ng-disabled="groupform.$waiting" ng-click="groupform.$cancel()" class="btn btn-default"> cancel </button> </form> <button class="btn btn-default" ng-show="!groupform.$visible" ng-click="groupform.$show()"> edit </button> </td> </tr> <tr ng-repeat="user in users"> <td> <!-- editable username (text with validation) --> <span editable-text="user.name" e-name="name" e-form="nameform" onbeforesave="checkName($data)"> {{ user.name || 'empty' }} </span> </td> <td> <!-- editable status (select-local) --> <span editable-select="user.status" e-name="status" e-form="statusform" e-ng-options="s.value as s.text for s in statuses"> {{ showStatus(user) }} </span> </td> <td> <!-- editable group (select-remote) --> <span editable-select="user.group" e-name="group" onshow="loadGroups()" e-form="groupform" e-ng-options="g.id as g.text for g in groups"> {{ showGroup(user) }} </span> </td> </tr> </table> </div>
app.controller('EditableColumnCtrl', function($scope, $filter, $http, $q) { $scope.users = [ {id: 1, name: 'awesome user1', status: 2, group: 4, groupName: 'admin'}, {id: 2, name: 'awesome user2', status: undefined, group: 3, groupName: 'vip'}, {id: 3, name: 'awesome user3', status: 2, group: null} ]; $scope.statuses = [ {value: 1, text: 'status1'}, {value: 2, text: 'status2'}, {value: 3, text: 'status3'}, {value: 4, text: 'status4'} ]; $scope.groups = []; $scope.loadGroups = function() { return $scope.groups.length ? null : $http.get('/groups').success(function(data) { $scope.groups = data; }); }; $scope.showGroup = function(user) { if(user.group && $scope.groups.length) { var selected = $filter('filter')($scope.groups, {id: user.group}); return selected.length ? selected[0].text : 'Not set'; } else { return user.groupName || 'Not set'; } }; $scope.showStatus = function(user) { var selected = []; if(user.status) { selected = $filter('filter')($scope.statuses, {value: user.status}); } return selected.length ? selected[0].text : 'Not set'; }; $scope.checkName = function(data) { if (data !== 'awesome') { return "Username should be `awesome`"; } }; $scope.saveColumn = function(column) { var results = []; angular.forEach($scope.users, function(user) { results.push($http.post('/saveColumn', {column: column, value: user[column], id: user.id})); }) return $q.all(results); }; });
Just wrap the whole table into <form>
tag with editable-form
attribute.
Note that using oncancel
hook allows you to revert all changes and put table into original state.
<div ng-controller="EditableTableCtrl"> <form editable-form name="tableform" onaftersave="saveTable()" oncancel="cancel()"> <!-- table --> <table class="table table-bordered table-hover table-condensed"> <tr style="font-weight: bold"> <td style="width:40%">Name</td> <td style="width:30%">Status</td> <td style="width:30%">Group</td> <td style="width:30%"><span ng-show="tableform.$visible">Action</span></td> </tr> <tr ng-repeat="user in users | filter:filterUser"> <td> <!-- editable username (text with validation) --> <span editable-text="user.name" e-form="tableform" onbeforesave="checkName($data, user.id)"> {{ user.name || 'empty' }} </span> </td> <td> <!-- editable status (select-local) --> <span editable-select="user.status" e-form="tableform" e-ng-options="s.value as s.text for s in statuses"> {{ showStatus(user) }} </span> </td> <td> <!-- editable group (select-remote) --> <span editable-select="user.group" e-form="tableform" onshow="loadGroups()" e-ng-options="g.id as g.text for g in groups"> {{ showGroup(user) }} </span> </td> <td><button type="button" ng-show="tableform.$visible" ng-click="deleteUser(user.id)" class="btn btn-danger pull-right">Del</button></td> </tr> </table> <!-- buttons --> <div class="btn-edit"> <button type="button" class="btn btn-default" ng-show="!tableform.$visible" ng-click="tableform.$show()"> edit </button> </div> <div class="btn-form" ng-show="tableform.$visible"> <button type="button" ng-disabled="tableform.$waiting" ng-click="addUser()" class="btn btn-default pull-right">add row</button> <button type="submit" ng-disabled="tableform.$waiting" class="btn btn-primary">save</button> <button type="button" ng-disabled="tableform.$waiting" ng-click="tableform.$cancel()" class="btn btn-default">cancel</button> </div> </form> </div>
app.controller('EditableTableCtrl', function($scope, $filter, $http, $q) { $scope.users = [ {id: 1, name: 'awesome user1', status: 2, group: 4, groupName: 'admin'}, {id: 2, name: 'awesome user2', status: undefined, group: 3, groupName: 'vip'}, {id: 3, name: 'awesome user3', status: 2, group: null} ]; $scope.statuses = [ {value: 1, text: 'status1'}, {value: 2, text: 'status2'}, {value: 3, text: 'status3'}, {value: 4, text: 'status4'} ]; $scope.groups = []; $scope.loadGroups = function() { return $scope.groups.length ? null : $http.get('/groups').success(function(data) { $scope.groups = data; }); }; $scope.showGroup = function(user) { if(user.group && $scope.groups.length) { var selected = $filter('filter')($scope.groups, {id: user.group}); return selected.length ? selected[0].text : 'Not set'; } else { return user.groupName || 'Not set'; } }; $scope.showStatus = function(user) { var selected = []; if(user.status) { selected = $filter('filter')($scope.statuses, {value: user.status}); } return selected.length ? selected[0].text : 'Not set'; }; $scope.checkName = function(data, id) { if (id === 2 && data !== 'awesome') { return "Username 2 should be `awesome`"; } }; // filter users to show $scope.filterUser = function(user) { return user.isDeleted !== true; }; // mark user as deleted $scope.deleteUser = function(id) { var filtered = $filter('filter')($scope.users, {id: id}); if (filtered.length) { filtered[0].isDeleted = true; } }; // add user $scope.addUser = function() { $scope.users.push({ id: $scope.users.length+1, name: '', status: null, group: null, isNew: true }); }; // cancel all changes $scope.cancel = function() { for (var i = $scope.users.length; i--;) { var user = $scope.users[i]; // undelete if (user.isDeleted) { delete user.isDeleted; } // remove new if (user.isNew) { $scope.users.splice(i, 1); } }; }; // save edits $scope.saveTable = function() { var results = []; for (var i = $scope.users.length; i--;) { var user = $scope.users[i]; // actually delete user if (user.isDeleted) { $scope.users.splice(i, 1); } // mark as not new if (user.isNew) { user.isNew = false; } // send on server results.push($http.post('/saveUser', user)); } return $q.all(results); }; });
There are several themes that can be used to style editable controls.
Include Bootstrap 3 CSS
<link href="//netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap.min.css" rel="stylesheet">
Set theme in app.run
:
app.run(function(editableOptions) { editableOptions.theme = 'bs3'; });
To have smaller or bigger controls modify inputClass
and buttonsClass
properties of theme:
app.run(function(editableOptions, editableThemes) { editableThemes.bs3.inputClass = 'input-sm'; editableThemes.bs3.buttonsClass = 'btn-sm'; editableOptions.theme = 'bs3'; });
Include Bootstrap 2 CSS
<link href="//netdna.bootstrapcdn.com/twitter-bootstrap/2.3.2/css/bootstrap-combined.min.css" rel="stylesheet">
Set theme in app.run
:
app.run(function(editableOptions) { editableOptions.theme = 'bs2'; });
No additional CSS required.
You can customize theme in app.run
by overwriting properties:
app.run(function(editableOptions, editableThemes) { // set `default` theme editableOptions.theme = 'default'; // overwrite submit button template editableThemes['default'].submitTpl = '<button type="submit">ok</button>'; });
Available properties of each theme you can see in source themes.js
To change appearance of editable links you should overwrite CSS:
a.editable-click { color: green; border-bottom: dotted 2px green; } a.editable-click:hover { color: #47a447; border-bottom-color: #47a447; }View customized theme in jsFiddle
List of all possible attributes, properties and methods.
Attributes can be defined for any element having editable-xxx
directive:
<a href="#" editable-text="user.name" [attributes]> {{user.name | "empty"}} </a>
Name | Type | Description |
---|---|---|
blur |
string | Action when control losses focus. Values: |
buttons |
string | Whether to show ok/cancel buttons. Values: |
e-* |
any | Attributes defined with |
onaftersave |
method | Called during submit after value is saved to model. |
onbeforesave |
method | Called during submit before value is saved to model. |
oncancel |
method | Called when control is cancelled. |
onhide |
method | Called when control is hidden after both save or cancel. |
onshow |
method | Called when control is shown. |
Attributes can be defined as:
<form editable-form name="myform" [attributes]> ... </form>
Name | Type | Description |
---|---|---|
blur |
string | Action when form losses focus. Values: |
onaftersave |
method | Called when form values are saved to model. |
onbeforesave |
method | Called after all children |
oncancel |
method | Called when form is cancelled. |
onhide |
method | Called when form hides after both save or cancel. |
onshow |
method | Called when form is shown. |
shown |
bool | Whether form initially rendered in shown state. |
Properties are available when you set name
attribute of form:
<form editable-form name="myform"> // now myform.[property] is available in template and $scope.myform.[property] - in controller
Name | Type | Description |
---|---|---|
$visible |
bool | Form visibility flag. |
$waiting |
bool | Form waiting flag. It becomes |
Methods are available when you set name
attribute of form:
<form editable-form name="myform"> // now myform.[method] is available in template and $scope.myform.[method] - in controller
Method | Params | Description |
---|---|---|
$activate(name) |
name (string) name of field |
Sets focus on form field specified by |
$cancel() |
none | Triggers |
$hide() |
none | Hides form with editable controls without saving. |
$setError(name, msg) |
name (string) name of field msg (string) error message |
Shows error message for particular field. |
$show() |
none | Shows form with editable controls. |