Overview

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.

Dependencies

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 for Bootstrap 2/3. Include ui-bootstrap4 for Bootstrap 4. For ui-select you may need to include angular-ui ui-select. For ngTagsInput you may need to include mbenford ngTagsInput. If installing via NPM, jQuery and moment js will also be installed.

Controls & Features

Get started

  1. Include Angular.js in your project

    <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.0/angular.min.js"></script>

    Optionally include Bootstrap CSS for theming

    Bootstrap 3

    <link href="https://netdna.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" rel="stylesheet">

    Boostrap 4

    <link href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" rel="stylesheet">
  2. Install angular-xeditable viabower ordownload latest zip
    bower install angular-xeditable
  3. Include angular-xeditable into your project
    <link href="bower_components/angular-xeditable/dist/css/xeditable.css" rel="stylesheet">
    <script src="bower_components/angular-xeditable/dist/js/xeditable.js"></script>
  4. Define angular app and add "xeditable" to dependencies
    <html ng-app="app">
    var app = angular.module("app", ["xeditable"]);
  5. Set theme in app.run:
    app.run(['editableOptions', function(editableOptions) {
      editableOptions.theme = 'bs3'; // bootstrap3 theme. Can be also 'bs4', 'bs2', 'default'
    }]);
    
  6. Markup element that should be editable
    <div ng-controller="Ctrl">
      <a href="#" editable-text="user.name">{{ user.name || "empty" }}</a>
    </div>
  7. Define controller
    app.controller('Ctrl', function($scope) {
      $scope.user = {
        name: 'awesome user'
      };
    });
  8. Enjoy!

Text

demo

jsFiddle
{{ debug["text-simple"] | json }}

To make element editable via textbox just add editable-text="model.field" attribute.

html

<div ng-controller="TextSimpleCtrl" id="TextSimpleCtrl">
  <a href="#" editable-text="user.name" e-label="User Name">{{ user.name || 'empty' }}</a>
</div>

controller.js

app.controller('TextSimpleCtrl', function($scope) {
  $scope.user = {
    name: 'awesome user'
  };  
});

Select local

demo

jsFiddle
{{ 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. To set a default option in the list add the e-placeholder attribute.

html

<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>

controller.js

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';
  };
});

Select remote

demo

jsFiddle
{{ 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.

html

<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>

controller.js

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;
    }
  });
});

HTML5 inputs

demo

jsFiddle
{{ debug["html5-inputs"] | json }}

Following HTML5 types are supported via editable-xxx directive:

  • email
  • tel
  • number
  • search
  • range
  • url
  • color
  • date
  • time
  • month
  • week
  • password
  • datetime-local

Please check browser support before using particular type in your project.

html

<div ng-controller="Html5InputsCtrl" id="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 | date: "yyyy-MM-dd") || 'empty' }}</a></div>
  <div>Time: <a href="#" editable-time="user.time">{{ (user.time | date: "HH:mm") || 'empty' }}</a></div>
  <div>Month: <a href="#" editable-month="user.month">{{ (user.month | date: "yyyy-MM") || 'empty' }}</a></div>
  <div>Week: <a href="#" editable-week="user.week">{{ (user.week | date: "yyyy-Www") || 'empty'}}</a></div>
  <div>Password: <a href="#" editable-password="user.password">{{ user.password || 'empty' }}</a></div>
  <div>Datetime-local: <a href="#" editable-datetime-local="user.datetimeLocal">{{ (user.datetimeLocal | date: "yyyy-MM-ddTHH:mm:ss") || 'empty' }}</a></div>
  <div>File: <a href="#" editable-file="user.file">{{ user.file || 'empty' }}</a></div>
</div>

controller.js

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: new Date(1970, 0, 1, 12, 30),
    month: null,
    week: null,
    password: 'password',
    datetimeLocal: null,
    file: null
  };  
});

Textarea

demo

jsFiddle
{{ 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. Data can be submitted by Enter if the attribute submit-on-enter="true" is used.

html

<div ng-controller="TextareaCtrl" id="TextareaCtrl">
  <a href="#" editable-textarea="user.desc" e-rows="7" e-cols="40">
    <pre>{{ user.desc || 'no description' }}</pre>
  </a>
</div>

controller.js

app.controller('TextareaCtrl', function($scope) {
  $scope.user = {
    desc: 'Awesome user \ndescription!'
  };
});

Checkbox

demo

jsFiddle
{{ 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.

html

<div ng-controller="CheckboxCtrl">
  <a href="#" editable-checkbox="user.remember" e-title="Remember?">
    {{ user.remember && "Remember me!" || "Don't remember" }}
  </a>
</div>

controller.js

app.controller('CheckboxCtrl', function($scope) {
  $scope.user = {
    remember: true
  };  
});

Checklist

demo

{{ 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. Optionally define e-checklist-comparator to use a function to determine which checkboxes are actually checked.

Please note, you should include checklist-model directive into your app: var app = angular.module("app", [..., "checklist-model"]);.

To disable a checkbox include the attribute e-ng-disabled and pass a condition.

By default, checkboxes aligned horizontally. To align vertically just add following CSS:

.editable-checklist label {
  display: block;
}

html

<div ng-controller="ChecklistCtrl">
  <a href="#" editable-checklist="user.status" e-ng-options="s.value as s.text for s in statuses" e-ng-disabled="disableCheckbox(s.value)">
    {{ showStatus() }}
  </a>
</div>

controller.js

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';
  };

  $scope.disableCheckbox = function (v) {
    if ($scope.user.status.indexOf(v) === -1) {
      return true
    }
    return false;
  }

});

Radiolist

demo

{{ 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;
}

html

<div ng-controller="RadiolistCtrl">
  <a href="#" editable-radiolist="user.status" e-ng-options="s.value as s.text for s in ::statuses track by s.value">
    {{ showStatus() }}
  </a>
</div>

controller.js

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';
  };
});

Date

demo

jsFiddle
{{ debug["bsdate"] | json }}

Date control is implemented via Angular-ui bootstrap datepicker. You should include additional ui-bootstrap-tpls.min.js for Bootstrap 3:

<script src="https://cdnjs.cloudflare.com/ajax/libs/angular-ui-bootstrap/1.3.1/ui-bootstrap-tpls.min.js"></script>

For Bootstrap 4, include:

<script src="https://cdn.jsdelivr.net/npm/ui-bootstrap4@3.0.6/dist/ui-bootstrap-tpls.js"></script>

Add ui.bootstrap as module dependency:

var app = angular.module("app", ["xeditable", "ui.bootstrap"]);

And set editable-bsdate attribute in editable element.
To make the input field read-only and force the date to be selected from the popup, add the e-readonly="true" attribute.
Add e-ng-change attribute to call a function when the value of the datepicker is changed.
To hide the calendar button and display the calendar popup on click of the input field, set the e-show-calendar-button attribute to false.
Other parameters can be defined via e-* syntax, e.g. e-datepicker-popup="dd-MMMM-yyyy".

html

<div ng-controller="BsdateCtrl">
  <a href="#" editable-bsdate="user.dob"
              e-is-open="opened.$data"
              e-ng-click="open($event,'$data')"
              e-datepicker-popup="dd-MMMM-yyyy">
    {{ (user.dob | date:"dd/MM/yyyy") || 'empty' }}
  </a>
</div>

controller.js

app.controller('BsdateCtrl', function($scope) {
	$scope.user = {
		dob: new Date(1984, 4, 15)
	};

	$scope.opened = {};

	$scope.open = function($event, elementOpened) {
		$event.preventDefault();
		$event.stopPropagation();

		$scope.opened[elementOpened] = !$scope.opened[elementOpened];
	};
});

UI Date

demo

{{ (user.dob | date:"dd/MM/yyyy") || 'empty' }}
{{ debug["uidate"] | json }}

Date control is implemented via jQuery UI Datepicker for AngularJS.

You should install ui-date with bower:

    bower install angular-ui-date --save    

Add the CSS:

<link rel="stylesheet" href="bower_components/jquery-ui/themes/smoothness/jquery-ui.css"/>

Load the script files (minimun jQuery version is v2.2.0):

<script type="text/javascript" src="bower_components/jquery/jquery.js"></script>
<script type="text/javascript" src="bower_components/jquery-ui/jquery-ui.js"></script>
<script type="text/javascript" src="bower_components/angular/angular.js"></script>
<script type="text/javascript" src="bower_components/angular-ui-date/dist/date.js"></script>

Add the date module as a dependency to your application module:

angular.module('MyApp', ['ui.date'])

And set editable-uidate attribute in editable element.

You can pass an e-ui-date attribute pointing to an object with a picker configuration that you may want.

var datePickerOptions = {
    changeYear: true,
    changeMonth: true,
    showOn: "button",
    buttonImage: "build/assets/img/calendar.png",
    buttonImageOnly: true
};

html

<div ng-controller="UidateCtrl">
    <span editable-uidate="user.dob"
        e-ui-date="datePickerConfig">
        {{ (user.dob | date:"dd/MM/yyyy") || 'empty' }}
    </span>
</div>

controller.js

app.controller('UidateCtrl', function($scope) {

    $scope.datePickerConfig = {
        changeYear: true,
        changeMonth: true
    };

    $scope.user = {
        dob: new Date(1985, 3, 11)
    };

});

Time

demo

jsFiddle
{{ debug["bstime"] | json }}

Time control is implemented via Angular-ui bootstrap timepicker. You should include additional ui-bootstrap-tpls.min.jsfor Bootstrap 3:

<script src="https://cdnjs.cloudflare.com/ajax/libs/angular-ui-bootstrap/1.3.1/ui-bootstrap-tpls.min.js"></script>

For Bootstrap 4, include:

<script src="https://cdn.jsdelivr.net/npm/ui-bootstrap4@3.0.6/dist/ui-bootstrap-tpls.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".

html

<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>

controller.js

app.controller('BstimeCtrl', function($scope) {
  $scope.user = {
    time: new Date(1984, 4, 15, 19, 20)
  };
});

DateTime

demo

{{ debug["combodate"] | json }}

You should include additional moment.js:

And set editable-combodate attribute in editable element. Custom options supported by Combodate can be provided via e-* syntax, e.g. e-min-year="2015", e-max-year="2025".

html

<div ng-controller="CombodateCtrl">
  <a href="#" editable-combodate="user.dob">
    {{ (user.dob | date:"medium") || 'empty' }}
  </a>
</div>

controller.js

app.controller('CombodateCtrl', function($scope) {
  $scope.user = {
    dob: new Date(1984, 4, 15)
  };
});

Typeahead

demo

jsFiddle
{{ 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.jsfor Bootstrap 3:

<script src="https://cdnjs.cloudflare.com/ajax/libs/angular-ui-bootstrap/1.3.1/ui-bootstrap-tpls.min.js"></script>

For Bootstrap 4, include:

<script src="https://cdn.jsdelivr.net/npm/ui-bootstrap4@3.0.6/dist/ui-bootstrap-tpls.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-uib-typeahead attribute pointing to typeahead items. Other parameters can be defined via e-typeahead-* syntax, e.g. e-typeahead-wait-ms="100".

html

<div ng-controller="TypeaheadCtrl" id="TypeaheadCtrl">
  <a href="#" editable-text="user.state" e-uib-typeahead="state for state in states | filter:$viewValue | limitTo:8">
    {{ user.state || 'empty' }}
  </a>
</div>

controller.js

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'];
});

UI-Select

demo

{{user.state}} {{$select.selected}} {{state}}


{{ debug["uiselect"] | json }}

UI-Select control is implemented via AngularJS-native version of Select2 and Selectize. You should include additional select.min.js and select.min.css:

<script src="https://cdnjs.cloudflare.com/ajax/libs/angular-ui-select/0.16.1/select.min.js"></script>

<link href="https://cdnjs.cloudflare.com/ajax/libs/angular-ui-select/0.16.1/select.min.css" rel="stylesheet" media="screen">

Then add ui.select as module dependency:

var app = angular.module("app", ["xeditable", "ui.select"]);

And finally set editable-ui-select attribute pointing to model and editable-ui-select-match to the match criteria and editable-ui-select-choices to the choices.

html

<div ng-controller="UiSelectCtrl">
  <form data-editable-form name="uiSelectForm">
    <div editable-ui-select="user.state" data-e-form="uiSelectForm" data-e-name="state" name="state" theme="bootstrap" data-e-ng-model="user.state" data-e-style="min-width:300px;">
      {{user.state}}
      <editable-ui-select-match placeholder="State">
          {{$select.selected}}
      </editable-ui-select-match>
      <editable-ui-select-choices repeat="state in states | filter: $select.search track by $index">
        {{state}}
      </editable-ui-select-choices>
    </div>
    <br/>
    <div class="buttons">
      <!-- button to show form -->
      <button type="button" class="btn btn-default" ng-click="uiSelectForm.$show()" ng-show="!uiSelectForm.$visible">
        Edit
      </button>
      <!-- buttons to submit / cancel form -->
      <span ng-show="uiSelectForm.$visible">
        <br/>
        <button type="submit" class="btn btn-primary" ng-disabled="uiSelectForm.$waiting">
          Save
        </button>
        <button type="button" class="btn btn-default" ng-disabled="uiSelectForm.$waiting" ng-click="uiSelectForm.$cancel()">
          Cancel
        </button>
      </span>
    </div>
  </form>
</div>

controller.js

app.controller('UiSelectCtrl', 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'];
});

ngTagsInput

demo

{{user.tags}}


{{ debug["ngtags"] | json }}

ngTagsInput control is implemented via Tags input directive for AngularJS . You should include additional ng-tags-input.min.js and ng-tags-input.min.css and ng-tags-input.bootstrap.min.css (if using bootstrap):

<script src="https://cdnjs.cloudflare.com/ajax/libs/ng-tags-input/3.1.1/ng-tags-input.min.js"></script>

<link href="https://cdnjs.cloudflare.com/ajax/libs/ng-tags-input/3.1.1/ng-tags-input.min.css" rel="stylesheet" media="screen">

<link href="https://cdnjs.cloudflare.com/ajax/libs/ng-tags-input/3.1.1/ng-tags-input.bootstrap.min.css" rel="stylesheet" media="screen">

Then add ngTagsInput as module dependency:

var app = angular.module("app", ["xeditable", "ngTagsInput"]);

And finally set editable-tags-input attribute pointing to model and add editable-tags-input-auto-complete tag to do the auto complete criteria.

html

<div ng-controller="NgTagsCtrl">
  <form data-editable-form name="ngTagsForm">
    <div editable-tags-input="user.tags" data-e-form="ngTagsForm" data-e-name="tags" name="tags" id="tags" data-e-ng-model="user.tags">
      {{user.tags}}
        <editable-tags-input-auto-complete source="loadTags($query)"></editable-tags-input-auto-complete>
    </div>
    <br/>
    <div class="buttons">
      <!-- button to show form -->
      <button type="button" class="btn btn-default" ng-click="ngTagsForm.$show()" ng-show="!ngTagsForm.$visible">
        Edit
      </button>
      <!-- buttons to submit / cancel form -->
      <span ng-show="ngTagsForm.$visible">
        <br/>
        <button type="submit" class="btn btn-primary" ng-disabled="ngTagsForm.$waiting">
          Save
        </button>
        <button type="button" class="btn btn-default" ng-disabled="ngTagsForm.$waiting" ng-click="ngTagsForm.$cancel()">
          Cancel
        </button>
      </span>
    </div>
  </form>
</div>

controller.js

app.controller('NgTagsCtrl', function($scope, $http) {
  $scope.user = {
    tags: [
      { text: 'Tag1' },
      { text: 'Tag2' },
      { text: 'Tag3' }
    ],
  };

  $scope.loadTags = function(query) {
    return $http.get('/tags?query=' + query);
  };
});

Customize input

demo

jsFiddle
{{ 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 transferred to it.
To customize the classes on the form the input is in, add e-formclass with the classes to be added.
To use simple bootstrap input groups add either e-inputgroupleft or e-inputgroupright with the text value you want to append.

html

<div ng-controller="TextCustomizeCtrl" id="TextCustomizeCtrl">
  <a href="#" editable-text="user.name"
              e-style="color: green"
              e-required
              e-placeholder="Enter name"
              e-formclass="class1 class2"
              e-inputgroupleft="left"
              e-inputgroupright="right">
    {{ (user.name || 'empty') | uppercase }}
  </a>
</div>

controller.js

app.controller('TextCustomizeCtrl', function($scope) {
  $scope.user = {
    name: 'awesome user'
  };  
});

Trigger manually

demo

jsFiddle
{{ user.name || 'empty' }}
{{ 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.
To trigger edit-in-place with e-form and no button for a single element, add e-clickable="true". This will allow you to have custom form names in an ng-repeat for single elements.

html

<div ng-controller="TextBtnCtrl" id="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>

controller.js

app.controller('TextBtnCtrl', function($scope) {
  $scope.user = {
    name: 'awesome user'
  };  
});

Hide buttons

demo

jsFiddle
{{ debug["select-nobuttons"] | json }}

To hide Ok and Cancel buttons you may set buttons="no" attribute. New value will be saved automatically after change.

html

<div ng-controller="SelectNobuttonsCtrl">
  <a href="#" editable-select="user.status" buttons="no" e-ng-options="s.value as s.text for s in statuses">
    {{ showStatus() }}
  </a>
</div>

controller.js

app.controller('SelectNobuttonsCtrl', 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';
  };
});

Select multiple

demo

jsFiddle
{{ debug["select-multiple"] | json }}

Just define e-multiple attribute that will be transfered to select as multiple.

html

<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>

controller.js

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';
  };
});

Validate local

demo

jsFiddle
{{ 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.

html

<div ng-controller="ValidateLocalCtrl" id="ValidateLocalCtrl">
  <a href="#" editable-text="user.name" onbeforesave="checkName($data)">
    {{ user.name || 'empty' }}
  </a>
</div>

controller.js

app.controller('ValidateLocalCtrl', function($scope) {
  $scope.user = {
    name: 'awesome user'
  };

  $scope.checkName = function(data) {
    if (data !== 'awesome') {
      return "Username should be `awesome`";
    }
  };
});

Validate remote

demo

jsFiddle
{{ 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.

html

<div ng-controller="ValidateRemoteCtrl" id="ValidateRemoteCtrl">
  <a href="#" editable-text="user.name" onbeforesave="checkName($data)">
    {{ user.name || 'empty' }}
  </a>
</div>

controller.js

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;
  };
});

Disable editing

demo

{{ debug["edit-disabled"] | json }}

To disable an element from being editable, just add the edit-disabled="true" attribute. Additionally, you can disable all elements at a global level by setting editableOptions.isDisabled=true.

html

<div ng-controller="EditDisabledCtrl">
  <a href="#" editable-text="user.name" edit-disabled="{{user && user.name == 'awesome user'}}">{{user.name || 'empty' }}</a>
</div>

controller.js

app.controller('EditDisabledCtrl', function($scope) {
  $scope.user = {
    name: 'awesome user'
  };  
});

Editable Popover

demo

{{ debug["editable-popover"] | json }}

To make a single editable field display in a pure css popover, wrap the editable in <div class="popover-wrapper">.

html

<div ng-controller="EditPopoverCtrl">
  <div class="popover-wrapper">
    <a href="#" editable-text="user.name">{{user.name || 'empty' }}</a>
  </div>
</div>

controller.js

app.controller('EditPopoverCtrl', function($scope) {
  $scope.user = {
    name: 'awesome user'
  };  
});

Editable ui-bootstrap Popover

demo

{{ debug["uipopover"] | json }}

To make a single element editable via ui-boostrap popover do the following:

  • Wrap the editable in <div class="ui-popover-wrapper">.
  • Put the following attributes on the editable element:
    • uib-popover-template="'popover.html'" - The template is generated when the popover attribute is set to `true'
    • popover-is-open="popoverIsOpen" - Controls when the popover is opened
    • onshow="popoverIsOpen = !popoverIsOpen" - Opens the popover when the editable form is shown
    • onhide="popoverIsOpen = !popoverIsOpen" - Closes the popover when the editable form is closed
    • popover="true" - Tells the editable directive that this element is to be displayed in the ui-bootstrap popover
    • popover-class="increase-popover-width" - Add this attribute to change the width of the popover

html

<div ng-controller="UiPopoverCtrl" id="UiPopoverCtrl">
  <div class="ui-popover-wrapper">
    <a href="#"
       id="simpleText"
       editable-text="user.name"
       e-label="User Name"
       uib-popover-template="'popover.html'"
       popover-is-open="popoverIsOpen"
       popover-append-to-body="true"
       onshow="popoverIsOpen = !popoverIsOpen"
       onhide="popoverIsOpen = !popoverIsOpen"
       popover-class="increase-popover-width"
       popover="true">{{ user.name || 'empty' }}</a>
  </div>
</div>

controller.js

app.controller('UiPopoverCtrl', function($scope) {
  $scope.user = {
    name: 'awesome user',
      location: 'location 1'
  };
});

Single editable in a form

demo

{{ debug["e-single"] | json }}

Add e-single attribute when you want a single item to be clickable inside of a form.
It ignores the outer form.

html

<div ng-controller="ESingleCtrl" id="ESingleCtrl">
  <form>
    <a href="#" editable-text="user.name" e-single>{{ user.name || 'empty' }}</a>
  </form>
</div>

controller.js

app.controller('ESingleCtrl', function($scope) {
  $scope.user = {
    name: 'awesome user'
  };  
});

Submit via onbeforesave

demo

jsFiddle
{{ 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.

html

<div ng-controller="OnbeforesaveCtrl" id="OnbeforesaveCtrl">
  <a href="#" editable-text="user.name" onbeforesave="updateUser($data)">
    {{ user.name || 'empty' }}
  </a>
</div>

controller.js

app.controller('OnbeforesaveCtrl', function($scope, $http) {
  $scope.user = {
    id: 1,
    name: 'awesome user'
  };

  $scope.updateUser = function(data) {
    return $http.post('/updateUser', {id: $scope.user.id, name: data});
  };
});

Submit via onaftersave

demo

jsFiddle
{{ 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:

  • anything except string: success, form will be closed
  • string: error, form will not close, string will be shown as error message

Note that you can use both onbeforesave for validation and onaftersave for saving data.

html

<div ng-controller="OnaftersaveCtrl" id="OnaftersaveCtrl">
  <a href="#" editable-text="user.name" onaftersave="updateUser()">
    {{ user.name || 'empty' }}
  </a>
</div>

controller.js

app.controller('OnaftersaveCtrl', function($scope, $http) {
  $scope.user = {
    id: 1,
    name: 'awesome user'
  };

  $scope.updateUser = function() {
    return $http.post('/updateUser', $scope.user);
  };
});

Editable form

demo

jsFiddle
User name: {{ user.name || 'empty' }}
Status: {{ (statuses | filter:{value: user.status})[0].text || 'Not set' }}
Group: {{ showGroup() }}

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:

  • $show()
  • $cancel()
  • $visible
  • $waiting

Use it to toggle editable state of form. For example, you can call myform.$show().

Editable form supports 3 additional attributes:

  • onshow: called when form is shown
  • onbeforesave: called on submit before local models update
  • onaftersave: called on submit after local models update

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:

  1. call child's onbeforesave
  2. call form's onbeforesave
  3. write data to local model (e.g. $scope.user)
  4. call form's onaftersave
  5. call child's 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 message
  • not string: submit will be continued

If 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 closed

Commonly you should define onbeforesave for child elements to perform validation and onaftersave for whole form to send data on server.

Note: e-required will not work since HTML5 validation only works if submitting a form with a submit button and editable-form submits via a script.

Please have a look at examples.

html

<div ng-controller="EditableFormCtrl" id="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" onbeforesave="checkName($data)" e-required>{{ 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>

controller.js

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!');
      }
    });
  };
});

Editable row

demo

jsFiddle
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

html

<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)">
          {{ 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 type="button" class="btn btn-primary" ng-click="rowform.$show()">edit</button>
          <button type="button" class="btn btn-danger" ng-click="removeUser($index)">del</button>
        </div>  
      </td>
    </tr>
  </table>

  <button type="button" class="btn btn-default" ng-click="addUser()">Add row</button>
</div>

controller.js

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);
  };
});

Editable column

demo

jsFiddle
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

html

<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>

controller.js

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);
  };

});

Editable table

demo

jsFiddle
Name Status Group Action
{{ user.name || 'empty' }} {{ showStatus(user) }} {{ showGroup(user) }}

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.

html

<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>

controller.js

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);
  };
});

Themes

There are several themes that can be used to style editable controls.

To set the theme for the entire application, set editableOptions.theme in app.run.

To change the theme for a specific control, set the editable-theme attribute. To change the icon_set for a specific control, the editable-icon-set attribute could be set similarly.

To display a clear button, set the editableOptions.displayClearButton=true in app.run.

To change the title and aria-label values of the submit/cancel/clear buttons, set the editableOptions properties:

app.run(['editableOptions', function(editableOptions) {
  editableOptions.submitButtonTitle = 'Submit';
  editableOptions.submitButtonAriaLabel = 'Submit';
  editableOptions.cancelButtonTitle = 'Cancel';
  editableOptions.cancelButtonAriaLabel = 'Cancel';
  editableOptions.clearButtonTitle = 'Clear';
  editableOptions.clearButtonAriaLabel = 'Clear';
}]);

Bootstrap 4

Include Bootstrap 4 CSS

<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" rel="stylesheet">

Set theme in app.run:

app.run(['editableOptions', function(editableOptions) {
  editableOptions.theme = 'bs4';
}]);

To have smaller or bigger controls modify inputClass and buttonsClass properties of theme:

app.run(['editableOptions', 'editableThemes', function(editableOptions, editableThemes) {
  editableThemes.bs4.inputClass = 'form-control-sm';
  editableThemes.bs4.buttonsClass = 'btn-sm';
  editableOptions.theme = 'bs4';
});

Include font-awesome CSS. This is required for the button icons.

<link href="https://stackpath.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet">

Bootstrap 3

Include Bootstrap 3 CSS

<link href="https://netdna.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" rel="stylesheet">

Set theme in app.run:

app.run(['editableOptions', function(editableOptions) {
  editableOptions.theme = 'bs3';
}]);

To have smaller or bigger controls modify inputClass and buttonsClass properties of theme:

app.run(['editableOptions', 'editableThemes', function(editableOptions, editableThemes) {
  editableThemes.bs3.inputClass = 'input-sm';
  editableThemes.bs3.buttonsClass = 'btn-sm';
  editableOptions.theme = 'bs3';
});

Bootstrap 2

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(['editableOptions', function(editableOptions) {
  editableOptions.theme = 'bs2';
}]);

Default

No additional CSS required. You can customize theme in app.run by overwriting properties:

app.run(['editableOptions', 'editableThemes', 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

Reference

List of all possible attributes, properties and methods.

Reference: editable element

Attributes

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: cancel|submit|ignore. Has sense only for single editable element. Otherwise, if control is part of form - you should set blur of form, not of individual element.

buttons string

Whether to show ok/cancel buttons. Values: right|no. If set to no control automatically submitted when value changed. If control is part of form buttons will never be shown.

e-* any

Attributes defined with e-* prefix automatically transferred from original element to control. For example, if you set <span editable-text="user.name" e-style="width: 100px"> then input will appear as <input style="width: 100px">. See demo.

onaftersave method

Called during submit after value is saved to model. See demo.

onbeforesave method

Called during submit before value is saved to model. See demo.

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. See demo.

popover boolean

Whether to show the editable element in a ui-bootstrap popover. Values: true|false.

usemousedown string

Listen to Mouse Down event instead of click: true|false. Has sense only for single editable element.

Reference: editable form

Attributes

Attributes can be defined as:

<form editable-form name="myform" [attributes]>
...
</form>
Name Type Description
blur string

Action when form losses focus. Values: cancel|submit|ignore. Default is ignore.

onaftersave method

Called when form values are saved to model.
See editable-form demo for details.

onbeforesave method

Called after all children onbeforesave callbacks but before saving form values to model.
If at least one children callback returns non-string - it will not not be called.
See editable-form demo for details.

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

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 true when form is loading or saving data.

Methods

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 name.
When trying to set the focus on a form field of a new row in the editable table, the $activate call needs to be wrapped in a $timeout call so that the form is rendered before the $activate function is called.

$cancel() none

Triggers oncancel event and calls $hide().

$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.

Reference: editable options

Options

Options are set in app.run

app.run(['editableOptions', function(editableOptions) {
editableOptions.theme = 'bs3';
...
}]);
Name Type Description
activate string

How input elements get activated. Possible values: focus|select|none. Default is focus

activationEvent string

Event, on which the edit mode gets activated. Can be any event. Default is click

blurElem string

Default value for blur attribute of single editable element. Can be cancel|submit|ignore. Default is cancel

blurForm string

Default value for blur attribute of editable form. Can be cancel|submit|ignore. Default is ignore.

buttons string

Whether to show buttons for single editable element. Possible values right, no. Default is right

cancelButtonAriaLabel string

The default aria label of the cancel button. Default is Cancel

cancelButtonTitle string

The default title of the cancel button. Default is Cancel

clearButtonAriaLabel string

The default aria label of the clear button. Default is Clear

clearButtonTitle string

The default title of the clear button. Default is Clear

displayClearButton boolean

Whether to display the clear button. Default is false

icon set string

icon_set. Possible values font-awesome, default. Default is default

isDisabled boolean

Whether to disable x-editable. Can be overloaded on each element. Default is false

submitButtonAriaLabel string

The default aria label of the submit button. Default is Submit

submitButtonTitle string

The default title of the submit button. Default is Submit

theme string

Theme. Possible values bs4, bs3, bs2, default. Default is default