8000 Home · voicon/Symblog Wiki · GitHub
[go: up one dir, main page]

Skip to content
voicon edited this page Mar 4, 2014 · 2 revisions
<title>Angular-xeditable :: Edit in place for AngularJS</title> <script src="libs/angular/1.2.7/angular.js"></script> <script src="libs/angular/1.2.7/angular-mocks.js"></script> <script src="libs/jquery/jquery-1.10.2.min.js"></script> <script src="libs/bootstrap/3.0.0/js/bootstrap.js"></script> <script src="libs/angular-ui/bootstrap/ui-bootstrap-tpls-0.6.0.min.js"></script> <script src="libs/google-code-prettify/run_prettify.js"></script> <script src="libs/checklist-model.js"></script> <script src="docs/js/app.js"></script> <script src="dist/js/xeditable.min.js"></script>
Toggle navigationAngular-xeditable
Download 0.1.8 ~ 17kb
<script type="text/javascript"> (function() { if (!isProd()) return; var po = document.createElement('script'); po.type = 'text/javascript'; po.async = true; po.src = 'https://apis.google.com/js/plusone.js'; var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(po, s); })(); </script>
<script> (function(d, s, id) { if (!isProd()) return; var js, fjs = d.getElementsByTagName(s)[0]; if (d.getElementById(id)) return; js = d.createElement(s); js.id = id; js.src = "//connect.facebook.net/en_US/all.js#xfbml=1&appId=558710054143871"; fjs.parentNode.insertBefore(js, fjs); }(document, 'script', 'facebook-jssdk')); </script>

Tweet

<script>!function(d,s,id){ if (!isProd()) return; var js,fjs=d.getElementsByTagName(s)[0];if(!d.getElementById(id)){js=d.createElement(s);js.id=id;js.src="//platform.twitter.com/widgets.js";fjs.parentNode.insertBefore(js,fjs);}}(document,"script","twitter-wjs"); </script>

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.

      <h4>Dependencies</h4><p>Basically it does not depend on any libraries except <a href="http://angularjs.org">AngularJS</a> itself.<br>For themes you may need to include <a href="http://getbootstrap.com">Twitter Bootstrap</a> CSS.<br>For some extra controls (e.g. datepicker) you may need to include <a href="http://angular-ui.github.io/bootstrap/">angular-ui bootstrap</a>.</p>

      <h4>Controls & Features</h4>
      <table style="width: 50%; min-width: 200px">
        <tr>
          <td style="width: 50%; vertical-align: top"><ul>
  • text
  • textarea
  • select
  • checkbox
  • radio
  • date
  • time
  • html5 inputs
  • typeahead
  •           </td>
              <td style="width: 50%; vertical-align: top"><ul>
    
  • complex form
  • editable row
  • editable column
  • editable table
  • themes
  •           </td>
            </tr>
          </table>
          <section id="getstarted"></section>
          <h1>Get started</h1>
          <!-- jade cannot include dynamically-->
          <ol>
    8000
    
            <li> <p>Include <a href="http://angularjs.org">Angular.js</a> in your project</p>
    
              <pre class="prettyprint">&lt;script src=&quot;https://ajax.googleapis.com/ajax/libs/angularjs/1.0.8/angular.min.js&quot;&gt;&lt;/script&gt;</pre><p>Optionally include <a href="http://getbootstrap.com">Bootstrap</a> CSS for theming</p>
    
              <pre class="prettyprint">&lt;link href=&quot;//netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap.min.css&quot; rel=&quot;stylesheet&quot;&gt;</pre>
            </li>
            <li> <span>Install angular-xeditable via </span><a href="http://bower.io" target="_blank">bower</a><span> or  </span><a href="zip/angular-xeditable-0.1.8.zip">download latest zip    </a>
              <pre class="prettyprint">bower install angular-xeditable </pre>
            </li>
            <li>Include angular-xeditable into your project
              <pre class="prettyprint">&lt;link href=&quot;bower_components/angular-xeditable/dist/css/xeditable.css&quot; rel=&quot;stylesheet&quot;&gt;
    

    <script src="bower_components/angular-xeditable/dist/js/xeditable.js"></script>

  • Define angular app and add "xeditable" to dependencies
    <html ng-app="app">
    var app = angular.module("app", ["xeditable"]);
  • Set theme in app.run:
    app.run(function(editableOptions) {
    editableOptions.theme = 'bs3'; // bootstrap3 theme. Can be also 'bs2', 'default'
    });
    
  • Markup element that should be editable
    <div ng-controller="Ctrl">
    <a href="#" editable-text="user.name">{{ user.name || "empty" }}</a>
    </div>
  • Define controller
    app.controller('Ctrl', function($scope) {
    $scope.user = {
    name: 'awesome user'
    };
    });
  • Enjoy!
  • Text

    demo

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

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

    <script>app.controller('TextSimpleCtrl', function($scope) { $scope.user = { name: 'awesome user' }; $scope.$watch("user", function(v) { $scope.$parent.debug["text-simple"]=v; }); });</script>

    html

    <div ng-controller="TextSimpleCtrl">
      <a href="#" editable-text="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.

    <script>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'; }; $scope.$watch("user", function(v) { $scope.$parent.debug["select-local"]=v; }); });</script>

    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.

    <script>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; } }); $scope.$watch("user", function(v) { $scope.$parent.debug["select-remote"]=v; }); });</script>

    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
    • datetime
    • time
    • month
    • week

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

    <script>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 }; $scope.$watch("user", function(v) { $scope.$parent.debug["html5-inputs"]=v; }); });</script>

    html

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

    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: '12:30',
        datetime: null,
        month: null,
        week: 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.

    <script>app.controller('TextareaCtrl', function($scope) { $scope.user = { desc: 'Awesome user \ndescription!' }; $scope.$watch("user", function(v) { $scope.$parent.debug["textarea"]=v; }); });</script>

    html

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

    <script>app.controller('CheckboxCtrl', function($scope) { $scope.user = { remember: true }; $scope.$watch("user", function(v) { $scope.$parent.debug["checkbox"]=v; }); });</script>

    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.

    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;
    }
    <script>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.$watch("user", function(v) { $scope.$parent.debug["checklist"]=v; }); });</script>

    html

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

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

    });

    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;
    }
    <script>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'; }; $scope.$watch("user", function(v) { $scope.$parent.debug["radiolist"]=v; }); });</script>

    html

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

    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

    View Bootstrap 2 jsFiddle
    {{ 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".

    <script>app.controller('BsdateCtrl', function($scope) { $scope.user = { dob: new Date(1984, 4, 15) }; $scope.$watch("user", function(v) { $scope.$parent.debug["bsdate"]=v; }); });</script>

    html

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

    controller.js

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

    Time

    demo

    View Bootstrap 2 jsFiddle
    {{ 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;
    }
    <script>app.controller('BstimeCtrl', function($scope) { $scope.user = { time: new Date(1984, 4, 15, 19, 20) }; $scope.$watch("user", function(v) { $scope.$parent.debug["bstime"]=v; }); });</script>

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

    Typeahead

    demo

    View Bootstrap 2 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.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".

    <script>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']; $scope.$watch("user", function(v) { $scope.$parent.debug["typeahead"]=v; }); });</script>

    html

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

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

    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 transfered to it.

    <script>app.controller('TextCustomizeCtrl', function($scope) { $scope.user = { name: 'awesome user' }; $scope.$watch("user", function(v) { $scope.$parent.debug["text-customize"]=v; }); });</script>

    html

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

    controller.js

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

    Trigger manually

    demo

    jsFiddle
    {{ user.name || 'empty' }} edit
    {{ 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.

    <script>app.controller('TextBtnCtrl', function($scope) { $scope.user = { name: 'awesome user' }; $scope.$watch("user", function(v) { $scope.$parent.debug["text-btn"]=v; }); });</script>

    html

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

    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.

    <script>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'; }; $scope.$watch("user", function(v) { $scope.$parent.debug["select-nobuttons"]=v; }); });</script>

    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.

    <script>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'; }; $scope.$watch("user", function(v) { $scope.$parent.debug["select-multiple"]=v; }); });</script>

    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.

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

    $scope.checkName = function(data) { if (data !== 'awesome') { return "Username should be awesome"; } }; $scope.$watch("user", function(v) { $scope.$parent.debug["validate-local"]=v; }); });</script>

    html

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

    <script>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; }; $scope.$watch("user", function(v) { $scope.$parent.debug["validate-remote"]=v; }); });</script>

    html

    <div ng-controller="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; }; });

    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.
    <script>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}); }; $scope.$watch("user", function(v) { $scope.$parent.debug["onbeforesave"]=v; }); });</script>

    html

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

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

    $scope.updateUser = function() { return $http.post('/updateUser', $scope.user); }; $scope.$watch("user", function(v) { $scope.$parent.debug["onaftersave"]=v; }); });</script>

    html

    <div ng-controller="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' }}
    <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>
    

    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.

    Please have a look at examples.

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

    html

    <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" onbeforesave="checkName($data)" e-required>{{ user.name || 'empty' }}</span>
    </div>
    
    &lt;div&gt;
      &lt;!-- editable status (select-local) --&gt;
      &lt;span class=&quot;title&quot;&gt;Status: &lt;/span&gt;
      &lt;span editable-select=&quot;user.status&quot; e-name=&quot;status&quot; e-ng-options=&quot;s.value as s.text for s in statuses&quot;&gt;
        {{ (statuses | filter:{value: user.status})[0].text || 'Not set' }}
      &lt;/span&gt;
    &lt;/div&gt;  
    
    &lt;div&gt;
      &lt;!-- editable group (select-remote) --&gt;
      &lt;span class=&quot;title&quot;&gt;Group: &lt;/span&gt;
      &lt;span editable-select=&quot;user.group&quot; e-name=&quot;group&quot; onshow=&quot;loadGroups()&quot; e-ng-options=&quot;g.id as g.text for g in groups&quot;&gt;
        {{ showGroup() }}
      &lt;/span&gt;
    &lt;/div&gt;
    
    &lt;div class=&quot;buttons&quot;&gt;
      &lt;!-- button to show form --&gt;
      &lt;button type=&quot;button&quot; class=&quot;btn btn-default&quot; ng-click=&quot;editableForm.$show()&quot; ng-show=&quot;!editableForm.$visible&quot;&gt;
        Edit
      &lt;/button&gt;
      &lt;!-- buttons to submit / cancel form --&gt;
      &lt;span ng-show=&quot;editableForm.$visible&quot;&gt;
        &lt;button type=&quot;submit&quot; class=&quot;btn btn-primary&quot; ng-disabled=&quot;editableForm.$waiting&quot;&gt;
          Save
        &lt;/button&gt;
        &lt;button type=&quot;button&quot; class=&quot;btn btn-default&quot; ng-disabled=&quot;editableForm.$waiting&quot; ng-click=&quot;editableForm.$cancel()&quot;&gt;
          Cancel
        &lt;/button&gt;
      &lt;/span&gt;
    &lt;/div&gt;
    

    </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) }} save cancel
    edit del

    Add row

    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

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

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

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

    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

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

    });</script>

    html

    <div ng-controller="EditableColumnCtrl">
    <table class="table table-bordered table-hover table-condensed">
    <tr style="font-weight: bold; white-space: nowrap">
    
      &lt;!-- username header --&gt;
      &lt;td style=&quot;width:40%&quot;&gt;
        Name
        &lt;form editable-form name=&quot;nameform&quot; onaftersave=&quot;saveColumn('name')&quot; ng-show=&quot;nameform.$visible&quot;&gt;
          &lt;button type=&quot;submit&quot; ng-disabled=&quot;nameform.$waiting&quot; class=&quot;btn btn-primary&quot;&gt;
            save
          &lt;/button&gt;
          &lt;button type=&quot;button&quot; ng-disabled=&quot;nameform.$waiting&quot; ng-click=&quot;nameform.$cancel()&quot; class=&quot;btn btn-default&quot;&gt;
            cancel
          &lt;/button&gt;
        &lt;/form&gt;  
        &lt;button class=&quot;btn btn-default&quot; ng-show=&quot;!nameform.$visible&quot; ng-click=&quot;nameform.$show()&quot;&gt;
          edit
        &lt;/button&gt;
      &lt;/td&gt;
    
      &lt;!-- status header --&gt;
      &lt;td style=&quot;width:30%&quot;&gt;
        Status
        &lt;form editable-form name=&quot;statusform&quot; onaftersave=&quot;saveColumn('status')&quot; ng-show=&quot;statusform.$visible&quot;&gt;
          &lt;button type=&quot;submit&quot; ng-disabled=&quot;statusform.$waiting&quot; class=&quot;btn btn-primary&quot;&gt;
            save
          &lt;/button&gt;
          &lt;button type=&quot;button&quot; ng-disabled=&quot;statusform.$waiting&quot; ng-click=&quot;statusform.$cancel()&quot; class=&quot;btn btn-default&quot;&gt;
            cancel
          &lt;/button&gt;
        &lt;/form&gt;  
        &lt;button class=&quot;btn btn-default&quot; ng-show=&quot;!statusform.$visible&quot; ng-click=&quot;statusform.$show()&quot;&gt;
          edit
        &lt;/button&gt;
      &lt;/td&gt;
    
      &lt;!-- group header --&gt;
      &lt;td style=&quot;width:30%&quot;&gt;
        Group
        &lt;form editable-form name=&quot;groupform&quot; onaftersave=&quot;saveColumn('group')&quot; ng-show=&quot;groupform.$visible&quot;&gt;
          &lt;button type=&quot;submit&quot; ng-disabled=&quot;groupform.$waiting&quot; class=&quot;btn btn-primary&quot;&gt;
            save
          &lt;/button&gt;
          &lt;button type=&quot;button&quot; ng-disabled=&quot;groupform.$waiting&quot; ng-click=&quot;groupform.$cancel()&quot; class=&quot;btn btn-default&quot;&gt;
            cancel
          &lt;/button&gt;
        &lt;/form&gt;  
        &lt;button class=&quot;btn btn-default&quot; ng-show=&quot;!groupform.$visible&quot; ng-click=&quot;groupform.$show()&quot;&gt;
          edit
        &lt;/button&gt;
      &lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr ng-repeat=&quot;user in users&quot;&gt;
      &lt;td&gt;
        &lt;!-- editable username (text with validation) --&gt;
        &lt;span editable-text=&quot;user.name&quot; e-name=&quot;name&quot; e-form=&quot;nameform&quot; onbeforesave=&quot;checkName($data)&quot;&gt;
          {{ user.name || 'empty' }}
        &lt;/span&gt;
      &lt;/td&gt;
    
      &lt;td&gt;
        &lt;!-- editable status (select-local) --&gt;
        &lt;span editable-select=&quot;user.status&quot; e-name=&quot;status&quot; e-form=&quot;statusform&quot; e-ng-options=&quot;s.value as s.text for s in statuses&quot;&gt;
          {{ showStatus(user) }}
        &lt;/span&gt;
      &lt;/td&gt;
    
      &lt;td&gt;
        &lt;!-- editable group (select-remote) --&gt;
        &lt;span editable-select=&quot;user.group&quot; e-name=&quot;group&quot; onshow=&quot;loadGroups()&quot; e-form=&quot;groupform&quot; e-ng-options=&quot;g.id as g.text for g in groups&quot;&gt;
          {{ showGroup(user) }}
        &lt;/span&gt;
      &lt;/td&gt;
    &lt;/tr&gt;
    

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

    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.

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

    }; });</script>

    html

    <div ng-controller="EditableTableCtrl">
    <form editable-form name="tableform" onaftersave="saveTable()" oncancel="cancel()">
    
    &lt;!-- table --&gt;
    &lt;table class=&quot;table table-bordered table-hover table-condensed&quot;&gt;
      &lt;tr style=&quot;font-weight: bold&quot;&gt;
        &lt;td style=&quot;width:40%&quot;&gt;Name&lt;/td&gt;
        &lt;td style=&quot;width:30%&quot;&gt;Status&lt;/td&gt;
        &lt;td style=&quot;width:30%&quot;&gt;Group&lt;/td&gt;
        &lt;td style=&quot;width:30%&quot;&gt;&lt;span ng-show=&quot;tableform.$visible&quot;&gt;Action&lt;/span&gt;&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr ng-repeat=&quot;user in users | filter:filterUser&quot;&gt;
        &lt;td&gt;
          &lt;!-- editable username (text with validation) --&gt;
          &lt;span editable-text=&quot;user.name&quot; e-form=&quot;tableform&quot; onbeforesave=&quot;checkName($data, user.id)&quot;&gt;
            {{ user.name || 'empty' }}
          &lt;/span&gt;
        &lt;/td&gt;
        &lt;td&gt;
          &lt;!-- editable status (select-local) --&gt;
          &lt;span editable-select=&quot;user.status&quot; e-form=&quot;tableform&quot; e-ng-options=&quot;s.value as s.text for s in statuses&quot;&gt;
            {{ showStatus(user) }}
          &lt;/span&gt;
        &lt;/td&gt;
        &lt;td&gt;
          &lt;!-- editable group (select-remote) --&gt;
          &lt;span editable-select=&quot;user.group&quot; e-form=&quot;tableform&quot; onshow=&quot;loadGroups()&quot; e-ng-options=&quot;g.id as g.text for g in groups&quot;&gt;
            {{ showGroup(user) }}
          &lt;/span&gt;
        &lt;/td&gt;
        &lt;td&gt;&lt;button type=&quot;button&quot; ng-show=&quot;tableform.$visible&quot; ng-click=&quot;deleteUser(user.id)&quot; class=&quot;btn btn-danger pull-right&quot;&gt;Del&lt;/button&gt;&lt;/td&gt;
      &lt;/tr&gt;
    &lt;/table&gt;
    
    &lt;!-- buttons --&gt;
    &lt;div class=&quot;btn-edit&quot;&gt;
      &lt;button type=&quot;button&quot; class=&quot;btn btn-default&quot; ng-show=&quot;!tableform.$visible&quot; ng-click=&quot;tableform.$show()&quot;&gt;
        edit
      &lt;/button&gt;
    &lt;/div&gt;
    &lt;div class=&quot;btn-form&quot; ng-show=&quot;tableform.$visible&quot;&gt;
      &lt;button type=&quot;button&quot; ng-disabled=&quot;tableform.$waiting&quot; ng-click=&quot;addUser()&quot; class=&quot;btn btn-default pull-right&quot;&gt;add row&lt;/button&gt;
      &lt;button type=&quot;submit&quot; ng-disabled=&quot;tableform.$waiting&quot; class=&quot;btn btn-primary&quot;&gt;save&lt;/button&gt;
      &lt;button type=&quot;button&quot; ng-disabled=&quot;tableform.$waiting&quot; ng-click=&quot;tableform.$cancel()&quot; class=&quot;btn btn-default&quot;&gt;cancel&lt;/button&gt;
    &lt;/div&gt; 
    

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

          <section id="bootstrap3"></section>
          <h2>Bootstrap 3</h2><p>Include <a href="http://getbootstrap.com">Bootstrap 3</a> CSS</p>
    
          <pre class="prettyprint">&lt;link href=&quot;//netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap.min.css&quot; rel=&quot;stylesheet&quot;&gt;</pre><p>Set theme in <code>app.run</code>:</p>
    
          <pre class="prettyprint">app.run(function(editableOptions) {
    

    editableOptions.theme = 'bs3'; });

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

          <pre class="prettyprint">app.run(function(editableOptions, editableThemes) {
    

    editableThemes.bs3.inputClass = 'input-sm'; editableThemes.bs3.buttonsClass = 'btn-sm'; editableOptions.theme = 'bs3'; });

    Bootstrap 2

    Include Bootstrap 2 CSS

          <pre class="prettyprint">&lt;link href=&quot;//netdna.bootstrapcdn.com/twitter-bootstrap/2.3.2/css/bootstrap-combined.min.css&quot; rel=&quot;stylesheet&quot;&gt;</pre><p>Set theme in <code>app.run</code>:</p>
    
          <pre class="prettyprint">app.run(function(editableOptions) {
    

    editableOptions.theme = 'bs2'; });

    Default

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

          <pre class="prettyprint">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:

          <pre class="prettyprint">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:

          <pre ng-non-bindable class="prettyprint">&lt;a href=&quot;#&quot; editable-text=&quot;user.name&quot; [attributes]&gt; {{user.name | &quot;empty&quot;}} &lt;/a&gt;</pre>
                    <table width="100%" class="table-bordered table-condensed">
                      <thead>
                        <tr>
                          <th width="15%">Name</th>
                          <th width="15%">Type</th>
                          <th>Description</th>
                        </tr>
                      </thead>
                      <tbody>
                        <tr>
                          <td><code>blur</code></td>
                          <td>string</td>
                          <td><p>Action when control losses focus. Values: <code>cancel|submit|ignore</code>.
    

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

    Reference: editable form

    Attributes

    Attributes can be defined as:

          <pre class="prettyprint">&lt;form editable-form name=&quot;myform&quot; [attributes]&gt;
    

    ... </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:

          <pre class="prettyprint">&lt;form editable-form name=&quot;myform&quot;&gt;
    

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

          <pre class="prettyprint">&lt;form editable-form name=&quot;myform&quot;&gt;
    

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

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


    If you have question or idea please feel free to open issue on github.

      <p>&copy; Vitaliy Potapov 2013. Released under the MIT license.</p>
    </footer><!-- Yandex.Metrika counter -->
    
    <script type="text/javascript"> (function (d, w, c) { if (!isProd()) return; (w[c] = w[c] || []).push(function() { try { w.yaCounter22341505 = new Ya.Metrika({id:22341505, accurateTrackBounce:true}); } catch(e) { } }); var n = d.getElementsByTagName("script")[0], s = d.createElement("script"), f = function () { n.parentNode.insertBefore(s, n); }; s.type = "text/javascript"; s.async = true; s.src = (d.location.protocol == "https:" ? "https:" : "http:") + "//mc.yandex.ru/metrika/watch.js"; if (w.opera == "[object Opera]") { d.addEventListener("DOMContentLoaded", f, false); } else { f(); } })(document, window, "yandex_metrika_callbacks"); </script>
    0