├── .gitattributes ├── .gitignore ├── CHANGELOG.txt ├── CONTRIBUTING.md ├── Gruntfile.js ├── LICENSE ├── bower.json ├── dist ├── css │ ├── xeditable.css │ └── xeditable.min.css └── js │ ├── xeditable.js │ └── xeditable.min.js ├── docs ├── css │ └── docs.css ├── demos │ ├── bsdate │ │ ├── controller.js │ │ ├── desc.md │ │ ├── test.js │ │ └── view.html │ ├── bstime │ │ ├── controller.js │ │ ├── desc.md │ │ ├── test.js │ │ └── view.html │ ├── checkbox │ │ ├── controller.js │ │ ├── desc.md │ │ ├── test.js │ │ └── view.html │ ├── checklist │ │ ├── controller.js │ │ ├── desc.md │ │ ├── test.js │ │ └── view.html │ ├── combodate │ │ ├── controller.js │ │ ├── desc.md │ │ ├── test.js │ │ └── view.html │ ├── dev-bsdate │ │ ├── controller.js │ │ ├── desc.md │ │ ├── test.js │ │ └── view.html │ ├── dev-checkbox │ │ ├── controller.js │ │ ├── desc.md │ │ ├── test.js │ │ └── view.html │ ├── dev-checklist │ │ ├── controller.js │ │ ├── desc.md │ │ ├── test.js │ │ └── view.html │ ├── dev-combodate │ │ ├── controller.js │ │ ├── desc.md │ │ ├── test.js │ │ └── view.html │ ├── dev-editable-row │ │ ├── controller.js │ │ ├── desc.md │ │ ├── test.js │ │ └── view.html │ ├── dev-eform │ │ ├── controller.js │ │ ├── desc.md │ │ ├── test.js │ │ └── view.html │ ├── dev-form │ │ ├── controller.js │ │ ├── desc.md │ │ ├── test.js │ │ └── view.html │ ├── dev-ngtags │ │ ├── controller.js │ │ ├── desc.md │ │ ├── test.js │ │ └── view.html │ ├── dev-radiolist │ │ ├── controller.js │ │ ├── desc.md │ │ ├── test.js │ │ └── view.html │ ├── dev-select-multiple │ │ ├── controller.js │ │ ├── desc.md │ │ ├── test.js │ │ └── view.html │ ├── dev-select │ │ ├── controller.js │ │ ├── desc.md │ │ ├── test.js │ │ └── view.html │ ├── dev-text │ │ ├── controller.js │ │ ├── desc.md │ │ ├── test.js │ │ └── view.html │ ├── dev-textarea │ │ ├── controller.js │ │ ├── desc.md │ │ ├── test.js │ │ └── view.html │ ├── dev-theme │ │ ├── controller.js │ │ ├── desc.md │ │ ├── test.js │ │ └── view.html │ ├── dev-uiselect │ │ ├── controller.js │ │ ├── desc.md │ │ ├── test.js │ │ └── view.html │ ├── e-single │ │ ├── controller.js │ │ ├── desc.md │ │ ├── test.js │ │ └── view.html │ ├── edit-disabled │ │ ├── controller.js │ │ ├── desc.md │ │ ├── test.js │ │ └── view.html │ ├── editable-column │ │ ├── controller.js │ │ ├── desc.md │ │ ├── test.js │ │ └── view.html │ ├── editable-form │ │ ├── controller.js │ │ ├── desc.md │ │ ├── test.js │ │ └── view.html │ ├── editable-popover │ │ ├── controller.js │ │ ├── desc.md │ │ ├── test.js │ │ └── view.html │ ├── editable-row │ │ ├── controller.js │ │ ├── desc.md │ │ ├── test.js │ │ └── view.html │ ├── editable-table │ │ ├── controller.js │ │ ├── desc.md │ │ ├── test.js │ │ └── view.html │ ├── html5-inputs │ │ ├── controller.js │ │ ├── desc.md │ │ ├── test.js │ │ └── view.html │ ├── ngtags │ │ ├── controller.js │ │ ├── desc.md │ │ ├── test.js │ │ └── view.html │ ├── onaftersave │ │ ├── controller.js │ │ ├── desc.md │ │ ├── test.js │ │ └── view.html │ ├── onbeforesave │ │ ├── controller.js │ │ ├── desc.md │ │ ├── test.js │ │ └── view.html │ ├── radiolist │ │ ├── controller.js │ │ ├── desc.md │ │ ├── test.js │ │ └── view.html │ ├── select-local │ │ ├── controller.js │ │ ├── desc.md │ │ ├── test.js │ │ └── view.html │ ├── select-multiple │ │ ├── controller.js │ │ ├── desc.md │ │ ├── test.js │ │ └── view.html │ ├── select-nobuttons │ │ ├── controller.js │ │ ├── desc.md │ │ ├── test.js │ │ └── view.html │ ├── select-remote │ │ ├── controller.js │ │ ├── desc.md │ │ ├── test.js │ │ └── view.html │ ├── text-btn │ │ ├── controller.js │ │ ├── desc.md │ │ ├── test.js │ │ └── view.html │ ├── text-customize │ │ ├── controller.js │ │ ├── desc.md │ │ ├── test.js │ │ └── view.html │ ├── text-simple │ │ ├── controller.js │ │ ├── desc.md │ │ ├── test.js │ │ └── view.html │ ├── textarea │ │ ├── controller.js │ │ ├── desc.md │ │ ├── test.js │ │ └── view.html │ ├── typeahead │ │ ├── controller.js │ │ ├── desc.md │ │ ├── test.js │ │ └── view.html │ ├── uidate │ │ ├── controller.js │ │ ├── desc.md │ │ ├── test.js │ │ └── view.html │ ├── uipopover │ │ ├── controller.js │ │ ├── desc.md │ │ ├── test.js │ │ └── view.html │ ├── uiselect │ │ ├── controller.js │ │ ├── desc.md │ │ ├── test.js │ │ └── view.html │ ├── validate-local │ │ ├── controller.js │ │ ├── desc.md │ │ ├── test.js │ │ └── view.html │ └── validate-remote │ │ ├── controller.js │ │ ├── desc.md │ │ ├── test.js │ │ └── view.html ├── img │ └── angular32.jpeg ├── jade │ ├── getstarted.jade │ ├── main.jade │ ├── metrika.html │ ├── navbar.jade │ ├── overview.jade │ ├── reference.jade │ ├── scripts-dev.jade │ ├── scripts-prod.jade │ ├── social.html │ └── themes.jade └── js │ ├── app.js │ └── structure.js ├── index.html ├── index.js ├── jsdoc.conf.json ├── package-lock.json ├── package.json ├── readme.md ├── src ├── css │ └── xeditable.css └── js │ ├── directives │ ├── bsdate.js │ ├── bstime.js │ ├── checkbox.js │ ├── checklist.js │ ├── combodate.js │ ├── input.js │ ├── ngtags.js │ ├── radiolist.js │ ├── select.js │ ├── textarea.js │ ├── uidate.js │ └── uiselect.js │ ├── editable-element │ ├── controller.js │ └── directive.js │ ├── editable-form │ ├── controller.js │ └── directive.js │ ├── helpers.js │ ├── icons.js │ ├── module.js │ └── themes.js ├── starter ├── angular-xeditable │ ├── css │ │ ├── xeditable.css │ │ └── xeditable.min.css │ └── js │ │ ├── xeditable.js │ │ └── xeditable.min.js ├── app.js └── index.html ├── test └── e2e │ ├── dev-test.html │ ├── docs-test.html │ └── scenarios.js └── zip ├── angular-xeditable-0.1.0.zip ├── angular-xeditable-0.1.1.zip ├── angular-xeditable-0.1.10.zip ├── angular-xeditable-0.1.11.zip ├── angular-xeditable-0.1.12.zip ├── angular-xeditable-0.1.2.zip ├── angular-xeditable-0.1.3.zip ├── angular-xeditable-0.1.4.zip ├── angular-xeditable-0.1.5.zip ├── angular-xeditable-0.1.6.zip ├── angular-xeditable-0.1.7.zip ├── angular-xeditable-0.1.8.1.zip ├── angular-xeditable-0.1.8.zip ├── angular-xeditable-0.1.9.zip ├── angular-xeditable-0.10.0.zip ├── angular-xeditable-0.10.1.zip ├── angular-xeditable-0.10.2.zip ├── angular-xeditable-0.2.0.zip ├── angular-xeditable-0.3.0.zip ├── angular-xeditable-0.4.0.zip ├── angular-xeditable-0.5.0.zip ├── angular-xeditable-0.6.0.zip ├── angular-xeditable-0.7.0.zip ├── angular-xeditable-0.7.1.zip ├── angular-xeditable-0.8.0.zip ├── angular-xeditable-0.8.1.zip ├── angular-xeditable-0.9.0.zip └── angular-xeditable-starter.zip /.gitattributes: -------------------------------------------------------------------------------- 1 | # Set default behaviour, in case users don't have core.autocrlf set. 2 | * text=auto -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /playground 3 | dev.html 4 | jsdoc.json 5 | /.idea 6 | node_modules 7 | .tmp 8 | .sass-cache 9 | bower_components 10 | npm-debug.log -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## jsFiddle / Plunker 2 | Please use these live templates when creating issues: 3 | http://jsfiddle.net/ckosloski/NfPcH/20243/ 4 | http://plnkr.co/edit/OqOveMtnSs1G68SC36wt?p=preview 5 | 6 | ## Contribute 7 | * Don't include files from the `dist`, `starter` and `zip` folders in your pull request. 8 | * Add new tests in `docs/demos` if possible. 9 | * Make sure all existing tests run. 10 | * Squash your commits before creating the pull request. 11 | 12 | ## How to run the tests 13 | * `npm install` in the project 14 | * `npm run build` - to build the project files 15 | * `npm start` - to start the server 16 | * Run the "doc" tests `http://localhost:8000/test/e2e/docs-test.html` 17 | * Run the "dev" tests `http://localhost:8000/test/e2e/dev-test.html` 18 | * Verify the documentation still works correctly by navigating to `http://localhost:8000` 19 | * To view the "dev" documentation, navigate to `http://localhost:8000/dev.html` 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Vitaliy Potapov 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-xeditable", 3 | "version": "0.10.2", 4 | "description": "Edit in place for AngularJS", 5 | "author": "https://github.com/vitalets", 6 | "license": "MIT", 7 | "homepage": "http://vitalets.github.io/angular-xeditable", 8 | "main": [ 9 | "dist/css/xeditable.css", 10 | "dist/js/xeditable.js" 11 | ], 12 | "ignore": [ 13 | "**/.*", 14 | "node_modules", 15 | "bower_components", 16 | "playground", 17 | "test", 18 | "libs", 19 | "docs", 20 | "zip", 21 | "src", 22 | "starter", 23 | "Gruntfile.js", 24 | "index.html", 25 | "jsdoc.conf.json", 26 | "package.json" 27 | ], 28 | "dependencies": { 29 | "angular": "~1.x" 30 | }, 31 | "devDependencies": { 32 | "angular": "~1.5.0", 33 | "angular-mocks": "~1.5.0", 34 | "angular-scenario": "~1.5.0", 35 | "angular-sanitize": "~1.5.0", 36 | "jquery": "^2.2.4", 37 | "bootstrap": "^3.3.7", 38 | "moment": "^2.17.1", 39 | "checklist-model": "^0.10.0", 40 | "ng-tags-input": "^3.1.1", 41 | "angular-bootstrap": "^2.5.0", 42 | "angular-ui-select": "^0.16.1", 43 | "jquery-ui": "^1.12.1", 44 | "angular-ui-date": "^1.0.1" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /dist/css/xeditable.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | angular-xeditable - 0.10.2 3 | Edit-in-place for angular.js 4 | Build date: 2019-11-01 5 | */ 6 | 7 | .editable-wrap{display:inline-block;white-space:pre;margin:0}.editable-wrap .editable-controls,.editable-wrap .editable-error{margin-bottom:0}.editable-wrap .editable-controls>input,.editable-wrap .editable-controls>select,.editable-wrap .editable-controls>textarea{margin-bottom:0}.editable-wrap .editable-input{display:inline-block}.editable-buttons{display:inline-block;vertical-align:top}.editable-buttons button{margin-left:5px}.editable-input.editable-has-buttons{width:auto}.editable-text{white-space:nowrap}.editable-bsdate{white-space:nowrap}.editable-bstime{white-space:nowrap}.editable-bstime .editable-input input[type=text]{width:46px}.editable-bstime .well-small{margin-bottom:0;padding:10px}.editable-range output{display:inline-block;min-width:30px;vertical-align:top;text-align:center}.editable-color input[type=color]{width:50px}.editable-checkbox label span,.editable-checklist label span,.editable-radiolist label span{margin-left:7px;margin-right:10px}.editable-hide{display:none!important}.editable-click,a.editable-click{text-decoration:none;color:#428bca;border-bottom:dashed 1px #428bca}.editable-click:hover,a.editable-click:hover{text-decoration:none;color:#2a6496;border-bottom-color:#2a6496}.editable-empty,.editable-empty:hover,.editable-empty:focus,a.editable-empty,a.editable-empty:hover,a.editable-empty:focus{font-style:italic;color:#D14;text-decoration:none}.ui-popover-wrapper a{display:inline!important}.ui-popover-wrapper form{display:none!important}.popover-wrapper>a{display:inline!important}.popover-wrapper{display:inline;position:relative}.popover-wrapper form{position:absolute;top:-53px;background:#FFF;border:1px solid #AAA;border-radius:5px;padding:7px;width:auto;display:inline-block;left:50%;z-index:101}.popover-wrapper form:before{content:"";width:0;height:0;border-left:10px solid transparent;border-right:10px solid transparent;border-top:10px solid #AAA;position:absolute;bottom:-10px}.popover-wrapper form:after{content:"";width:0;height:0;border-left:9px solid transparent;border-right:9px solid transparent;border-top:9px solid #FFF;position:absolute;bottom:-9px}@media screen and (max-width:750px){.popover-wrapper form{margin-left:-60px}.popover-wrapper form:before{left:50px}.popover-wrapper form:after{left:51px}}@media screen and (min-width:750px){.popover-wrapper form{margin-left:-110px}.popover-wrapper form:before{left:100px}.popover-wrapper form:after{left:101px}} -------------------------------------------------------------------------------- /docs/demos/bsdate/controller.js: -------------------------------------------------------------------------------- 1 | app.controller('BsdateCtrl', function($scope) { 2 | $scope.user = { 3 | dob: new Date(1984, 4, 15) 4 | }; 5 | 6 | $scope.opened = {}; 7 | 8 | $scope.open = function($event, elementOpened) { 9 | $event.preventDefault(); 10 | $event.stopPropagation(); 11 | 12 | $scope.opened[elementOpened] = !$scope.opened[elementOpened]; 13 | }; 14 | }); -------------------------------------------------------------------------------- /docs/demos/bsdate/desc.md: -------------------------------------------------------------------------------- 1 | Date control is implemented via [Angular-ui bootstrap datepicker](http://angular-ui.github.io/bootstrap/#/datepicker). 2 | You should include additional `ui-bootstrap-tpls.min.js` for Bootstrap 3: 3 | 4 | 5 | 6 | For Bootstrap 4, include: 7 | 8 | 9 | 10 | Add `ui.bootstrap` as module dependency: 11 | 12 | var app = angular.module("app", ["xeditable", "ui.bootstrap"]); 13 | 14 | And set `editable-bsdate` attribute in editable element. 15 | To make the input field read-only and force the date to be selected from the popup, add the `e-readonly="true"` attribute. 16 | Add `e-ng-change` attribute to call a function when the value of the datepicker is changed. 17 | 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. 18 | Other parameters can be defined via `e-*` syntax, e.g. `e-datepicker-popup="dd-MMMM-yyyy"`. -------------------------------------------------------------------------------- /docs/demos/bsdate/test.js: -------------------------------------------------------------------------------- 1 | describe('bsdate', function() { 2 | 3 | beforeEach(function() { 4 | browser().navigateTo(mainUrl); 5 | }); 6 | 7 | it('should show editor and submit new value', function() { 8 | var s = '[ng-controller="BsdateCtrl"] '; 9 | 10 | expect(element(s+'a[editable-bsdate]').css('display')).not().toBe('none'); 11 | expect(element(s+'a[editable-bsdate]').text()).toMatch('15/05/1984'); 12 | element(s+'a[editable-bsdate]').click(); 13 | element(s+'form .input-group-btn button[type="button"]').click(); 14 | 15 | expect(element(s+'a[editable-bsdate]').css('display')).toBe('none'); 16 | expect(element(s+'form[editable-form="$form"]').count()).toBe(1); 17 | expect(element(s+'form input[type="text"]:visible').count()).toBe(1); 18 | expect(element(s+'form input[type="text"]').val()).toBe('15-May-1984'); 19 | expect(element(s+'form .editable-buttons button[type="submit"]:visible').count()).toBe(1); 20 | expect(element(s+'form .editable-buttons button[type="button"]:visible').count()).toBe(1); 21 | expect(element(s+'ul.dropdown-menu:visible').count()).toBe(1); 22 | expect(element(s+'form table button.btn-info span').text()).toMatch('15'); 23 | 24 | //set 29 april 25 | element(s+'form table > tbody > tr:eq(0) > td:eq(1) > button').click(); 26 | expect(element(s+'ul.dropdown-menu:visible').count()).toBe(0); 27 | expect(element(s+'form input[type="text"]').val()).toBe('29-April-1984'); 28 | 29 | //submit 30 | element(s+'form button[type="submit"]').click(); 31 | 32 | expect(element(s+'a[editable-bsdate]').css('display')).not().toBe('none'); 33 | expect(element(s+'a[editable-bsdate]').text()).toMatch('29/04/1984'); 34 | expect(element(s+'form').count()).toBe(0); 35 | }); 36 | }); -------------------------------------------------------------------------------- /docs/demos/bsdate/view.html: -------------------------------------------------------------------------------- 1 |
my text...' 4 | }; 5 | }); -------------------------------------------------------------------------------- /docs/demos/dev-textarea/desc.md: -------------------------------------------------------------------------------- 1 | dev -------------------------------------------------------------------------------- /docs/demos/dev-textarea/test.js: -------------------------------------------------------------------------------- 1 | describe('dev-textarea', function() { 2 | 3 | beforeEach(function() { 4 | browser().navigateTo(mainUrl); 5 | }); 6 | 7 | it('should show html in link and show editor and submit new value', function() { 8 | var s = '[ng-controller="DevTextareaCtrl"] '; 9 | var a = s + 'a#displayHtml '; 10 | 11 | expect(element(a).css('display')).not().toBe('none'); 12 | expect(element(a).text()).toMatch('Titlemy text...'); 13 | element(a).click(); 14 | 15 | expect(element(a).css('display')).toBe('none'); 16 | expect(element(s+'form[editable-form="$form"]').count()).toBe(1); 17 | expect(element(s+'form textarea:visible').count()).toBe(1); 18 | expect(element(s+'form textarea').val()).toMatch('Title
my text...'); 19 | expect(element(s+'form button[type="submit"]:visible').count()).toBe(1); 20 | expect(element(s+'form button[type="button"]:visible').count()).toBe(1); 21 | 22 | using(s).input('$parent.$data').enter('username2'); 23 | element(s+'form button[type="submit"]').click(); 24 | 25 | expect(element(a).css('display')).not().toBe('none'); 26 | expect(element(a).text()).toMatch('username2'); 27 | expect(element(s+'form').count()).toBe(0); 28 | }); 29 | 30 | it('textarea with submit on enter attribute', function() { 31 | var s = '[ng-controller="DevTextareaCtrl"] '; 32 | var a = s + 'a#submitOnEnter '; 33 | 34 | expect(element(a).css('display')).not().toBe('none'); 35 | expect(element(a).text()).toMatch('Titlemy text...'); 36 | element(a).click(); 37 | 38 | expect(element(a).css('display')).toBe('none'); 39 | expect(element(s+'form[editable-form="$form"]').count()).toBe(1); 40 | expect(element(s+'form textarea:visible').count()).toBe(1); 41 | expect(element(s+'form textarea').val()).toMatch('Title
my text...'); 42 | expect(element(s+'form button[type="submit"]:visible').count()).toBe(0); 43 | expect(element(s+'form button[type="button"]:visible').count()).toBe(0); 44 | 45 | using(s).input('$parent.$data').enter('username3'); 46 | 47 | //click body --> submit 48 | element('body').click(); 49 | 50 | expect(element(a).css('display')).not().toBe('none'); 51 | expect(element(a).text()).toMatch('Titlemy text...'); 52 | expect(element(s+'form').count()).toBe(0); 53 | }); 54 | 55 | it('should show textarea with formclasses set', function() { 56 | var s = '[ng-controller="DevTextareaCtrl"] '; 57 | var a = s + 'a#formclass '; 58 | 59 | expect(element(a).css('display')).not().toBe('none'); 60 | expect(element(a).text()).toMatch('Title
my text...'); 61 | element(a).click(); 62 | 63 | expect(element(a).css('display')).toBe('none'); 64 | expect(element(s+'form[editable-form="$form"]').count()).toBe(1); 65 | expect(element(s+'form textarea:visible').count()).toBe(1); 66 | expect(element(s+'form textarea').val()).toMatch('Title
my text...'); 67 | expect(element(s+'form button[type="submit"]:visible').count()).toBe(1); 68 | expect(element(s+'form button[type="button"]:visible').count()).toBe(1); 69 | expect(element(s+'form textarea').attr('formclass')).not().toBeDefined(); 70 | expect(element(s+'form').attr('class')).toBe("form-inline editable-wrap editable-textarea testclass ng-pristine ng-valid ng-scope"); 71 | 72 | using(s).input('$parent.$data').enter('username2'); 73 | element(s+'form button[type="submit"]').click(); 74 | 75 | expect(element(a).css('display')).not().toBe('none'); 76 | expect(element(a).text()).toMatch('username2'); 77 | expect(element(s+'form').count()).toBe(0); 78 | }); 79 | }); -------------------------------------------------------------------------------- /docs/demos/dev-textarea/view.html: -------------------------------------------------------------------------------- 1 |
{{ user.html || 'no description' }}
` tags to keep linebreaks. 3 | Data can be submitted by *Ctrl + Enter*. 4 | Data can be submitted by *Enter* if the attribute `submit-on-enter="true"` is used. -------------------------------------------------------------------------------- /docs/demos/textarea/test.js: -------------------------------------------------------------------------------- 1 | describe('textarea', function() { 2 | 3 | beforeEach(function() { 4 | browser().navigateTo(mainUrl); 5 | }); 6 | 7 | it('should show editor and submit new value', function() { 8 | var s = '[ng-controller="TextareaCtrl"] '; 9 | 10 | expect(element(s+'a').css('display')).not().toBe('none'); 11 | expect(element(s+'a').text()).toMatch('description'); 12 | element(s+'a').click(); 13 | 14 | expect(element(s+'a').css('display')).toBe('none'); 15 | expect(element(s+'form[editable-form="$form"]').count()).toBe(1); 16 | expect(element(s+'form textarea:visible').count()).toBe(1); 17 | expect(element(s+'form textarea').val()).toMatch('description'); 18 | expect(element(s+'form button[type="submit"]:visible').count()).toBe(1); 19 | expect(element(s+'form button[type="button"]:visible').count()).toBe(1); 20 | 21 | using(s).input('$parent.$data').enter('username2'); 22 | element(s+'form button[type="submit"]').click(); 23 | 24 | expect(element(s+'a').css('display')).not().toBe('none'); 25 | expect(element(s+'a').text()).toMatch('username2'); 26 | expect(element(s+'form').count()).toBe(0); 27 | }); 28 | 29 | }); -------------------------------------------------------------------------------- /docs/demos/textarea/view.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | {{ user.desc || 'no description' }} 4 | 5 | -------------------------------------------------------------------------------- /docs/demos/typeahead/controller.js: -------------------------------------------------------------------------------- 1 | app.controller('TypeaheadCtrl', function($scope) { 2 | $scope.user = { 3 | state: 'Arizona' 4 | }; 5 | 6 | $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']; 7 | }); -------------------------------------------------------------------------------- /docs/demos/typeahead/desc.md: -------------------------------------------------------------------------------- 1 | Typeahead control is implemented via [Angular-ui bootstrap typeahead](http://angular-ui.github.io/bootstrap/#/typeahead). 2 | Basically it is normal `editable-text` control with additional `e-typeahead` attributes. 3 | You should include additional `ui-bootstrap-tpls.min.js`for Bootstrap 3: 4 | 5 | 6 | 7 | For Bootstrap 4, include: 8 | 9 | 10 | 11 | Then add `ui.bootstrap` as module dependency: 12 | 13 | var app = angular.module("app", ["xeditable", "ui.bootstrap"]); 14 | 15 | And finally set `editable-text` attribute pointing to model and `e-uib-typeahead` attribute pointing to typeahead items. 16 | Other parameters can be defined via `e-typeahead-*` syntax, e.g. `e-typeahead-wait-ms="100"`. -------------------------------------------------------------------------------- /docs/demos/typeahead/test.js: -------------------------------------------------------------------------------- 1 | describe('typeahead', function() { 2 | 3 | beforeEach(function() { 4 | browser().navigateTo(mainUrl); 5 | }); 6 | 7 | it('should show editor and submit new value', function() { 8 | var s = '[ng-controller="TypeaheadCtrl"] '; 9 | var a = s+'a[editable-text] '; 10 | 11 | expect(element(a).css('display')).not().toBe('none'); 12 | expect(element(a).text()).toMatch('Arizona'); 13 | element(a).click(); 14 | 15 | expect(element(a).css('display')).toBe('none'); 16 | expect(element(s+'form[editable-form="$form"]').count()).toBe(1); 17 | expect(element(s+'form input[type="text"]:visible').count()).toBe(1); 18 | expect(element(s+'form input[type="text"]').val()).toBe('Arizona'); 19 | expect(element(s+'form .editable-buttons button[type="submit"]:visible').count()).toBe(1); 20 | expect(element(s+'form .editable-buttons button[type="button"]:visible').count()).toBe(1); 21 | 22 | //type 'a' 23 | using(s).input('$parent.$data').enter('a'); 24 | expect(element(s+'form ul.dropdown-menu:visible').count()).toBe(1); 25 | expect(element(s+'form ul.dropdown-menu > li').count()).toBe(8); 26 | 27 | //click 4 - California 28 | element(s+'form ul.dropdown-menu > li:eq(4)').click(); 29 | expect(element(s+'form ul.dropdown-menu:visible').count()).toBe(0); 30 | expect(element(s+'form input[type="text"]').val()).toBe('California'); 31 | 32 | //submit 33 | element(s+'form button[type="submit"]').click(); 34 | 35 | expect(element(a).css('display')).not().toBe('none'); 36 | expect(element(a).text()).toMatch('California'); 37 | expect(element(s+'form').count()).toBe(0); 38 | }); 39 | }); -------------------------------------------------------------------------------- /docs/demos/typeahead/view.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | {{ user.state || 'empty' }} 4 | 5 | -------------------------------------------------------------------------------- /docs/demos/uidate/controller.js: -------------------------------------------------------------------------------- 1 | app.controller('UidateCtrl', function($scope) { 2 | 3 | $scope.datePickerConfig = { 4 | changeYear: true, 5 | changeMonth: true 6 | }; 7 | 8 | $scope.user = { 9 | dob: new Date(1985, 3, 11) 10 | }; 11 | 12 | }); 13 | -------------------------------------------------------------------------------- /docs/demos/uidate/desc.md: -------------------------------------------------------------------------------- 1 | Date control is implemented via [jQuery UI Datepicker for AngularJS](https://github.com/angular-ui/ui-date). 2 | 3 | You should install ui-date with bower: 4 | 5 | bower install angular-ui-date --save 6 | 7 | Add the CSS: 8 | 9 | ```html 10 | 11 | ``` 12 | 13 | Load the script files (minimun jQuery version is v2.2.0): 14 | 15 | ```html 16 | 17 | 18 | 19 | 20 | ``` 21 | 22 | Add the date module as a dependency to your application module: 23 | 24 | ```js 25 | angular.module('MyApp', ['ui.date']) 26 | ``` 27 | 28 | And set `editable-uidate` attribute in editable element. 29 | 30 | You can pass an `e-ui-date` attribute pointing to an object with a picker configuration that you may want. 31 | 32 | ```js 33 | var datePickerOptions = { 34 | changeYear: true, 35 | changeMonth: true, 36 | showOn: "button", 37 | buttonImage: "build/assets/img/calendar.png", 38 | buttonImageOnly: true 39 | }; 40 | ``` 41 | 42 | -------------------------------------------------------------------------------- /docs/demos/uidate/test.js: -------------------------------------------------------------------------------- 1 | describe('uidate', function() { 2 | 3 | beforeEach(function() { 4 | browser().navigateTo(mainUrl); 5 | }); 6 | 7 | it('should show editor and submit new value', function() { 8 | var s = '[ng-controller="UidateCtrl"] '; 9 | 10 | expect(element(s+'span[editable-uidate]').css('display')).not().toBe('none'); 11 | expect(element(s+'span[editable-uidate]').text()).toMatch('11/04/1985'); 12 | element(s+'span[editable-uidate]').click(); 13 | 14 | expect(element(s+'span[editable-uidate]').css('display')).toBe('none'); 15 | expect(element(s+'form[editable-form="$form"]').count()).toBe(1); 16 | expect(element(s+'form input:visible').count()).toBe(1); 17 | expect(element(s+'form input').val()).toBe('04/11/1985'); 18 | expect(element(s+'form .editable-buttons button[type="submit"]:visible').count()).toBe(1); 19 | expect(element(s+'form .editable-buttons button[type="button"]:visible').count()).toBe(1); 20 | sleep(delay); 21 | sleep(delay); 22 | expect(element('div#ui-datepicker-div:visible').count()).toBe(1); 23 | expect(element('a.ui-state-active').text()).toMatch('11'); 24 | 25 | //set 29 april 26 | element('div#ui-datepicker-div > table > tbody > tr:eq(0) > td:eq(1) > a').click(); 27 | expect(element(s+'div#ui-datepicker-div:visible').count()).toBe(0); 28 | expect(element(s+'form input').val()).toBe('04/01/1985'); 29 | 30 | //submit 31 | element(s+'form button[type="submit"]').click(); 32 | 33 | expect(element(s+'span[editable-uidate]').css('display')).not().toBe('none'); 34 | expect(element(s+'span[editable-uidate]').text()).toMatch('01/04/1985'); 35 | expect(element(s+'form').count()).toBe(0); 36 | }); 37 | }); 38 | -------------------------------------------------------------------------------- /docs/demos/uidate/view.html: -------------------------------------------------------------------------------- 1 | 2 | 4 | {{ (user.dob | date:"dd/MM/yyyy") || 'empty' }} 5 | 6 | 7 | -------------------------------------------------------------------------------- /docs/demos/uipopover/controller.js: -------------------------------------------------------------------------------- 1 | app.controller('UiPopoverCtrl', function($scope) { 2 | $scope.user = { 3 | name: 'awesome user', 4 | location: 'location 1' 5 | }; 6 | }); -------------------------------------------------------------------------------- /docs/demos/uipopover/desc.md: -------------------------------------------------------------------------------- 1 | To make a single element editable via `ui-boostrap popover` do the following: 2 | - Wrap the editable in ``. 3 | - Put the following attributes on the editable element: 4 | - `uib-popover-template="'popover.html'"` - The template is generated when the `popover` attribute is set to `true' 5 | - `popover-is-open="popoverIsOpen"` - Controls when the popover is opened 6 | - `onshow="popoverIsOpen = !popoverIsOpen"` - Opens the popover when the editable form is shown 7 | - `onhide="popoverIsOpen = !popoverIsOpen"` - Closes the popover when the editable form is closed 8 | - `popover="true"` - Tells the editable directive that this element is to be displayed in the ui-bootstrap popover 9 | - `popover-class="increase-popover-width"` - Add this attribute to change the width of the popover 10 | -------------------------------------------------------------------------------- /docs/demos/uipopover/view.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | {{ user.name || 'empty' }} 14 | 15 | 16 | -------------------------------------------------------------------------------- /docs/demos/uiselect/controller.js: -------------------------------------------------------------------------------- 1 | app.controller('UiSelectCtrl', function($scope) { 2 | $scope.user = { 3 | state: 'Arizona' 4 | }; 5 | 6 | $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']; 7 | }); -------------------------------------------------------------------------------- /docs/demos/uiselect/desc.md: -------------------------------------------------------------------------------- 1 | UI-Select control is implemented via [AngularJS-native version of Select2 and Selectize](https://github.com/angular-ui/ui-select). 2 | You should include additional `select.min.js` and `select.min.css`: 3 | 4 | 5 | 6 | 7 | 8 | Then add `ui.select` as module dependency: 9 | 10 | var app = angular.module("app", ["xeditable", "ui.select"]); 11 | 12 | And finally set `editable-ui-select` attribute pointing to model and `editable-ui-select-match` to the match criteria 13 | and `editable-ui-select-choices` to the choices. -------------------------------------------------------------------------------- /docs/demos/uiselect/test.js: -------------------------------------------------------------------------------- 1 | describe('uiselect', function() { 2 | 3 | beforeEach(function() { 4 | browser().navigateTo(mainUrl); 5 | }); 6 | 7 | it('should show form by `edit` button click and close by `cancel`', function() { 8 | var s = '[ng-controller="UiSelectCtrl"] '; 9 | 10 | //edit button initially shown, form initially hidden 11 | expect(element(s+'form > div[editable-ui-select]:visible').count()).toBe(1); 12 | expect(element(s+'.buttons > button:visible').count()).toBe(1); 13 | expect(element(s+'.buttons > span:visible').count()).toBe(0); 14 | 15 | //show form 16 | element(s+'form > div > button').click(); 17 | //second click to test that controls not duplicated! 18 | element(s+'form > div > button').click(); 19 | //also click outside to check blur = ignore 20 | element('body').click(); 21 | 22 | //form shown in disabled state (loading) 23 | expect(element(s+'form > div[editable-ui-select]:visible').count()).toBe(0); 24 | expect(element(s+'.buttons > button:visible').count()).toBe(0); 25 | 26 | sleep(delay); 27 | 28 | //also click outside to check blur = ignore 29 | element('body').click(); 30 | 31 | //form enabled when data loaded 32 | expect(element(s+'form > div[editable-ui-select]:visible').count()).toBe(0); 33 | expect(element(s+'.buttons > button:visible').count()).toBe(0); 34 | expect(element(s+'.buttons > span button:enabled').count()).toBe(2); 35 | 36 | //click cancel 37 | element(s+'form > div > span button[type="button"]').click(); 38 | 39 | //form closed 40 | expect(element(s+'form > div[editable-ui-select]:visible').count()).toBe(1); 41 | expect(element(s+'.buttons > button:visible').count()).toBe(1); 42 | expect(element(s+'.buttons > span:visible').count()).toBe(0); 43 | }); 44 | 45 | it('should show form and save new values', function() { 46 | var s = '[ng-controller="UiSelectCtrl"] '; 47 | 48 | //show form 49 | element(s+'form > div > button').click(); 50 | 51 | sleep(delay); 52 | 53 | //select a value 54 | element(s+'.ui-select-toggle').click(); 55 | input('$select.search').enter('Illinois'); 56 | element(s+'#ui-select-choices-row-0-').click(); 57 | 58 | //click submit 59 | element(s+'span button[type="submit"]').click(); 60 | //second click to check that it works correctly 61 | element(s+'span button[type="submit"]').click(); 62 | 63 | //saving 64 | expect(element(s+'form > div:eq(0) .editable-error:visible').count()).toBe(0); 65 | 66 | sleep(delay); 67 | 68 | //form closed, new values shown 69 | expect(element(s+'form > div[editable-ui-select]:visible').count()).toBe(1); 70 | expect(element(s+'form > div[editable-ui-select]:visible').text()).toMatch('Illinois'); 71 | expect(element(s+'.buttons > button:visible').count()).toBe(1); 72 | expect(element(s+'.buttons > span:visible').count()).toBe(0); 73 | }); 74 | 75 | }); -------------------------------------------------------------------------------- /docs/demos/uiselect/view.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {{user.state}} 5 | 6 | {{$select.selected}} 7 | 8 | 9 | {{state}} 10 | 11 | 12 | 13 | 14 | 15 | 16 | Edit 17 | 18 | 19 | 20 | 21 | 22 | Save 23 | 24 | 25 | Cancel 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /docs/demos/validate-local/controller.js: -------------------------------------------------------------------------------- 1 | app.controller('ValidateLocalCtrl', function($scope) { 2 | $scope.user = { 3 | name: 'awesome user' 4 | }; 5 | 6 | $scope.checkName = function(data) { 7 | if (data !== 'awesome') { 8 | return "Username should be `awesome`"; 9 | } 10 | }; 11 | }); -------------------------------------------------------------------------------- /docs/demos/validate-local/desc.md: -------------------------------------------------------------------------------- 1 | Validation is performed via `onbeforesave` attribute pointing to validation method. 2 | Value is available as `$data` parameter. If method returns **string** - validation failed 3 | and string shown as error message. -------------------------------------------------------------------------------- /docs/demos/validate-local/test.js: -------------------------------------------------------------------------------- 1 | describe('validate-local', function() { 2 | 3 | beforeEach(function() { 4 | browser().navigateTo(mainUrl); 5 | }); 6 | 7 | it('should show error', function() { 8 | var s = '[ng-controller="ValidateLocalCtrl"] '; 9 | 10 | expect(element(s+'a').text()).toMatch('awesome user'); 11 | element(s+'a').click(); 12 | 13 | //not valid 14 | using(s).input('$parent.$data').enter('username'); 15 | element(s+'form button[type="submit"]').click(); 16 | 17 | //form remains open 18 | expect(element(s+'a').css('display')).toBe('none'); 19 | expect(element(s+'input[type="text"]:visible').count()).toBe(1); 20 | expect(element(s+'.editable-error:visible').count()).toBe(1); 21 | expect(element(s+'.editable-error').text()).toMatch('Username should be `awesome`'); 22 | 23 | //valid 24 | using(s).input('$parent.$data').enter('awesome'); 25 | element(s+'form button[type="submit"]').click(); 26 | 27 | expect(element(s+'a').css('display')).not().toBe('none'); 28 | expect(element(s+'a').text()).toMatch('awesome'); 29 | expect(element(s+'form').count()).toBe(0); 30 | expect(element(s+'.editable-error').count()).toBe(0); 31 | }); 32 | 33 | }); -------------------------------------------------------------------------------- /docs/demos/validate-local/view.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | {{ user.name || 'empty' }} 4 | 5 | -------------------------------------------------------------------------------- /docs/demos/validate-remote/controller.js: -------------------------------------------------------------------------------- 1 | app.controller('ValidateRemoteCtrl', function($scope, $http, $q) { 2 | $scope.user = { 3 | name: 'awesome user' 4 | }; 5 | 6 | $scope.checkName = function(data) { 7 | var d = $q.defer(); 8 | $http.post('/checkName', {value: data}).success(function(res) { 9 | res = res || {}; 10 | if(res.status === 'ok') { // {status: "ok"} 11 | d.resolve() 12 | } else { // {status: "error", msg: "Username should be `awesome`!"} 13 | d.resolve(res.msg) 14 | } 15 | }).error(function(e){ 16 | d.reject('Server error!'); 17 | }); 18 | return d.promise; 19 | }; 20 | }); -------------------------------------------------------------------------------- /docs/demos/validate-remote/desc.md: -------------------------------------------------------------------------------- 1 | You can perform remote validation e.g. checking username in db. 2 | For that define validation method returning `$q` **promise**. 3 | If promise resolves to **string** validation failed. -------------------------------------------------------------------------------- /docs/demos/validate-remote/test.js: -------------------------------------------------------------------------------- 1 | describe('validate-remote', function() { 2 | 3 | beforeEach(function() { 4 | browser().navigateTo(mainUrl); 5 | }); 6 | 7 | it('should show error', function() { 8 | var s = '[ng-controller="ValidateRemoteCtrl"] '; 9 | 10 | expect(element(s+'a').text()).toMatch('awesome user'); 11 | element(s+'a').click(); 12 | 13 | //not valid 14 | using(s).input('$parent.$data').enter('username'); 15 | element(s+'form button[type="submit"]').click(); 16 | 17 | //checking 18 | expect(element(s+'a').css('display')).toBe('none'); 19 | expect(element(s+'input[type="text"]:visible:disabled').count()).toBe(1); 20 | expect(element(s+'button:disabled:visible').count()).toBe(2); 21 | expect(element(s+'.editable-error:visible').count()).toBe(0); 22 | 23 | sleep(delay); 24 | 25 | //error shown 26 | expect(element(s+'a').css('display')).toBe('none'); 27 | expect(element(s+'input[type="text"]:enabled:visible').count()).toBe(1); 28 | expect(element(s+'button:visible:enabled').count()).toBe(2); 29 | expect(element(s+'.editable-error:visible').count()).toBe(1); 30 | expect(element(s+'.editable-error').text()).toMatch('Username should be `awesome`'); 31 | 32 | //valid 33 | using(s).input('$parent.$data').enter('awesome'); 34 | element(s+'form button[type="submit"]').click(); 35 | 36 | //checking 37 | expect(element(s+'a').css('display')).toBe('none'); 38 | expect(element(s+'input[type="text"]:visible:disabled').count()).toBe(1); 39 | expect(element(s+'button:visible:disabled').count()).toBe(2); 40 | expect(element(s+'.editable-error:visible').count()).toBe(0); 41 | 42 | sleep(delay); 43 | 44 | //no error shown, form closed 45 | expect(element(s+'a').css('display')).not().toBe('none'); 46 | expect(element(s+'a').text()).toMatch('awesome'); 47 | expect(element(s+'form').count()).toBe(0); 48 | expect(element(s+'.editable-error').count()).toBe(0); 49 | }); 50 | 51 | }); -------------------------------------------------------------------------------- /docs/demos/validate-remote/view.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | {{ user.name || 'empty' }} 4 | 5 | -------------------------------------------------------------------------------- /docs/img/angular32.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vitalets/angular-xeditable/40c4dfbfa33e396a263ed8c120bd17853256e1b6/docs/img/angular32.jpeg -------------------------------------------------------------------------------- /docs/jade/getstarted.jade: -------------------------------------------------------------------------------- 1 | ol 2 | li 3 | :markdown 4 | Include [Angular.js](http://angularjs.org) in your project 5 | 6 | pre.prettyprint 7 | = '' 8 | 9 | :markdown 10 | Optionally include [Bootstrap](http://getbootstrap.com) CSS for theming 11 | 12 | :markdown 13 | Bootstrap 3 14 | 15 | pre.prettyprint 16 | = '' 17 | 18 | :markdown 19 | Boostrap 4 20 | 21 | pre.prettyprint 22 | = '' 23 | li 24 | span Install angular-xeditable via 25 | a(href="http://bower.io", target="_blank") bower 26 | span or 27 | a(href="zip/angular-xeditable-"+version+".zip") download latest zip 28 | pre.prettyprint bower install angular-xeditable 29 | 30 | 31 | li Include angular-xeditable into your project 32 | pre.prettyprint 33 | = '\n' 34 | = '' 35 | 36 | li Define angular app and add "xeditable" to dependencies 37 | pre.prettyprint 38 | = '' 39 | pre.prettyprint var app = angular.module("app", ["xeditable"]); 40 | 41 | li Set theme in app.run: 42 | pre.prettyprint. 43 | app.run(['editableOptions', function(editableOptions) { 44 | editableOptions.theme = 'bs3'; // bootstrap3 theme. Can be also 'bs4', 'bs2', 'default' 45 | }]); 46 | 47 | li Markup element that should be editable 48 | pre.prettyprint(ng-non-bindable) 49 | = '\n' 50 | = ' {{ user.name || "empty" }}\n' 51 | = '' 52 | 53 | li Define controller 54 | pre.prettyprint. 55 | app.controller('Ctrl', function($scope) { 56 | $scope.user = { 57 | name: 'awesome user' 58 | }; 59 | }); 60 | li Enjoy! 61 | div(style="padding-top: 5px") 62 | a.btn.btn-success(href="starter", target="_blank") View starter template 63 | a.btn.btn-primary(href="zip/angular-xeditable-starter.zip", style="margin-left: 10px") Download starter zip 64 | -------------------------------------------------------------------------------- /docs/jade/main.jade: -------------------------------------------------------------------------------- 1 | doctype 2 | html(ng-app='app') 3 | head 4 | meta(charset='utf-8') 5 | meta(name="viewport", content="width=device-width, initial-scale=1.0") 6 | 7 | if env == 'prod' 8 | title Angular-xeditable :: Edit in place for AngularJS 9 | include scripts-prod.jade 10 | else 11 | title Dev 12 | include scripts-dev.jade 13 | 14 | body(data-spy="scroll", data-target=".sidebar") 15 | include navbar.jade 16 | 17 | .container 18 | .row 19 | .col-sm-3.col-xs-12 20 | .well.sidebar 21 | ul.nav 22 | each g in structure 23 | if (!g.env || g.env === env) 24 | li 25 | a(href="#"+(g.anchor || g.id))= g.text 26 | ul.nav 27 | each item in g.items 28 | li: a(href="#"+(item.anchor || item.id))= item.menutext || item.text || item.id 29 | 30 | .col-sm-9.col-xs-12 31 | 32 | mixin demo(item) 33 | - var brief = item.id 34 | section(id=brief) 35 | h1= item.text || brief 36 | - var dir = "docs/demos/"+brief 37 | - var desc = fs.readFileSync(dir+"/desc.md").toString() 38 | - var view = fs.readFileSync(dir+"/view.html").toString().trim() 39 | - var ctrl = fs.readFileSync(dir+"/controller.js").toString().trim() 40 | 41 | // watch change of user to update rootScope for debugging 42 | - var ctrlDebug = item.nodebug ? ctrl : ctrl.replace(/\}\);$/i, '$scope.$watch("user", function(v) { $scope.$parent.debug["'+brief+'"]=v; }); });') 43 | 44 | h3 demo 45 | 46 | if item.fiddle 47 | a.btn.btn-info.fiddle.pull-right(href=item.fiddle, target="_blank") 48 | = item.fiddleText || 'jsFiddle' 49 | 50 | .well.line-example!= view 51 | 52 | if !item.nodebug 53 | pre!= '{{ debug["'+brief+'"] | json }}' 54 | p!= md(desc) 55 | //script(src=dir+'/controller.js') 56 | script!= ctrlDebug 57 | h3 html 58 | pre.prettyprint.ng-non-bindable= view 59 | h3 controller.js 60 | pre.prettyprint= ctrl 61 | 62 | // render 63 | - for(var i=0; i 2 | 25 | 26 | -------------------------------------------------------------------------------- /docs/jade/navbar.jade: -------------------------------------------------------------------------------- 1 | nav.navbar.navbar-default.navbar-fixed-top(role="navigation") 2 | .container 3 | .navbar-header 4 | button.navbar-toggle(type="button", data-toggle="collapse", data-target=".navbar-ex1-collapse") 5 | span.sr-only Toggle navigation 6 | span.icon-bar 7 | span.icon-bar 8 | span.icon-bar 9 | a.navbar-brand(href="#") Angular-xeditable #{env == 'prod' ? '' : '(dev)'} 10 | 11 | .collapse.navbar-collapse.navbar-ex1-collapse 12 | ul.nav.navbar-nav 13 | li.active 14 | a(href="#") Home 15 | li 16 | a(href="https://github.com/vitalets/angular-xeditable") GitHub 17 | if env !== 'prod' 18 | li 19 | a(href="/test/e2e/dev-test.html") dev-test 20 | li 21 | a(href="/test/e2e/docs-test.html") prod-test 22 | 23 | form.navbar-form.navbar-right 24 | a.btn.btn-primary(href="zip/angular-xeditable-"+version+'.zip', style="font-weight:bold") 25 | span.glyphicon.glyphicon-save(style="margin-right:10px") 26 | | Download #{version} ~ #{size}kb 27 | 28 | if env == 'prod' 29 | div(style="padding-top: 13px") 30 | include social.html 31 | -------------------------------------------------------------------------------- /docs/jade/overview.jade: -------------------------------------------------------------------------------- 1 | :markdown 2 | **Angular-xeditable** is a bundle of [AngularJS](http://angularjs.org) directives that allows you to create 3 | *editable elements*. 4 | Such technique is also known as *click-to-edit* or *edit-in-place*. 5 | It is based on ideas of [x-editable](http://vitalets.github.io/x-editable) but was written from scratch 6 | to use power of angular and support complex forms / editable grids. 7 | 8 | h4 Dependencies 9 | :markdown 10 | Basically it does not depend on any libraries except [AngularJS](http://angularjs.org) itself. 11 | For themes you may need to include [Twitter Bootstrap](http://getbootstrap.com) CSS. 12 | For some extra controls (e.g. datepicker) you may need to include [angular-ui bootstrap](http://angular-ui.github.io/bootstrap/) for Bootstrap 2/3. 13 | Include [ui-bootstrap4](https://morgul.github.io/ui-bootstrap4/) for Bootstrap 4. 14 | For ui-select you may need to include [angular-ui ui-select](https://github.com/angular-ui/ui-select/). 15 | For ngTagsInput you may need to include [mbenford ngTagsInput](https://github.com/mbenford/ngTagsInput). 16 | If installing via NPM, jQuery and moment js will also be installed. 17 | 18 | h4 Controls & Features 19 | table(style="width: 50%; min-width: 200px") 20 | tr 21 | td(style="width: 50%; vertical-align: top") 22 | :markdown 23 | * [text](#text-simple) 24 | * [textarea](#textarea) 25 | * [select](#select-local) 26 | * [checkbox](#checklist) 27 | * [radio](#radiolist) 28 | * [date](#bsdate) 29 | * [time](#bstime) 30 | * [datetime](#combodate) 31 | * [html5 inputs](#html5-inputs) 32 | * [typeahead](#typeahead) 33 | * [ui-select](#uiselect) 34 | * [ngTagsInput](#ngtags) 35 | 36 | td(style="width: 50%; vertical-align: top") 37 | :markdown 38 | * [complex form](#editable-form) 39 | * [editable row](#editable-row) 40 | * [editable column](#editable-column) 41 | * [editable table](#editable-table) 42 | * [themes](#themes) 43 | -------------------------------------------------------------------------------- /docs/jade/scripts-dev.jade: -------------------------------------------------------------------------------- 1 | //theme: Bootstrap 3 2 | link(ng-repeat="c in css", ng-href="{{c}}", rel="stylesheet", media="screen") 3 | link(href="bower_components/bootstrap/dist/css/bootstrap.css", rel="stylesheet", media="screen") 4 | link(href="https://stackpath.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css", rel="stylesheet", media="screen") 5 | 6 | //ui-select css 7 | link(href="bower_components/angular-ui-select/dist/select.css", rel="stylesheet", media="screen") 8 | 9 | //ng-tags css 10 | link(href="bower_components/ng-tags-input/ng-tags-input.css", rel="stylesheet", media="screen") 11 | link(href="bower_components/ng-tags-input/ng-tags-input.bootstrap.css", rel="stylesheet", media="screen") 12 | 13 | //jquery-ui css 14 | link(href="bower_components/jquery-ui/themes/smoothness/jquery-ui.css", rel="stylesheet", media="screen") 15 | 16 | //xeditable css 17 | link(href="src/css/xeditable.css", rel="stylesheet", media="screen") 18 | 19 | //docs css 20 | link(href="docs/css/docs.css", rel="stylesheet", media="screen") 21 | 22 | //jquery (needed for bootstrap)? for dev include before angular! 23 | script(src="bower_components/jquery/dist/jquery.js") 24 | 25 | //jquery ui 26 | script(src="bower_components/jquery-ui/jquery-ui.js") 27 | 28 | //angular 29 | script(src='bower_components/angular/angular.js') 30 | script(src='bower_components/angular-mocks/angular-mocks.js') 31 | script(src='bower_components/angular-sanitize/angular-sanitize.js') 32 | 33 | //bootstrap 34 | script(src="bower_components/bootstrap/dist/js/bootstrap.js") 35 | 36 | //angular-ui-bootstrap 37 | script(src="bower_components/angular-bootstrap/ui-bootstrap-tpls.js") 38 | 39 | //angular-ui-date 40 | script(src="bower_components/angular-ui-date/dist/date.js") 41 | 42 | //ui-select 43 | script(src="bower_components/angular-ui-select/dist/select.js") 44 | 45 | //ng-tags 46 | script(src="bower_components/ng-tags-input/ng-tags-input.js") 47 | 48 | //moment 49 | script(src="bower_components/moment/moment.js") 50 | 51 | //google-code-prettify 52 | script(src='https://cdn.rawgit.com/google/code-prettify/master/loader/run_prettify.js') 53 | 54 | //checklist-model 55 | script(src='bower_components/checklist-model/checklist-model.js') 56 | 57 | //app 58 | script(src='docs/js/app.js') 59 | 60 | //xeditable 61 | script(src='src/js/module.js') 62 | script(src='src/js/helpers.js') 63 | script(src='src/js/themes.js') 64 | script(src='src/js/icons.js') 65 | 66 | script(src='src/js/editable-form/controller.js') 67 | script(src='src/js/editable-form/directive.js') 68 | 69 | script(src='src/js/editable-element/controller.js') 70 | script(src='src/js/editable-element/directive.js') 71 | 72 | script(src='src/js/directives/input.js') 73 | script(src='src/js/directives/select.js') 74 | script(src='src/js/directives/textarea.js') 75 | script(src='src/js/directives/checkbox.js') 76 | script(src='src/js/directives/bsdate.js') 77 | script(src='src/js/directives/bstime.js') 78 | script(src='src/js/directives/radiolist.js') 79 | script(src='src/js/directives/checklist.js') 80 | script(src='src/js/directives/uiselect.js') 81 | script(src='src/js/directives/ngtags.js') 82 | script(src='src/js/directives/combodate.js') 83 | script(src='src/js/directives/uidate.js') 84 | 85 | 86 | -------------------------------------------------------------------------------- /docs/jade/scripts-prod.jade: -------------------------------------------------------------------------------- 1 | //theme: Bootstrap 3 2 | link(href="bower_components/bootstrap/dist/css/bootstrap.css", rel="stylesheet", media="screen") 3 | 4 | //ui-select css 5 | link(href="bower_components/angular-ui-select/dist/select.css", rel="stylesheet", media="screen") 6 | 7 | //ng-tags css 8 | link(href="bower_components/ng-tags-input/ng-tags-input.css", rel="stylesheet", media="screen") 9 | link(href="bower_components/ng-tags-input/ng-tags-input.bootstrap.css", rel="stylesheet", media="screen") 10 | 11 | //jquery-ui css 12 | link(href="bower_components/jquery-ui/themes/smoothness/jquery-ui.css", rel="stylesheet", media="screen") 13 | 14 | //xeditable css 15 | link(href="dist/css/xeditable.css", rel="stylesheet", media="screen") 16 | 17 | //docs css 18 | link(href="docs/css/docs.css", rel="stylesheet", media="screen") 19 | 20 | //jquery (needed for bootstrap) 21 | script(src="bower_components/jquery/dist/jquery.js") 22 | 23 | //jquery ui 24 | script(src="bower_components/jquery-ui/jquery-ui.js") 25 | 26 | //angular 27 | script(src='bower_components/angular/angular.js') 28 | script(src='bower_components/angular-mocks/angular-mocks.js') 29 | script(src='bower_components/angular-sanitize/angular-sanitize.js') 30 | 31 | 32 | //bootstrap 33 | script(src="bower_components/bootstrap/dist/js/bootstrap.js") 34 | 35 | //angular-ui-date 36 | script(src="bower_components/angular-ui-date/dist/date.js") 37 | 38 | //angular-ui-bootstrap 39 | script(src="bower_components/angular-bootstrap/ui-bootstrap-tpls.js") 40 | 41 | //ui-select 42 | script(src="bower_components/angular-ui-select/dist/select.js") 43 | 44 | //ng-tags 45 | script(src="bower_components/ng-tags-input/ng-tags-input.js") 46 | 47 | //moment 48 | script(src="bower_components/moment/moment.js") 49 | 50 | //google-code-prettify 51 | script(src='https://cdn.rawgit.com/google/code-prettify/master/loader/run_prettify.js') 52 | 53 | //checklist-model 54 | script(src='bower_components/checklist-model/checklist-model.js') 55 | 56 | //app 57 | script(src='docs/js/app.js') 58 | 59 | //xeditable 60 | script(src='dist/js/xeditable.min.js') -------------------------------------------------------------------------------- /docs/jade/social.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 13 | 14 | 15 | 16 | 17 | 27 | 28 | 29 | 30 | 31 | Tweet 32 | 36 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | require('./dist/js/xeditable.js'); 2 | module.exports = 'xeditable'; 3 | -------------------------------------------------------------------------------- /jsdoc.conf.json: -------------------------------------------------------------------------------- 1 | { 2 | "opts": { 3 | "template": "node_modules/jsdoc/templates/haruki", 4 | "destination": "console", 5 | "recurse": true 6 | }, 7 | "tags": { 8 | "allowUnknownTags": true 9 | }, 10 | "source": { 11 | "include": ["src"], 12 | "includePattern": ".+\\.js(doc)?$" 13 | }, 14 | "plugins": [], 15 | "templates": { 16 | "cleverLinks": true, 17 | "monospaceLinks": false 18 | } 19 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-xeditable", 3 | "description": "Edit-in-place for angular.js", 4 | "version": "0.10.2", 5 | "homepage": "https://vitalets.github.io/angular-xeditable", 6 | "author": { 7 | "name": "Vitaliy Potapov", 8 | "email": "noginsk@rambler.ru" 9 | }, 10 | "main": "index.js", 11 | "style": "dist/css/xeditable.css", 12 | "repository": { 13 | "type": "git", 14 | "url": "git://github.com/vitalets/angular-xeditable.git" 15 | }, 16 | "engines": { 17 | "node": ">= 0.8.0" 18 | }, 19 | "files": [ 20 | "dist/**/*" 21 | ], 22 | "dependencies": { 23 | "angular": "~1.x" 24 | }, 25 | "devDependencies": { 26 | "bower": "^1.8.4", 27 | "catharsis": "^0.8.11", 28 | "grunt": "~0.4.1", 29 | "grunt-cli": "~0.1.9", 30 | "grunt-contrib-clean": "~0.4.1", 31 | "grunt-contrib-compress": "~0.5.0", 32 | "grunt-contrib-concat": "~0.3.0", 33 | "grunt-contrib-connect": "~0.4.0", 34 | "grunt-contrib-copy": "~0.4.1", 35 | "grunt-contrib-cssmin": "~0.6.1", 36 | "grunt-contrib-jade": "~0.8.0", 37 | "grunt-contrib-jshint": "~0.5.3", 38 | "grunt-contrib-uglify": "~0.2.0", 39 | "grunt-contrib-watch": "~0.4.3", 40 | "grunt-jsdoc": "^0.4.3", 41 | "grunt-replace": "~0.4.4", 42 | "grunt-shell": "~0.5.0", 43 | "marked": "^0.7.0", 44 | "taffydb": "^2.7.3" 45 | }, 46 | "license": "MIT", 47 | "bugs": { 48 | "url": "https://github.com/vitalets/angular-xeditable/issues" 49 | }, 50 | "scripts": { 51 | "build": "grunt build", 52 | "start": "grunt server", 53 | "prepublish": "bower update" 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Angular-xeditable 2 | 3 | [](https://www.npmjs.com/package/angular-xeditable) [](http://bower.io/search/?q=angular-xeditable) 4 | 5 | Edit in place for AngularJS 6 | 7 | ## Overview 8 | **Angular-xeditable** is a bundle of [AngularJS](http://angularjs.org) directives that allows you to create 9 | *editable* elements in your projects. 10 | Such technique is also known as *click-to-edit* or *edit-in-place*. 11 | It is based on ideas of [x-editable](https://vitalets.github.io/x-editable) but was written from scratch 12 | to use power of angular and support complex forms / editable grids. 13 | 14 | ## Demo and docs 15 | **https://vitalets.github.io/angular-xeditable** 16 | 17 | ## Installation 18 | #### Bower 19 | ```` 20 | bower install angular-xeditable 21 | ```` 22 | #### NPM 23 | ```` 24 | npm install angular-xeditable 25 | ```` 26 | #### Manual 27 | Download latest version from [project homepage](https://vitalets.github.io/angular-xeditable). 28 | 29 | #### Insert dependency 30 | ```` 31 | var app = angular.module("app", ["xeditable"]); 32 | ```` 33 | #### Usage with a Asset/Module Bundler 34 | ```js 35 | import angularXeditable from 'angular-xeditable'; 36 | 37 | angular.module('app', [angularXeditable]); 38 | ``` 39 | 40 | ## Dependencies 41 | Basically it does not depend on any libraries except [AngularJS](http://angularjs.org) itself. 42 | For themes you may need to include [Twitter Bootstrap](http://getbootstrap.com) CSS. 43 | For some extra controls (e.g. datepicker) you may need to include [angular-ui bootstrap](http://angular-ui.github.io/bootstrap/) for Bootstrap 2/3. 44 | Include [ui-bootstrap4](https://morgul.github.io/ui-bootstrap4/) for Bootstrap 4. 45 | To use ui-select you will need to include [angular-ui ui-select](https://github.com/angular-ui/ui-select/). 46 | To use ngTagsInput you will need to include [mbenford ngTagsInput](https://github.com/mbenford/ngTagsInput). 47 | To use ui-date you will need to include [angular-ui ui-date](https://github.com/angular-ui/ui-date). 48 | 49 | ## Reporting issues and Contributing 50 | Please read our [Contributor guidelines](CONTRIBUTING.md) before reporting an issue or creating a pull request. 51 | 52 | ## License 53 | [MIT](LICENSE) 54 | -------------------------------------------------------------------------------- /src/js/directives/bstime.js: -------------------------------------------------------------------------------- 1 | /* 2 | Angular-ui bootstrap editable timepicker 3 | http://angular-ui.github.io/bootstrap/#/timepicker 4 | */ 5 | angular.module('xeditable').directive('editableBstime', ['editableDirectiveFactory', 6 | function(editableDirectiveFactory) { 7 | return editableDirectiveFactory({ 8 | directiveName: 'editableBstime', 9 | inputTpl: '', 10 | render: function() { 11 | this.parent.render.call(this); 12 | 13 | // timepicker can't update model when ng-model set directly to it 14 | // see: https://github.com/angular-ui/bootstrap/issues/1141 15 | // so we wrap it into DIV 16 | var div = angular.element(''); 17 | 18 | // move ng-model to wrapping div 19 | div.attr('ng-model', this.inputEl.attr('ng-model')); 20 | this.inputEl.removeAttr('ng-model'); 21 | 22 | // move ng-change to wrapping div 23 | if(this.attrs.eNgChange) { 24 | div.attr('ng-change', this.inputEl.attr('ng-change')); 25 | this.inputEl.removeAttr('ng-change'); 26 | } 27 | 28 | // wrap 29 | this.inputEl.wrap(div); 30 | } 31 | }); 32 | }]); -------------------------------------------------------------------------------- /src/js/directives/checkbox.js: -------------------------------------------------------------------------------- 1 | //checkbox 2 | angular.module('xeditable').directive('editableCheckbox', ['editableDirectiveFactory', 3 | function(editableDirectiveFactory) { 4 | return editableDirectiveFactory({ 5 | directiveName: 'editableCheckbox', 6 | inputTpl: '', 7 | render: function() { 8 | this.parent.render.call(this); 9 | this.inputEl.wrap(''); 10 | 11 | if (this.attrs.eTitle) { 12 | this.inputEl.parent().append('' + this.attrs.eTitle + ''); 13 | } 14 | }, 15 | autosubmit: function() { 16 | var self = this; 17 | self.inputEl.bind('change', function() { 18 | setTimeout(function() { 19 | self.scope.$apply(function() { 20 | self.scope.$form.$submit(); 21 | }); 22 | }, 500); 23 | }); 24 | } 25 | }); 26 | }]); 27 | -------------------------------------------------------------------------------- /src/js/directives/checklist.js: -------------------------------------------------------------------------------- 1 | // checklist 2 | angular.module('xeditable').directive('editableChecklist', [ 3 | 'editableDirectiveFactory', 4 | 'editableNgOptionsParser', 5 | function(editableDirectiveFactory, editableNgOptionsParser) { 6 | return editableDirectiveFactory({ 7 | directiveName: 'editableChecklist', 8 | inputTpl: '', 9 | useCopy: true, 10 | render: function() { 11 | this.parent.render.call(this); 12 | var parsed = editableNgOptionsParser(this.attrs.eNgOptions), 13 | ngChangeHtml = '', 14 | ngChecklistComparatorHtml = '', 15 | ngDisabled = '', 16 | ngNameHtml = ''; 17 | 18 | if (this.attrs.eNgChange) { 19 | ngChangeHtml = ' ng-change="' + this.attrs.eNgChange + '"'; 20 | } 21 | 22 | if (this.attrs.eChecklistComparator) { 23 | ngChecklistComparatorHtml = ' checklist-comparator="' + this.attrs.eChecklistComparator + '"'; 24 | } 25 | 26 | if (this.attrs.eNgDisabled) { 27 | ngDisabled = ' ng-disabled="' + this.attrs.eNgDisabled+'"'; 28 | } 29 | 30 | if (this.attrs.eName) { 31 | ngNameHtml = ' name="' + this.attrs.eName + '"'; 32 | } 33 | 34 | var html = ''+ 35 | ''+ 37 | ''; 38 | 39 | this.inputEl.removeAttr('ng-model'); 40 | this.inputEl.removeAttr('ng-options'); 41 | this.inputEl.removeAttr('ng-change'); 42 | this.inputEl.removeAttr('checklist-comparator'); 43 | this.inputEl.removeAttr('name'); 44 | this.inputEl.html(html); 45 | } 46 | }); 47 | }]); -------------------------------------------------------------------------------- /src/js/directives/combodate.js: -------------------------------------------------------------------------------- 1 | 2 | angular.module('xeditable').directive('editableCombodate', ['editableDirectiveFactory', 'editableCombodate', 3 | function(editableDirectiveFactory, editableCombodate) { 4 | return editableDirectiveFactory({ 5 | directiveName: 'editableCombodate', 6 | inputTpl: '', 7 | render: function() { 8 | this.parent.render.call(this); 9 | 10 | var options = { 11 | value: new Date(this.scope.$data) 12 | }; 13 | var self = this; 14 | angular.forEach(["format", "template", "minYear", "maxYear", "yearDescending", "minuteStep", "secondStep", "firstItem", "errorClass", "customClass", "roundTime", "smartDays"], function(name) { 15 | 16 | var attrName = "e" + name.charAt(0).toUpperCase() + name.slice(1); 17 | 18 | if (attrName in self.attrs) { 19 | if (name == "minYear" || name == "maxYear" || name == "minuteStep" || name == "secondStep") { 20 | options[name] = parseInt(self.attrs[attrName], 10); 21 | } else { 22 | options[name] = self.attrs[attrName]; 23 | } 24 | } 25 | }); 26 | 27 | var combodate = editableCombodate.getInstance(this.inputEl, options); 28 | combodate.$widget.find('select').bind('change', function(e) { 29 | self.scope.$data = combodate.getValue() ? 30 | moment(combodate.getValue(), combodate.options.format).toDate().toISOString() : null; 31 | }).change(); 32 | } 33 | }); 34 | } 35 | ]); -------------------------------------------------------------------------------- /src/js/directives/input.js: -------------------------------------------------------------------------------- 1 | /* 2 | Input types: text|password|email|tel|number|url|search|color|date|datetime|datetime-local|time|month|week|file 3 | */ 4 | 5 | (function() { 6 | 7 | var camelCase = function(dashDelimitedString) { 8 | return dashDelimitedString.toLowerCase().replace(/-(.)/g, function(match, word) { 9 | return word.toUpperCase(); 10 | }); 11 | }; 12 | 13 | var types = 'text|password|email|tel|number|url|search|color|date|datetime|datetime-local|time|month|week|file'.split('|'); 14 | 15 | //todo: datalist 16 | 17 | // generate directives 18 | angular.forEach(types, function(type) { 19 | var directiveName = camelCase('editable' + '-' + type); 20 | angular.module('xeditable').directive(directiveName, ['editableDirectiveFactory', 21 | function(editableDirectiveFactory) { 22 | return editableDirectiveFactory({ 23 | directiveName: directiveName, 24 | inputTpl: '', 25 | render: function() { 26 | this.parent.render.call(this); 27 | 28 | var attrs = this.attrs; 29 | var scope = this.scope; 30 | 31 | //Add bootstrap simple input groups 32 | if (attrs.eInputgroupleft || attrs.eInputgroupright) { 33 | this.inputEl.wrap(''); 34 | 35 | if (attrs.eInputgroupleft) { 36 | var inputGroupLeft = angular.element(''); 37 | this.inputEl.parent().prepend(inputGroupLeft); 38 | } 39 | 40 | if (attrs.eInputgroupright) { 41 | var inputGroupRight = angular.element(''); 42 | this.inputEl.parent().append(inputGroupRight); 43 | } 44 | } 45 | 46 | // Add label to the input 47 | if (attrs.eLabel) { 48 | var label = angular.element('' + attrs.eLabel + ''); 49 | if (attrs.eInputgroupleft || attrs.eInputgroupright) { 50 | this.inputEl.parent().parent().prepend(label); 51 | } else { 52 | this.inputEl.parent().prepend(label); 53 | } 54 | } 55 | 56 | // Add classes to the form 57 | if (attrs.eFormclass) { 58 | this.editorEl.addClass(attrs.eFormclass); 59 | this.inputEl.removeAttr('formclass'); 60 | } 61 | }, 62 | autosubmit: function() { 63 | var self = this; 64 | self.inputEl.bind('keydown', function(e) { 65 | //submit on tab 66 | if (e.keyCode === 9 && self.editorEl.attr('blur') === 'submit') { 67 | self.scope.$apply(function() { 68 | self.scope.$form.$submit(); 69 | }); 70 | } 71 | }); 72 | } 73 | }); 74 | }]); 75 | }); 76 | 77 | //`range` is bit specific 78 | angular.module('xeditable').directive('editableRange', ['editableDirectiveFactory', '$interpolate', 79 | function(editableDirectiveFactory, $interpolate) { 80 | return editableDirectiveFactory({ 81 | directiveName: 'editableRange', 82 | inputTpl: '', 83 | render: function() { 84 | this.parent.render.call(this); 85 | this.inputEl.after('' + $interpolate.startSymbol() + '$data' + $interpolate.endSymbol() + ''); 86 | } 87 | }); 88 | }]); 89 | 90 | }()); 91 | 92 | -------------------------------------------------------------------------------- /src/js/directives/ngtags.js: -------------------------------------------------------------------------------- 1 | /* 2 | Tags input directive for AngularJS 3 | https://github.com/mbenford/ngTagsInput 4 | */ 5 | angular.module('xeditable').directive('editableTagsInput', ['editableDirectiveFactory', 'editableUtils', 6 | function(editableDirectiveFactory, editableUtils) { 7 | var dir = editableDirectiveFactory({ 8 | directiveName: 'editableTagsInput', 9 | inputTpl: '', 10 | useCopy: true, 11 | render: function () { 12 | this.parent.render.call(this); 13 | this.inputEl.append(editableUtils.rename('auto-complete', this.attrs.$autoCompleteElement)); 14 | this.inputEl.removeAttr('ng-model'); 15 | this.inputEl.attr('ng-model', '$parent.$data'); 16 | } 17 | }); 18 | 19 | var linkOrg = dir.link; 20 | 21 | dir.link = function (scope, el, attrs, ctrl) { 22 | var autoCompleteEl = el.find('editable-tags-input-auto-complete'); 23 | 24 | attrs.$autoCompleteElement = autoCompleteEl.clone(); 25 | 26 | autoCompleteEl.remove(); 27 | 28 | return linkOrg(scope, el, attrs, ctrl); 29 | }; 30 | 31 | return dir; 32 | }]); -------------------------------------------------------------------------------- /src/js/directives/radiolist.js: -------------------------------------------------------------------------------- 1 | // radiolist 2 | angular.module('xeditable').directive('editableRadiolist', [ 3 | 'editableDirectiveFactory', 4 | 'editableNgOptionsParser', 5 | '$interpolate', 6 | function(editableDirectiveFactory, editableNgOptionsParser, $interpolate) { 7 | return editableDirectiveFactory({ 8 | directiveName: 'editableRadiolist', 9 | inputTpl: '', 10 | render: function() { 11 | this.parent.render.call(this); 12 | var parsed = editableNgOptionsParser(this.attrs.eNgOptions), 13 | ngChangeHtml = '', 14 | ngNameHtml = '', 15 | ngValueFn = isNaN(parsed.locals.valueFn) ? "'" + parsed.locals.valueFn + "'" : parsed.locals.valueFn, 16 | ngDisabled = this.attrs.eNgDisabled ? this.attrs.eNgDisabled : false; 17 | 18 | if (this.attrs.eNgChange) { 19 | ngChangeHtml = ' ng-change="' + this.attrs.eNgChange + '"'; 20 | } 21 | 22 | if (this.attrs.eName) { 23 | ngNameHtml = ' name="' + this.attrs.eName + '"'; 24 | } 25 | 26 | var html = ''+ 27 | ''+ 32 | ''; 33 | 34 | this.inputEl.removeAttr('ng-model'); 35 | this.inputEl.removeAttr('ng-options'); 36 | this.inputEl.removeAttr('ng-change'); 37 | this.inputEl.html(html); 38 | }, 39 | autosubmit: function() { 40 | var self = this; 41 | self.inputEl.bind('change', function() { 42 | setTimeout(function() { 43 | self.scope.$apply(function() { 44 | self.scope.$form.$submit(); 45 | }); 46 | }, 500); 47 | }); 48 | } 49 | }); 50 | }]); 51 | -------------------------------------------------------------------------------- /src/js/directives/select.js: -------------------------------------------------------------------------------- 1 | //select 2 | angular.module('xeditable').directive('editableSelect', ['editableDirectiveFactory', 3 | function(editableDirectiveFactory) { 4 | return editableDirectiveFactory({ 5 | directiveName: 'editableSelect', 6 | inputTpl: '', 7 | render: function() { 8 | this.parent.render.call(this); 9 | 10 | if (this.attrs.ePlaceholder) { 11 | var placeholder = angular.element('' + this.attrs.ePlaceholder + ''); 12 | this.inputEl.append(placeholder); 13 | } 14 | }, 15 | autosubmit: function() { 16 | var self = this; 17 | 18 | if (!self.attrs.hasOwnProperty("eMultiple")) { 19 | self.inputEl.bind('change', function () { 20 | self.scope.$apply(function () { 21 | self.scope.$form.$submit(); 22 | }); 23 | }); 24 | } 25 | } 26 | }); 27 | }]); -------------------------------------------------------------------------------- /src/js/directives/textarea.js: -------------------------------------------------------------------------------- 1 | //textarea 2 | angular.module('xeditable').directive('editableTextarea', ['editableDirectiveFactory', 3 | function(editableDirectiveFactory) { 4 | return editableDirectiveFactory({ 5 | directiveName: 'editableTextarea', 6 | inputTpl: '', 7 | render: function() { 8 | this.parent.render.call(this); 9 | 10 | // Add classes to the form 11 | if (this.attrs.eFormclass) { 12 | this.editorEl.addClass(this.attrs.eFormclass); 13 | this.inputEl.removeAttr('formclass'); 14 | } 15 | }, 16 | addListeners: function() { 17 | var self = this; 18 | self.parent.addListeners.call(self); 19 | // submit textarea by ctrl+enter even with buttons 20 | if (self.single && self.buttons !== 'no') { 21 | self.autosubmit(); 22 | } 23 | }, 24 | autosubmit: function() { 25 | var self = this; 26 | self.inputEl.bind('keydown', function(e) { 27 | if (self.attrs.submitOnEnter) { 28 | if (e.keyCode === 13 && !e.shiftKey) { 29 | self.scope.$apply(function() { 30 | self.scope.$form.$submit(); 31 | }); 32 | } 33 | } else if ((e.ctrlKey || e.metaKey) && (e.keyCode === 13) || 34 | (e.keyCode === 9 && self.editorEl.attr('blur') === 'submit')) { 35 | self.scope.$apply(function() { 36 | self.scope.$form.$submit(); 37 | }); 38 | } 39 | }); 40 | } 41 | }); 42 | }]); 43 | -------------------------------------------------------------------------------- /src/js/directives/uidate.js: -------------------------------------------------------------------------------- 1 | /* 2 | jQuery UI Datepicker for AngularJS 3 | https://github.com/angular-ui/ui-date 4 | */ 5 | angular.module('xeditable').directive('editableUidate', ['editableDirectiveFactory', 6 | function(editableDirectiveFactory) { 7 | return editableDirectiveFactory({ 8 | directiveName: 'editableUidate', 9 | inputTpl: '', 10 | render: function() { 11 | this.parent.render.call(this); 12 | this.inputEl.attr('ui-date', this.attrs.eUiDate); 13 | this.inputEl.attr('placeholder', this.attrs.ePlaceholder); 14 | } 15 | }); 16 | }]); 17 | -------------------------------------------------------------------------------- /src/js/directives/uiselect.js: -------------------------------------------------------------------------------- 1 | /* 2 | AngularJS-native version of Select2 and Selectize 3 | https://github.com/angular-ui/ui-select 4 | */ 5 | angular.module('xeditable').directive('editableUiSelect',['editableDirectiveFactory', 'editableUtils', 6 | function(editableDirectiveFactory, editableUtils) { 7 | var dir = editableDirectiveFactory({ 8 | directiveName: 'editableUiSelect', 9 | inputTpl: '', 10 | render: function () { 11 | this.parent.render.call(this); 12 | this.inputEl.append(editableUtils.rename('ui-select-match', this.attrs.$matchElement)); 13 | this.inputEl.append(editableUtils.rename('ui-select-choices', this.attrs.$choicesElement)); 14 | this.inputEl.removeAttr('ng-model'); 15 | this.inputEl.attr('ng-model', '$parent.$parent.$data'); 16 | }, 17 | autosubmit: function() { 18 | var self = this; 19 | self.inputEl.bind('change', function() { 20 | setTimeout(function() { 21 | self.scope.$apply(function() { 22 | self.scope.$form.$submit(); 23 | }); 24 | }, 500); 25 | }); 26 | 27 | self.inputEl.bind('keydown', function(e) { 28 | //submit on tab 29 | if (e.keyCode === 9 && self.editorEl.attr('blur') === 'submit') { 30 | self.scope.$apply(function() { 31 | self.scope.$form.$submit(); 32 | }); 33 | } 34 | }); 35 | } 36 | }); 37 | 38 | var linkOrg = dir.link; 39 | 40 | dir.link = function (scope, el, attrs, ctrl) { 41 | var matchEl = el.find('editable-ui-select-match'); 42 | var choicesEl = el.find('editable-ui-select-choices'); 43 | 44 | attrs.$matchElement = matchEl.clone(); 45 | attrs.$choicesElement = choicesEl.clone(); 46 | 47 | matchEl.remove(); 48 | choicesEl.remove(); 49 | 50 | return linkOrg(scope, el, attrs, ctrl); 51 | }; 52 | 53 | return dir; 54 | }]); -------------------------------------------------------------------------------- /src/js/icons.js: -------------------------------------------------------------------------------- 1 | /* 2 | Editable icons: 3 | - default 4 | - font-awesome 5 | 6 | */ 7 | angular.module('xeditable').factory('editableIcons', function() { 8 | 9 | var icons = { 10 | //Icon-set to use, defaults to bootstrap icons 11 | default: { 12 | 'bs2': { 13 | ok: 'icon-ok icon-white', 14 | cancel: 'icon-remove', 15 | clear: 'icon-trash', 16 | calendar: 'icon-calendar' 17 | }, 18 | 'bs3': { 19 | ok: 'glyphicon glyphicon-ok', 20 | cancel: 'glyphicon glyphicon-remove', 21 | clear: 'glyphicon glyphicon-trash', 22 | calendar: 'glyphicon glyphicon-calendar' 23 | }, 24 | 'bs4': { 25 | ok: 'fa fa-check', 26 | cancel: 'fa fa-times', 27 | clear: 'fa fa-trash', 28 | calendar: 'fa fa-calendar' 29 | } 30 | }, 31 | external: { 32 | 'font-awesome': { 33 | ok: 'fa fa-check', 34 | cancel: 'fa fa-times', 35 | clear: 'fa fa-trash', 36 | calendar: 'fa fa-calendar' 37 | } 38 | } 39 | }; 40 | 41 | return icons; 42 | }); 43 | -------------------------------------------------------------------------------- /starter/angular-xeditable/css/xeditable.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | angular-xeditable - 0.10.2 3 | Edit-in-place for angular.js 4 | Build date: 2019-11-01 5 | */ 6 | 7 | .editable-wrap{display:inline-block;white-space:pre;margin:0}.editable-wrap .editable-controls,.editable-wrap .editable-error{margin-bottom:0}.editable-wrap .editable-controls>input,.editable-wrap .editable-controls>select,.editable-wrap .editable-controls>textarea{margin-bottom:0}.editable-wrap .editable-input{display:inline-block}.editable-buttons{display:inline-block;vertical-align:top}.editable-buttons button{margin-left:5px}.editable-input.editable-has-buttons{width:auto}.editable-text{white-space:nowrap}.editable-bsdate{white-space:nowrap}.editable-bstime{white-space:nowrap}.editable-bstime .editable-input input[type=text]{width:46px}.editable-bstime .well-small{margin-bottom:0;padding:10px}.editable-range output{display:inline-block;min-width:30px;vertical-align:top;text-align:center}.editable-color input[type=color]{width:50px}.editable-checkbox label span,.editable-checklist label span,.editable-radiolist label span{margin-left:7px;margin-right:10px}.editable-hide{display:none!important}.editable-click,a.editable-click{text-decoration:none;color:#428bca;border-bottom:dashed 1px #428bca}.editable-click:hover,a.editable-click:hover{text-decoration:none;color:#2a6496;border-bottom-color:#2a6496}.editable-empty,.editable-empty:hover,.editable-empty:focus,a.editable-empty,a.editable-empty:hover,a.editable-empty:focus{font-style:italic;color:#D14;text-decoration:none}.ui-popover-wrapper a{display:inline!important}.ui-popover-wrapper form{display:none!important}.popover-wrapper>a{display:inline!important}.popover-wrapper{display:inline;position:relative}.popover-wrapper form{position:absolute;top:-53px;background:#FFF;border:1px solid #AAA;border-radius:5px;padding:7px;width:auto;display:inline-block;left:50%;z-index:101}.popover-wrapper form:before{content:"";width:0;height:0;border-left:10px solid transparent;border-right:10px solid transparent;border-top:10px solid #AAA;position:absolute;bottom:-10px}.popover-wrapper form:after{content:"";width:0;height:0;border-left:9px solid transparent;border-right:9px solid transparent;border-top:9px solid #FFF;position:absolute;bottom:-9px}@media screen and (max-width:750px){.popover-wrapper form{margin-left:-60px}.popover-wrapper form:before{left:50px}.popover-wrapper form:after{left:51px}}@media screen and (min-width:750px){.popover-wrapper form{margin-left:-110px}.popover-wrapper form:before{left:100px}.popover-wrapper form:after{left:101px}} -------------------------------------------------------------------------------- /starter/app.js: -------------------------------------------------------------------------------- 1 | var app = angular.module("app", ["xeditable"]); 2 | 3 | app.run(function(editableOptions) { 4 | editableOptions.theme = 'bs3'; // bootstrap3 theme. Can be also 'bs2', 'bs3', 'default' 5 | }); 6 | 7 | app.controller('Ctrl', function($scope) { 8 | $scope.user = { 9 | name: 'awesome user' 10 | }; 11 | }); -------------------------------------------------------------------------------- /starter/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Angular-xeditable Starter Template 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | Angular-xeditable Starter Template 20 | 21 | 22 | {{ user.name || "empty" }} 23 | 24 | {{ user | json }} 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /test/e2e/dev-test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Dev Test Runner 5 | 6 | 7 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /test/e2e/docs-test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Docs Test Runner 5 | 6 | 7 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /zip/angular-xeditable-0.1.0.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vitalets/angular-xeditable/40c4dfbfa33e396a263ed8c120bd17853256e1b6/zip/angular-xeditable-0.1.0.zip -------------------------------------------------------------------------------- /zip/angular-xeditable-0.1.1.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vitalets/angular-xeditable/40c4dfbfa33e396a263ed8c120bd17853256e1b6/zip/angular-xeditable-0.1.1.zip -------------------------------------------------------------------------------- /zip/angular-xeditable-0.1.10.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vitalets/angular-xeditable/40c4dfbfa33e396a263ed8c120bd17853256e1b6/zip/angular-xeditable-0.1.10.zip -------------------------------------------------------------------------------- /zip/angular-xeditable-0.1.11.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vitalets/angular-xeditable/40c4dfbfa33e396a263ed8c120bd17853256e1b6/zip/angular-xeditable-0.1.11.zip -------------------------------------------------------------------------------- /zip/angular-xeditable-0.1.12.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vitalets/angular-xeditable/40c4dfbfa33e396a263ed8c120bd17853256e1b6/zip/angular-xeditable-0.1.12.zip -------------------------------------------------------------------------------- /zip/angular-xeditable-0.1.2.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vitalets/angular-xeditable/40c4dfbfa33e396a263ed8c120bd17853256e1b6/zip/angular-xeditable-0.1.2.zip -------------------------------------------------------------------------------- /zip/angular-xeditable-0.1.3.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vitalets/angular-xeditable/40c4dfbfa33e396a263ed8c120bd17853256e1b6/zip/angular-xeditable-0.1.3.zip -------------------------------------------------------------------------------- /zip/angular-xeditable-0.1.4.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vitalets/angular-xeditable/40c4dfbfa33e396a263ed8c120bd17853256e1b6/zip/angular-xeditable-0.1.4.zip -------------------------------------------------------------------------------- /zip/angular-xeditable-0.1.5.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vitalets/angular-xeditable/40c4dfbfa33e396a263ed8c120bd17853256e1b6/zip/angular-xeditable-0.1.5.zip -------------------------------------------------------------------------------- /zip/angular-xeditable-0.1.6.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vitalets/angular-xeditable/40c4dfbfa33e396a263ed8c120bd17853256e1b6/zip/angular-xeditable-0.1.6.zip -------------------------------------------------------------------------------- /zip/angular-xeditable-0.1.7.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vitalets/angular-xeditable/40c4dfbfa33e396a263ed8c120bd17853256e1b6/zip/angular-xeditable-0.1.7.zip -------------------------------------------------------------------------------- /zip/angular-xeditable-0.1.8.1.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vitalets/angular-xeditable/40c4dfbfa33e396a263ed8c120bd17853256e1b6/zip/angular-xeditable-0.1.8.1.zip -------------------------------------------------------------------------------- /zip/angular-xeditable-0.1.8.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vitalets/angular-xeditable/40c4dfbfa33e396a263ed8c120bd17853256e1b6/zip/angular-xeditable-0.1.8.zip -------------------------------------------------------------------------------- /zip/angular-xeditable-0.1.9.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vitalets/angular-xeditable/40c4dfbfa33e396a263ed8c120bd17853256e1b6/zip/angular-xeditable-0.1.9.zip -------------------------------------------------------------------------------- /zip/angular-xeditable-0.10.0.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vitalets/angular-xeditable/40c4dfbfa33e396a263ed8c120bd17853256e1b6/zip/angular-xeditable-0.10.0.zip -------------------------------------------------------------------------------- /zip/angular-xeditable-0.10.1.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vitalets/angular-xeditable/40c4dfbfa33e396a263ed8c120bd17853256e1b6/zip/angular-xeditable-0.10.1.zip -------------------------------------------------------------------------------- /zip/angular-xeditable-0.10.2.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vitalets/angular-xeditable/40c4dfbfa33e396a263ed8c120bd17853256e1b6/zip/angular-xeditable-0.10.2.zip -------------------------------------------------------------------------------- /zip/angular-xeditable-0.2.0.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vitalets/angular-xeditable/40c4dfbfa33e396a263ed8c120bd17853256e1b6/zip/angular-xeditable-0.2.0.zip -------------------------------------------------------------------------------- /zip/angular-xeditable-0.3.0.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vitalets/angular-xeditable/40c4dfbfa33e396a263ed8c120bd17853256e1b6/zip/angular-xeditable-0.3.0.zip -------------------------------------------------------------------------------- /zip/angular-xeditable-0.4.0.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vitalets/angular-xeditable/40c4dfbfa33e396a263ed8c120bd17853256e1b6/zip/angular-xeditable-0.4.0.zip -------------------------------------------------------------------------------- /zip/angular-xeditable-0.5.0.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vitalets/angular-xeditable/40c4dfbfa33e396a263ed8c120bd17853256e1b6/zip/angular-xeditable-0.5.0.zip -------------------------------------------------------------------------------- /zip/angular-xeditable-0.6.0.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vitalets/angular-xeditable/40c4dfbfa33e396a263ed8c120bd17853256e1b6/zip/angular-xeditable-0.6.0.zip -------------------------------------------------------------------------------- /zip/angular-xeditable-0.7.0.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vitalets/angular-xeditable/40c4dfbfa33e396a263ed8c120bd17853256e1b6/zip/angular-xeditable-0.7.0.zip -------------------------------------------------------------------------------- /zip/angular-xeditable-0.7.1.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vitalets/angular-xeditable/40c4dfbfa33e396a263ed8c120bd17853256e1b6/zip/angular-xeditable-0.7.1.zip -------------------------------------------------------------------------------- /zip/angular-xeditable-0.8.0.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vitalets/angular-xeditable/40c4dfbfa33e396a263ed8c120bd17853256e1b6/zip/angular-xeditable-0.8.0.zip -------------------------------------------------------------------------------- /zip/angular-xeditable-0.8.1.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vitalets/angular-xeditable/40c4dfbfa33e396a263ed8c120bd17853256e1b6/zip/angular-xeditable-0.8.1.zip -------------------------------------------------------------------------------- /zip/angular-xeditable-0.9.0.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vitalets/angular-xeditable/40c4dfbfa33e396a263ed8c120bd17853256e1b6/zip/angular-xeditable-0.9.0.zip -------------------------------------------------------------------------------- /zip/angular-xeditable-starter.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vitalets/angular-xeditable/40c4dfbfa33e396a263ed8c120bd17853256e1b6/zip/angular-xeditable-starter.zip --------------------------------------------------------------------------------
{{ user.desc || 'no description' }}