Built motion from commit 54a160d.|0.0.140
[motion.git] / public / bower_components / angular-xeditable / xeditable.js
1 /*!
2 angular-xeditable - 0.1.12
3 Edit-in-place for angular.js
4 Build date: 2016-04-14 
5 */
6 /**
7  * Angular-xeditable module 
8  *
9  */
10 angular.module('xeditable', [])
11
12
13 /**
14  * Default options. 
15  *
16  * @namespace editable-options
17  */
18 //todo: maybe better have editableDefaults, not options...
19 .value('editableOptions', {
20   /**
21    * Theme. Possible values `bs3`, `bs2`, `default`.
22    * 
23    * @var {string} theme
24    * @memberOf editable-options
25    */  
26   theme: 'default',
27   /**
28    * Icon Set. Possible values `font-awesome`, `default`.
29    * 
30    * @var {string} icon set
31    * @memberOf editable-options
32    */  
33   icon_set: 'default',
34   /**
35    * Whether to show buttons for single editalbe element.  
36    * Possible values `right` (default), `no`.
37    * 
38    * @var {string} buttons
39    * @memberOf editable-options
40    */    
41   buttons: 'right',
42   /**
43    * Default value for `blur` attribute of single editable element.  
44    * Can be `cancel|submit|ignore`.
45    * 
46    * @var {string} blurElem
47    * @memberOf editable-options
48    */
49   blurElem: 'cancel',
50   /**
51    * Default value for `blur` attribute of editable form.  
52    * Can be `cancel|submit|ignore`.
53    * 
54    * @var {string} blurForm
55    * @memberOf editable-options
56    */
57   blurForm: 'ignore',
58   /**
59    * How input elements get activated. Possible values: `focus|select|none`.
60    *
61    * @var {string} activate
62    * @memberOf editable-options
63    */
64   activate: 'focus',
65   /**
66    * Whether to disable x-editable. Can be overloaded on each element.
67    *
68    * @var {boolean} isDisabled
69    * @memberOf editable-options
70    */
71    isDisabled: false,
72   
73   /**
74    * Event, on which the edit mode gets activated. 
75    * Can be any event.
76    *
77    * @var {string} activationEvent
78    * @memberOf editable-options
79    */
80   activationEvent: 'click'
81
82 });
83
84 /*
85  Angular-ui bootstrap datepicker
86  http://angular-ui.github.io/bootstrap/#/datepicker
87  */
88 angular.module('xeditable').directive('editableBsdate', ['editableDirectiveFactory',
89     function(editableDirectiveFactory) {
90         return editableDirectiveFactory({
91             directiveName: 'editableBsdate',
92             inputTpl: '<div></div>',
93             render: function() {
94                 /** This basically renders a datepicker as in the example shown in
95                  **  http://angular-ui.github.io/bootstrap/#/datepicker
96                  **  The attributes are all the same as in the bootstrap-ui datepicker with e- as prefix
97                  **/
98                 this.parent.render.call(this);
99
100                 var inputDatePicker = angular.element('<input type="text" class="form-control" ng-model="$data"/>');
101                 var buttonDatePicker = angular.element('<button type="button" class="btn btn-default"><i class="glyphicon glyphicon-calendar"></i></button>');
102                 var buttonWrapper = angular.element('<span class="input-group-btn"></span>');
103
104                 inputDatePicker.attr('uib-datepicker-popup', this.attrs.eDatepickerPopupXEditable || 'yyyy/MM/dd' );
105                 inputDatePicker.attr('is-open', this.attrs.eIsOpen);
106                 inputDatePicker.attr('date-disabled', this.attrs.eDateDisabled);
107                 inputDatePicker.attr('uib-datepicker-popup', this.attrs.eDatepickerPopup);
108                 inputDatePicker.attr('min-date', this.attrs.eMinDate);
109                 inputDatePicker.attr('max-date', this.attrs.eMaxDate);
110                 inputDatePicker.attr('year-range', this.attrs.eYearRange || 20);
111                 inputDatePicker.attr('show-button-bar', this.attrs.eShowButtonBar || true);
112                 inputDatePicker.attr('current-text', this.attrs.eCurrentText || 'Today');
113                 inputDatePicker.attr('clear-text', this.attrs.eClearText || 'Clear');
114                 inputDatePicker.attr('close-text', this.attrs.eCloseText || 'Done');
115                 inputDatePicker.attr('close-on-date-selection', this.attrs.eCloseOnDateSelection || true);
116                 inputDatePicker.attr('datepicker-append-to-body', this.attrs.eDatePickerAppendToBody || false);
117                 inputDatePicker.attr('date-disabled', this.attrs.eDateDisabled);
118                 inputDatePicker.attr('name', this.attrs.eName);
119
120                 this.scope.dateOptions = {
121                     formatDay:  this.attrs.eFormatDay || 'dd',
122                     formatMonth: this.attrs.eFormatMonth || 'MMMM',
123                     formatYear: this.attrs.eFormatYear || 'yyyy',
124                     formatDayHeader: this.attrs.eFormatDayHeader || 'EEE',
125                     formatDayTitle: this.attrs.eFormatDayTitle || 'MMMM yyyy',
126                     formatMonthTitle: this.attrs.eFormatMonthTitle || 'yyyy',
127                     showWeeks: this.attrs.eShowWeeks ? this.attrs.eShowWeeks.toLowerCase() === 'true' : true,
128                     startingDay: this.attrs.eStartingDay || 0,
129                     minMode: this.attrs.eMinMode || 'day',
130                     maxMode: this.attrs.eMaxMode || 'year',
131                     initDate: this.attrs.eInitDate || new Date(),
132                     datepickerMode: this.attrs.eDatepickerMode || 'day'
133                 };
134
135                 inputDatePicker.attr('datepicker-options', "dateOptions");
136
137                 buttonDatePicker.attr('ng-click',this.attrs.eNgClick);
138
139                 buttonWrapper.append(buttonDatePicker);
140                 this.inputEl.prepend(inputDatePicker);
141                 this.inputEl.append(buttonWrapper);
142
143                 this.inputEl.removeAttr('class');
144                 this.inputEl.removeAttr('ng-click');
145                 this.inputEl.removeAttr('is-open');
146                 this.inputEl.removeAttr('init-date');
147                 this.inputEl.removeAttr('datepicker-popup');
148                 this.inputEl.removeAttr('required');
149                 this.inputEl.removeAttr('ng-model');
150                 this.inputEl.removeAttr('date-picker-append-to-body');
151                 this.inputEl.removeAttr('name');
152                 this.inputEl.attr('class','input-group');
153             }
154         });
155 }]);
156 /*
157 Angular-ui bootstrap editable timepicker
158 http://angular-ui.github.io/bootstrap/#/timepicker
159 */
160 angular.module('xeditable').directive('editableBstime', ['editableDirectiveFactory',
161   function(editableDirectiveFactory) {
162     return editableDirectiveFactory({
163       directiveName: 'editableBstime',
164       inputTpl: '<uib-timepicker></uib-timepicker>',
165       render: function() {
166         this.parent.render.call(this);
167
168         // timepicker can't update model when ng-model set directly to it
169         // see: https://github.com/angular-ui/bootstrap/issues/1141
170         // so we wrap it into DIV
171         var div = angular.element('<div class="well well-small" style="display:inline-block;"></div>');
172
173         // move ng-model to wrapping div
174         div.attr('ng-model', this.inputEl.attr('ng-model'));
175         this.inputEl.removeAttr('ng-model');
176
177         // move ng-change to wrapping div
178         if(this.attrs.eNgChange) {
179           div.attr('ng-change', this.inputEl.attr('ng-change'));
180           this.inputEl.removeAttr('ng-change');
181         }
182
183         // wrap
184         this.inputEl.wrap(div);
185       }
186     });
187 }]);
188 //checkbox
189 angular.module('xeditable').directive('editableCheckbox', ['editableDirectiveFactory',
190   function(editableDirectiveFactory) {
191     return editableDirectiveFactory({
192       directiveName: 'editableCheckbox',
193       inputTpl: '<input type="checkbox">',
194       render: function() {
195         this.parent.render.call(this);
196         if(this.attrs.eTitle) {
197           this.inputEl.wrap('<label></label>');
198           this.inputEl.parent().append(this.attrs.eTitle);
199         }
200       },
201       autosubmit: function() {
202         var self = this;
203         self.inputEl.bind('change', function() {
204           setTimeout(function() {
205             self.scope.$apply(function() {
206               self.scope.$form.$submit();
207             });
208           }, 500);
209         });
210       }
211     });
212 }]);
213
214 // checklist
215 angular.module('xeditable').directive('editableChecklist', [
216   'editableDirectiveFactory',
217   'editableNgOptionsParser',
218   function(editableDirectiveFactory, editableNgOptionsParser) {
219     return editableDirectiveFactory({
220       directiveName: 'editableChecklist',
221       inputTpl: '<span></span>',
222       useCopy: true,
223       render: function() {
224         this.parent.render.call(this);
225         var parsed = editableNgOptionsParser(this.attrs.eNgOptions);
226         var html = '<label ng-repeat="'+parsed.ngRepeat+'">'+
227           '<input type="checkbox" checklist-model="$parent.$data" checklist-value="'+parsed.locals.valueFn+'">'+
228           '<span ng-bind="'+parsed.locals.displayFn+'"></span></label>';
229
230         this.inputEl.removeAttr('ng-model');
231         this.inputEl.removeAttr('ng-options');
232         this.inputEl.html(html);
233       }
234     });
235 }]);
236
237 angular.module('xeditable').directive('editableCombodate', ['editableDirectiveFactory', 'editableCombodate',
238   function(editableDirectiveFactory, editableCombodate) {
239     return editableDirectiveFactory({
240       directiveName: 'editableCombodate',
241       inputTpl: '<input type="text">',
242       render: function() {
243         this.parent.render.call(this);
244
245         var options = {
246           value: new Date(this.scope.$data)
247         };
248         var self = this;
249         angular.forEach(["format", "template", "minYear", "maxYear", "yearDescending", "minuteStep", "secondStep", "firstItem", "errorClass", "customClass", "roundTime", "smartDays"], function(name) {
250
251           var attrName = "e" + name.charAt(0).toUpperCase() + name.slice(1);
252           if (attrName in self.attrs) {
253             options[name] = self.attrs[attrName];
254           }
255         });
256
257         var combodate = editableCombodate.getInstance(this.inputEl, options);
258         combodate.$widget.find('select').bind('change', function(e) {
259           self.scope.$data = (new Date(combodate.getValue())).toISOString();
260         });
261       }
262     });
263   }
264 ]);
265
266 /*
267 Input types: text|email|tel|number|url|search|color|date|datetime|time|month|week
268 */
269
270 (function() {
271
272   var types = 'text|password|email|tel|number|url|search|color|date|datetime|time|month|week|file'.split('|');
273
274   //todo: datalist
275   
276   // generate directives
277   angular.forEach(types, function(type) {
278     var directiveName = 'editable'+type.charAt(0).toUpperCase() + type.slice(1);
279     angular.module('xeditable').directive(directiveName, ['editableDirectiveFactory',
280       function(editableDirectiveFactory) {
281         return editableDirectiveFactory({
282           directiveName: directiveName,
283           inputTpl: '<input type="'+type+'">'
284         });
285     }]);
286   });
287
288   //`range` is bit specific
289   angular.module('xeditable').directive('editableRange', ['editableDirectiveFactory',
290     function(editableDirectiveFactory) {
291       return editableDirectiveFactory({
292         directiveName: 'editableRange',
293         inputTpl: '<input type="range" id="range" name="range">',
294         render: function() {
295           this.parent.render.call(this);
296           this.inputEl.after('<output>{{$data}}</output>');
297         }        
298       });
299   }]);
300
301 }());
302
303
304 // radiolist
305 angular.module('xeditable').directive('editableRadiolist', [
306   'editableDirectiveFactory',
307   'editableNgOptionsParser',
308   function(editableDirectiveFactory, editableNgOptionsParser) {
309     return editableDirectiveFactory({
310       directiveName: 'editableRadiolist',
311       inputTpl: '<span></span>',
312       render: function() {
313         this.parent.render.call(this);
314         var parsed = editableNgOptionsParser(this.attrs.eNgOptions);
315         var html = '<label ng-repeat="'+parsed.ngRepeat+'">'+
316           '<input type="radio" ng-disabled="' + this.attrs.eNgDisabled + '" ng-model="$parent.$data" value="{{'+parsed.locals.valueFn+'}}">'+
317           '<span ng-bind="'+parsed.locals.displayFn+'"></span></label>';
318
319         this.inputEl.removeAttr('ng-model');
320         this.inputEl.removeAttr('ng-options');
321         this.inputEl.html(html);
322       },
323       autosubmit: function() {
324         var self = this;
325         self.inputEl.bind('change', function() {
326           setTimeout(function() {
327             self.scope.$apply(function() {
328               self.scope.$form.$submit();
329             });
330           }, 500);
331         });
332       }
333     });
334 }]);
335
336 //select
337 angular.module('xeditable').directive('editableSelect', ['editableDirectiveFactory',
338   function(editableDirectiveFactory) {
339     return editableDirectiveFactory({
340       directiveName: 'editableSelect',
341       inputTpl: '<select></select>',
342       autosubmit: function() {
343         var self = this;
344         self.inputEl.bind('change', function() {
345           self.scope.$apply(function() {
346             self.scope.$form.$submit();
347           });
348         });
349       }
350     });
351 }]);
352 //textarea
353 angular.module('xeditable').directive('editableTextarea', ['editableDirectiveFactory',
354   function(editableDirectiveFactory) {
355     return editableDirectiveFactory({
356       directiveName: 'editableTextarea',
357       inputTpl: '<textarea></textarea>',
358       addListeners: function() {
359         var self = this;
360         self.parent.addListeners.call(self);
361         // submit textarea by ctrl+enter even with buttons
362         if (self.single && self.buttons !== 'no') {
363           self.autosubmit();
364         }
365       },
366       autosubmit: function() {
367         var self = this;
368         self.inputEl.bind('keydown', function(e) {
369           if ((e.ctrlKey || e.metaKey) && (e.keyCode === 13)) {
370             self.scope.$apply(function() {
371               self.scope.$form.$submit();
372             });
373           }
374         });
375       }
376     });
377 }]);
378
379 /*
380  AngularJS-native version of Select2 and Selectize
381  https://github.com/angular-ui/ui-select
382  */
383 angular.module('xeditable').directive('editableUiSelect',['editableDirectiveFactory',
384     function(editableDirectiveFactory) {
385         var rename = function (tag, el) {
386             var newEl = angular.element('<' + tag + '/>');
387             newEl.html(el.html());
388             var attrs = el[0].attributes;
389             for (var i = 0; i < attrs.length; ++i) {
390                 newEl.attr(attrs.item(i).nodeName, attrs.item(i).value);
391             }
392             return newEl;
393         };
394
395         var match = null;
396         var choices = null;
397         var dir = editableDirectiveFactory({
398             directiveName: 'editableUiSelect',
399             inputTpl: '<ui-select></ui-select>',
400             render: function () {
401                 this.parent.render.call(this);
402                 this.inputEl.append(rename('ui-select-match', match));
403                 this.inputEl.append(rename('ui-select-choices', choices));
404                 this.inputEl.removeAttr('ng-model');
405                 this.inputEl.attr('ng-model', '$parent.$data');
406             }
407         });
408
409         var linkOrg = dir.link;
410
411         dir.link = function (scope, el, attrs, ctrl) {
412             var matchEl = el.find('editable-ui-select-match');
413             var choicesEl = el.find('editable-ui-select-choices');
414
415             match = matchEl.clone();
416             choices = choicesEl.clone();
417
418             matchEl.remove();
419             choicesEl.remove();
420
421             return linkOrg(scope, el, attrs, ctrl);
422         };
423
424         return dir;
425     }]);
426 /**
427  * EditableController class. 
428  * Attached to element with `editable-xxx` directive.
429  *
430  * @namespace editable-element
431  */
432 /*
433 TODO: this file should be refactored to work more clear without closures!
434 */
435 angular.module('xeditable').factory('editableController', 
436   ['$q', 'editableUtils',
437   function($q, editableUtils) {
438
439   //EditableController function
440   EditableController.$inject = ['$scope', '$attrs', '$element', '$parse', 'editableThemes', 'editableIcons', 'editableOptions', '$rootScope', '$compile', '$q'];
441   function EditableController($scope, $attrs, $element, $parse, editableThemes, editableIcons, editableOptions, $rootScope, $compile, $q) {
442     var valueGetter;
443
444     //if control is disabled - it does not participate in waiting process
445     var inWaiting;
446
447     var self = this;
448
449     self.scope = $scope;
450     self.elem = $element;
451     self.attrs = $attrs;
452     self.inputEl = null;
453     self.editorEl = null;
454     self.single = true;
455     self.error = '';
456     self.theme =  editableThemes[editableOptions.theme] || editableThemes['default'];
457     self.parent = {};
458
459     //will be undefined if icon_set is default and theme is default
460     self.icon_set = editableOptions.icon_set === 'default' ? editableIcons.default[editableOptions.theme] : editableIcons.external[editableOptions.icon_set];
461
462     //to be overwritten by directive
463     self.inputTpl = '';
464     self.directiveName = '';
465
466     // with majority of controls copy is not needed, but..
467     // copy MUST NOT be used for `select-multiple` with objects as items
468     // copy MUST be used for `checklist`
469     self.useCopy = false;
470
471     //runtime (defaults)
472     self.single = null;
473
474     /**
475      * Attributes defined with `e-*` prefix automatically transfered from original element to
476      * control.  
477      * For example, if you set `<span editable-text="user.name" e-style="width: 100px"`>
478      * then input will appear as `<input style="width: 100px">`.  
479      * See [demo](#text-customize).
480      * 
481      * @var {any|attribute} e-*
482      * @memberOf editable-element
483      */ 
484
485     /**
486      * Whether to show ok/cancel buttons. Values: `right|no`.
487      * If set to `no` control automatically submitted when value changed.  
488      * If control is part of form buttons will never be shown. 
489      * 
490      * @var {string|attribute} buttons
491      * @memberOf editable-element
492      */    
493     self.buttons = 'right'; 
494     /**
495      * Action when control losses focus. Values: `cancel|submit|ignore`.
496      * Has sense only for single editable element.
497      * Otherwise, if control is part of form - you should set `blur` of form, not of individual element.
498      * 
499      * @var {string|attribute} blur
500      * @memberOf editable-element
501      */     
502     // no real `blur` property as it is transfered to editable form
503
504     //init
505     self.init = function(single) {
506       self.single = single;
507
508       self.name = $attrs.eName || $attrs[self.directiveName];
509       /*
510       if(!$attrs[directiveName] && !$attrs.eNgModel && ($attrs.eValue === undefined)) {
511         throw 'You should provide value for `'+directiveName+'` or `e-value` in editable element!';
512       }
513       */
514       if($attrs[self.directiveName]) {
515         valueGetter = $parse($attrs[self.directiveName]);
516       } else {
517         throw 'You should provide value for `'+self.directiveName+'` in editable element!';
518       }
519
520       // settings for single and non-single
521       if (!self.single) {
522         // hide buttons for non-single
523         self.buttons = 'no';
524       } else {
525         self.buttons = self.attrs.buttons || editableOptions.buttons;
526       }
527
528       //if name defined --> watch changes and update $data in form
529       if($attrs.eName) {
530         self.scope.$watch('$data', function(newVal){
531           self.scope.$form.$data[$attrs.eName] = newVal;
532         });
533       }
534
535       /**
536        * Called when control is shown.  
537        * See [demo](#select-remote).
538        * 
539        * @var {method|attribute} onshow
540        * @memberOf editable-element
541        */
542       if($attrs.onshow) {
543         self.onshow = function() {
544           return self.catchError($parse($attrs.onshow)($scope));
545         };
546       }
547
548       /**
549        * Called when control is hidden after both save or cancel.  
550        * 
551        * @var {method|attribute} onhide
552        * @memberOf editable-element
553        */
554       if($attrs.onhide) {
555         self.onhide = function() {
556           return $parse($attrs.onhide)($scope);
557         };
558       }
559
560       /**
561        * Called when control is cancelled.  
562        * 
563        * @var {method|attribute} oncancel
564        * @memberOf editable-element
565        */
566       if($attrs.oncancel) {
567         self.oncancel = function() {
568           return $parse($attrs.oncancel)($scope);
569         };
570       }          
571
572       /**
573        * Called during submit before value is saved to model.  
574        * See [demo](#onbeforesave).
575        * 
576        * @var {method|attribute} onbeforesave
577        * @memberOf editable-element
578        */
579       if ($attrs.onbeforesave) {
580         self.onbeforesave = function() {
581           return self.catchError($parse($attrs.onbeforesave)($scope));
582         };
583       }
584
585       /**
586        * Called during submit after value is saved to model.  
587        * See [demo](#onaftersave).
588        * 
589        * @var {method|attribute} onaftersave
590        * @memberOf editable-element
591        */
592       if ($attrs.onaftersave) {
593         self.onaftersave = function() {
594           return self.catchError($parse($attrs.onaftersave)($scope));
595         };
596       }
597
598       // watch change of model to update editable element
599       // now only add/remove `editable-empty` class.
600       // Initially this method called with newVal = undefined, oldVal = undefined
601       // so no need initially call handleEmpty() explicitly
602       $scope.$parent.$watch($attrs[self.directiveName], function(newVal, oldVal) {
603         self.setLocalValue();
604         self.handleEmpty();
605       });
606     };
607
608     self.render = function() {
609       var theme = self.theme;
610
611       //build input
612       self.inputEl = angular.element(self.inputTpl);
613
614       //build controls
615       self.controlsEl = angular.element(theme.controlsTpl);
616       self.controlsEl.append(self.inputEl);
617
618       //build buttons
619       if(self.buttons !== 'no') {
620         self.buttonsEl = angular.element(theme.buttonsTpl);
621         self.submitEl = angular.element(theme.submitTpl);
622         self.cancelEl = angular.element(theme.cancelTpl);
623         if(self.icon_set) {
624           self.submitEl.find('span').addClass(self.icon_set.ok);
625           self.cancelEl.find('span').addClass(self.icon_set.cancel);
626         }
627         self.buttonsEl.append(self.submitEl).append(self.cancelEl);
628         self.controlsEl.append(self.buttonsEl);
629         
630         self.inputEl.addClass('editable-has-buttons');
631       }
632
633       //build error
634       self.errorEl = angular.element(theme.errorTpl);
635       self.controlsEl.append(self.errorEl);
636
637       //build editor
638       self.editorEl = angular.element(self.single ? theme.formTpl : theme.noformTpl);
639       self.editorEl.append(self.controlsEl);
640
641       // transfer `e-*|data-e-*|x-e-*` attributes
642       for(var k in $attrs.$attr) {
643         if(k.length <= 1) {
644           continue;
645         }
646         var transferAttr = false;
647         var nextLetter = k.substring(1, 2);
648
649         // if starts with `e` + uppercase letter
650         if(k.substring(0, 1) === 'e' && nextLetter === nextLetter.toUpperCase()) {
651           transferAttr = k.substring(1); // cut `e`
652         } else {
653           continue;
654         }
655         
656         // exclude `form` and `ng-submit`, 
657         if(transferAttr === 'Form' || transferAttr === 'NgSubmit') {
658           continue;
659         }
660
661         // convert back to lowercase style
662         transferAttr = transferAttr.substring(0, 1).toLowerCase() + editableUtils.camelToDash(transferAttr.substring(1));  
663
664         // workaround for attributes without value (e.g. `multiple = "multiple"`)
665         // except for 'e-value'
666         var attrValue = (transferAttr !== 'value' && $attrs[k] === '') ? transferAttr : $attrs[k];
667
668         // set attributes to input
669         self.inputEl.attr(transferAttr, attrValue);
670       }
671
672       self.inputEl.addClass('editable-input');
673       self.inputEl.attr('ng-model', '$data');
674
675       // add directiveName class to editor, e.g. `editable-text`
676       self.editorEl.addClass(editableUtils.camelToDash(self.directiveName));
677
678       if(self.single) {
679         self.editorEl.attr('editable-form', '$form');
680         // transfer `blur` to form
681         self.editorEl.attr('blur', self.attrs.blur || (self.buttons === 'no' ? 'cancel' : editableOptions.blurElem));
682       }
683
684       //apply `postrender` method of theme
685       if(angular.isFunction(theme.postrender)) {
686         theme.postrender.call(self);
687       }
688
689     };
690
691     // with majority of controls copy is not needed, but..
692     // copy MUST NOT be used for `select-multiple` with objects as items
693     // copy MUST be used for `checklist`
694     self.setLocalValue = function() {
695       self.scope.$data = self.useCopy ? 
696         angular.copy(valueGetter($scope.$parent)) : 
697         valueGetter($scope.$parent);
698     };
699
700     //show
701     self.show = function() {
702       // set value of scope.$data
703       self.setLocalValue();
704
705       /*
706       Originally render() was inside init() method, but some directives polluting editorEl,
707       so it is broken on second openning.
708       Cloning is not a solution as jqLite can not clone with event handler's.
709       */
710       self.render();
711
712       // insert into DOM
713       $element.after(self.editorEl);
714
715       // compile (needed to attach ng-* events from markup)
716       $compile(self.editorEl)($scope);
717
718       // attach listeners (`escape`, autosubmit, etc)
719       self.addListeners();
720
721       // hide element
722       $element.addClass('editable-hide');
723
724       // onshow
725       return self.onshow();
726     };
727
728     //hide
729     self.hide = function() {
730       
731       self.editorEl.remove();
732       $element.removeClass('editable-hide');
733
734       // onhide
735       return self.onhide();
736     };
737
738     // cancel
739     self.cancel = function() {
740       // oncancel
741       self.oncancel();
742       // don't call hide() here as it called in form's code
743     };
744
745     /*
746     Called after show to attach listeners
747     */
748     self.addListeners = function() {
749       // bind keyup for `escape`
750       self.inputEl.bind('keyup', function(e) {
751           if(!self.single) {
752             return;
753           }
754
755           // todo: move this to editable-form!
756           switch(e.keyCode) {
757             // hide on `escape` press
758             case 27:
759               self.scope.$apply(function() {
760                 self.scope.$form.$cancel();
761               });
762             break;
763           }
764       });
765
766       // autosubmit when `no buttons`
767       if (self.single && self.buttons === 'no') {
768         self.autosubmit();
769       }
770
771       // click - mark element as clicked to exclude in document click handler
772       self.editorEl.bind('click', function(e) {
773         // ignore right/middle button click
774         if (e.which && e.which !== 1) {
775           return;
776         }
777
778         if (self.scope.$form.$visible) {
779           self.scope.$form._clicked = true;
780         }
781       });
782     };
783
784     // setWaiting
785     self.setWaiting = function(value) {
786       if (value) {
787         // participate in waiting only if not disabled
788         inWaiting = !self.inputEl.attr('disabled') &&
789                     !self.inputEl.attr('ng-disabled') &&
790                     !self.inputEl.attr('ng-enabled');
791         if (inWaiting) {
792           self.inputEl.attr('disabled', 'disabled');
793           if(self.buttonsEl) {
794             self.buttonsEl.find('button').attr('disabled', 'disabled');
795           }
796         }
797       } else {
798         if (inWaiting) {
799           self.inputEl.removeAttr('disabled');
800           if (self.buttonsEl) {
801             self.buttonsEl.find('button').removeAttr('disabled');
802           }
803         }
804       }
805     };
806
807     self.activate = function(start, end) {
808       setTimeout(function() {
809         var el = self.inputEl[0];
810         if (editableOptions.activate === 'focus' && el.focus) {
811           if(start){
812             end = end || start;
813             el.onfocus = function(){
814               var that = this;
815               setTimeout(function(){
816                 that.setSelectionRange(start,end);
817               });
818             };
819           }
820           el.focus();
821         }
822         if (editableOptions.activate === 'select' && el.select) {
823           el.select();
824         }
825       }, 0);
826     };
827
828     self.setError = function(msg) {
829       if(!angular.isObject(msg)) {
830         $scope.$error = msg;
831         self.error = msg;
832       }
833     };
834
835     /*
836     Checks that result is string or promise returned string and shows it as error message
837     Applied to onshow, onbeforesave, onaftersave
838     */
839     self.catchError = function(result, noPromise) {
840       if (angular.isObject(result) && noPromise !== true) {
841         $q.when(result).then(
842           //success and fail handlers are equal
843           angular.bind(this, function(r) {
844             this.catchError(r, true);
845           }),
846           angular.bind(this, function(r) {
847             this.catchError(r, true);
848           })
849         );
850       //check $http error
851       } else if (noPromise && angular.isObject(result) && result.status &&
852         (result.status !== 200) && result.data && angular.isString(result.data)) {
853         this.setError(result.data);
854         //set result to string: to let form know that there was error
855         result = result.data;
856       } else if (angular.isString(result)) {
857         this.setError(result);
858       }
859       return result;
860     };
861
862     self.save = function() {
863       valueGetter.assign($scope.$parent,
864           self.useCopy ? angular.copy(self.scope.$data) : self.scope.$data);
865
866       // no need to call handleEmpty here as we are watching change of model value
867       // self.handleEmpty();
868     };
869
870     /*
871     attach/detach `editable-empty` class to element
872     */
873     self.handleEmpty = function() {
874       var val = valueGetter($scope.$parent);
875       var isEmpty = val === null || val === undefined || val === "" || (angular.isArray(val) && val.length === 0); 
876       $element.toggleClass('editable-empty', isEmpty);
877     };
878
879     /*
880     Called when `buttons = "no"` to submit automatically
881     */
882     self.autosubmit = angular.noop;
883
884     self.onshow = angular.noop;
885     self.onhide = angular.noop;
886     self.oncancel = angular.noop;
887     self.onbeforesave = angular.noop;
888     self.onaftersave = angular.noop;
889   }
890
891   return EditableController;
892 }]);
893
894 /*
895 editableFactory is used to generate editable directives (see `/directives` folder)
896 Inside it does several things:
897 - detect form for editable element. Form may be one of three types:
898   1. autogenerated form (for single editable elements)
899   2. wrapper form (element wrapped by <form> tag)
900   3. linked form (element has `e-form` attribute pointing to existing form)
901
902 - attach editableController to element
903
904 Depends on: editableController, editableFormFactory
905 */
906 angular.module('xeditable').factory('editableDirectiveFactory',
907 ['$parse', '$compile', 'editableThemes', '$rootScope', '$document', 'editableController', 'editableFormController', 'editableOptions',
908 function($parse, $compile, editableThemes, $rootScope, $document, editableController, editableFormController, editableOptions) {
909
910   //directive object
911   return function(overwrites) {
912     return {
913       restrict: 'A',
914       scope: true,
915       require: [overwrites.directiveName, '?^form'],
916       controller: editableController,
917       link: function(scope, elem, attrs, ctrl) {
918         // editable controller
919         var eCtrl = ctrl[0];
920
921         // form controller
922         var eFormCtrl;
923
924         // this variable indicates is element is bound to some existing form, 
925         // or it's single element who's form will be generated automatically
926         // By default consider single element without any linked form.ß
927         var hasForm = false;
928      
929         // element wrapped by form
930         if(ctrl[1]) {
931           eFormCtrl = ctrl[1];
932           hasForm = attrs.eSingle === undefined;
933         } else if(attrs.eForm) { // element not wrapped by <form>, but we hane `e-form` attr
934           var getter = $parse(attrs.eForm)(scope);
935           if(getter) { // form exists in scope (above), e.g. editable column
936             eFormCtrl = getter;
937             hasForm = true;
938           } else { // form exists below or not exist at all: check document.forms
939             for(var i=0; i<$document[0].forms.length;i++){
940               if($document[0].forms[i].name === attrs.eForm) {
941                 // form is below and not processed yet
942                 eFormCtrl = null;
943                 hasForm = true;
944                 break;
945               }
946             }
947           }
948         }
949
950         /*
951         if(hasForm && !attrs.eName) {
952           throw 'You should provide `e-name` for editable element inside form!';
953         }
954         */
955
956         //check for `editable-form` attr in form
957         /*
958         if(eFormCtrl && ) {
959           throw 'You should provide `e-name` for editable element inside form!';
960         }
961         */
962
963         // store original props to `parent` before merge
964         angular.forEach(overwrites, function(v, k) {
965           if(eCtrl[k] !== undefined) {
966             eCtrl.parent[k] = eCtrl[k];
967           }
968         });
969
970         // merge overwrites to base editable controller
971         angular.extend(eCtrl, overwrites);
972
973         // x-editable can be disabled using editableOption or edit-disabled attribute
974         var disabled = angular.isDefined(attrs.editDisabled) ?
975           scope.$eval(attrs.editDisabled) :
976           editableOptions.isDisabled;
977
978         if (disabled) {
979           return;
980         }
981         
982         // init editable ctrl
983         eCtrl.init(!hasForm);
984
985         // publich editable controller as `$editable` to be referenced in html
986         scope.$editable = eCtrl;
987
988         // add `editable` class to element
989         elem.addClass('editable');
990
991         // hasForm
992         if(hasForm) {
993           if(eFormCtrl) {
994             scope.$form = eFormCtrl;
995             if(!scope.$form.$addEditable) {
996               throw 'Form with editable elements should have `editable-form` attribute.';
997             }
998             scope.$form.$addEditable(eCtrl);
999           } else {
1000             // future form (below): add editable controller to buffer and add to form later
1001             $rootScope.$$editableBuffer = $rootScope.$$editableBuffer || {};
1002             $rootScope.$$editableBuffer[attrs.eForm] = $rootScope.$$editableBuffer[attrs.eForm] || [];
1003             $rootScope.$$editableBuffer[attrs.eForm].push(eCtrl);
1004             scope.$form = null; //will be re-assigned later
1005           }
1006         // !hasForm
1007         } else {
1008           // create editableform controller
1009           scope.$form = editableFormController();
1010           // add self to editable controller
1011           scope.$form.$addEditable(eCtrl);
1012
1013           // if `e-form` provided, publish local $form in scope
1014           if(attrs.eForm) {
1015             scope.$parent[attrs.eForm] = scope.$form;
1016           }
1017
1018           // bind click - if no external form defined
1019           if(!attrs.eForm || attrs.eClickable) {
1020             elem.addClass('editable-click');
1021             elem.bind(editableOptions.activationEvent, function(e) {
1022               e.preventDefault();
1023               e.editable = eCtrl;
1024               scope.$apply(function(){
1025                 scope.$form.$show();
1026               });
1027             });
1028           }
1029         }
1030
1031       }
1032     };
1033   };
1034 }]);
1035
1036 /*
1037 Returns editableForm controller
1038 */
1039 angular.module('xeditable').factory('editableFormController', 
1040   ['$parse', '$document', '$rootScope', 'editablePromiseCollection', 'editableUtils',
1041   function($parse, $document, $rootScope, editablePromiseCollection, editableUtils) {
1042
1043   // array of opened editable forms
1044   var shown = [];
1045
1046   //Check if the child element correspond or is a descendant of the parent element
1047   var isSelfOrDescendant = function (parent, child) {
1048     if (child == parent) {
1049       return true;
1050     }
1051
1052     var node = child.parentNode;
1053     while (node !== null) {
1054       if (node == parent) {
1055         return true;
1056       }
1057       node = node.parentNode;
1058     }
1059     return false;
1060   };
1061   
1062   //Check if it is a real blur : if the click event appear on a shown editable elem, this is not a blur.
1063   var isBlur = function(shown, event) {
1064     var isBlur = true;
1065
1066     var editables = shown.$editables;
1067     angular.forEach(editables, function(v){
1068       var element = v.editorEl[0];
1069       if (isSelfOrDescendant(element, event.target))
1070         isBlur = false;
1071       
1072     });
1073     return isBlur;
1074   };
1075   
1076   // bind click to body: cancel|submit|ignore forms
1077   $document.bind('click', function(e) {
1078     // ignore right/middle button click
1079     if (e.which && e.which !== 1) {
1080       return;
1081     }
1082
1083     var toCancel = [];
1084     var toSubmit = [];
1085     for (var i=0; i<shown.length; i++) {
1086
1087       // exclude clicked
1088       if (shown[i]._clicked) {
1089         shown[i]._clicked = false;
1090         continue;
1091       }
1092
1093       // exclude waiting
1094       if (shown[i].$waiting) {
1095         continue;
1096       }
1097
1098       if (shown[i]._blur === 'cancel' && isBlur(shown[i], e)) {
1099         toCancel.push(shown[i]);
1100       }
1101
1102       if (shown[i]._blur === 'submit' && isBlur(shown[i], e)) {
1103         toSubmit.push(shown[i]);
1104       }
1105     }
1106
1107     if (toCancel.length || toSubmit.length) {
1108       $rootScope.$apply(function() {
1109         angular.forEach(toCancel, function(v){ v.$cancel(); });
1110         angular.forEach(toSubmit, function(v){ v.$submit(); });
1111       });
1112     }
1113   });
1114  
1115   $rootScope.$on('closeEdit', function() {
1116     for(var i=0; i < shown.length; i++) {
1117       shown[i].$hide();
1118     }
1119   }); 
1120
1121   var base = {
1122     $addEditable: function(editable) {
1123       //console.log('add editable', editable.elem, editable.elem.bind);
1124       this.$editables.push(editable);
1125
1126       //'on' is not supported in angular 1.0.8
1127       editable.elem.bind('$destroy', angular.bind(this, this.$removeEditable, editable));
1128
1129       //bind editable's local $form to self (if not bound yet, below form) 
1130       if (!editable.scope.$form) {
1131         editable.scope.$form = this;
1132       }
1133
1134       //if form already shown - call show() of new editable
1135       if (this.$visible) {
1136         editable.catchError(editable.show());
1137       }
1138       editable.catchError(editable.setWaiting(this.$waiting));
1139     },
1140
1141     $removeEditable: function(editable) {
1142       //arrayRemove
1143       for(var i=0; i < this.$editables.length; i++) {
1144         if(this.$editables[i] === editable) {
1145           this.$editables.splice(i, 1);
1146           return;
1147         }
1148       }
1149     },
1150
1151     /**
1152      * Shows form with editable controls.
1153      * 
1154      * @method $show()
1155      * @memberOf editable-form
1156      */
1157     $show: function() {
1158       if (this.$visible) {
1159         return;
1160       }
1161
1162       this.$visible = true;
1163
1164       var pc = editablePromiseCollection();
1165
1166       //own show
1167       pc.when(this.$onshow());
1168
1169       //clear errors
1170       this.$setError(null, '');
1171
1172       //children show
1173       angular.forEach(this.$editables, function(editable) {
1174         pc.when(editable.show());
1175       });
1176
1177       //wait promises and activate
1178       pc.then({
1179         onWait: angular.bind(this, this.$setWaiting), 
1180         onTrue: angular.bind(this, this.$activate), 
1181         onFalse: angular.bind(this, this.$activate), 
1182         onString: angular.bind(this, this.$activate)
1183       });
1184
1185       // add to internal list of shown forms
1186       // setTimeout needed to prevent closing right after opening (e.g. when trigger by button)
1187       setTimeout(angular.bind(this, function() {
1188         // clear `clicked` to get ready for clicks on visible form
1189         this._clicked = false;
1190         if(editableUtils.indexOf(shown, this) === -1) {
1191           shown.push(this);
1192         }
1193       }), 0);      
1194     },
1195
1196     /**
1197      * Sets focus on form field specified by `name`.
1198      * 
1199      * @method $activate(name)
1200      * @param {string} name name of field
1201      * @memberOf editable-form
1202      */
1203     $activate: function(name) {
1204       var i;
1205       if (this.$editables.length) {
1206         //activate by name
1207         if (angular.isString(name)) {
1208           for(i=0; i<this.$editables.length; i++) {
1209             if (this.$editables[i].name === name) {
1210               this.$editables[i].activate();
1211               return;
1212             }
1213           }
1214         }
1215
1216         //try activate error field
1217         for(i=0; i<this.$editables.length; i++) {
1218           if (this.$editables[i].error) {
1219             this.$editables[i].activate();
1220             return;
1221           }
1222         }
1223
1224         //by default activate first field
1225         this.$editables[0].activate(this.$editables[0].elem[0].selectionStart, this.$editables[0].elem[0].selectionEnd);
1226       }
1227     },
1228
1229     /**
1230      * Hides form with editable controls without saving.
1231      * 
1232      * @method $hide()
1233      * @memberOf editable-form
1234      */
1235     $hide: function() {
1236       if (!this.$visible) {
1237         return;
1238       }      
1239       this.$visible = false;
1240       // self hide
1241       this.$onhide();
1242       // children's hide
1243       angular.forEach(this.$editables, function(editable) {
1244         editable.hide();
1245       });
1246
1247       // remove from internal list of shown forms
1248       editableUtils.arrayRemove(shown, this);
1249     },
1250
1251     /**
1252      * Triggers `oncancel` event and calls `$hide()`.
1253      * 
1254      * @method $cancel()
1255      * @memberOf editable-form
1256      */
1257     $cancel: function() {
1258       if (!this.$visible) {
1259         return;
1260       }      
1261       // self cancel
1262       this.$oncancel();
1263       // children's cancel      
1264       angular.forEach(this.$editables, function(editable) {
1265         editable.cancel();
1266       });
1267       // self hide
1268       this.$hide();
1269     },    
1270
1271     $setWaiting: function(value) {
1272       this.$waiting = !!value;
1273       // we can't just set $waiting variable and use it via ng-disabled in children
1274       // because in editable-row form is not accessible
1275       angular.forEach(this.$editables, function(editable) {
1276         editable.setWaiting(!!value);
1277       });
1278     },
1279
1280     /**
1281      * Shows error message for particular field.
1282      * 
1283      * @method $setError(name, msg)
1284      * @param {string} name name of field
1285      * @param {string} msg error message
1286      * @memberOf editable-form
1287      */
1288     $setError: function(name, msg) {
1289       angular.forEach(this.$editables, function(editable) {
1290         if(!name || editable.name === name) {
1291           editable.setError(msg);
1292         }
1293       });
1294     },
1295
1296     $submit: function() {
1297       if (this.$waiting) {
1298         return;
1299       } 
1300
1301       //clear errors
1302       this.$setError(null, '');
1303
1304       //children onbeforesave
1305       var pc = editablePromiseCollection();
1306       angular.forEach(this.$editables, function(editable) {
1307         pc.when(editable.onbeforesave());
1308       });
1309
1310       /*
1311       onbeforesave result:
1312       - true/undefined: save data and close form
1313       - false: close form without saving
1314       - string: keep form open and show error
1315       */
1316       pc.then({
1317         onWait: angular.bind(this, this.$setWaiting), 
1318         onTrue: angular.bind(this, checkSelf, true), 
1319         onFalse: angular.bind(this, checkSelf, false), 
1320         onString: angular.bind(this, this.$activate)
1321       });
1322
1323       //save
1324       function checkSelf(childrenTrue){
1325         var pc = editablePromiseCollection();
1326         pc.when(this.$onbeforesave());
1327         pc.then({
1328           onWait: angular.bind(this, this.$setWaiting), 
1329           onTrue: childrenTrue ? angular.bind(this, this.$save) : angular.bind(this, this.$hide), 
1330           onFalse: angular.bind(this, this.$hide), 
1331           onString: angular.bind(this, this.$activate)
1332         });
1333       }
1334     },
1335
1336     $save: function() {
1337       // write model for each editable
1338       angular.forEach(this.$editables, function(editable) {
1339         editable.save();
1340       });
1341
1342       //call onaftersave of self and children
1343       var pc = editablePromiseCollection();
1344       pc.when(this.$onaftersave());
1345       angular.forEach(this.$editables, function(editable) {
1346         pc.when(editable.onaftersave());
1347       });
1348
1349       /*
1350       onaftersave result:
1351       - true/undefined/false: just close form
1352       - string: keep form open and show error
1353       */
1354       pc.then({
1355         onWait: angular.bind(this, this.$setWaiting), 
1356         onTrue: angular.bind(this, this.$hide), 
1357         onFalse: angular.bind(this, this.$hide), 
1358         onString: angular.bind(this, this.$activate)
1359       });
1360     },
1361
1362     $onshow: angular.noop,
1363     $oncancel: angular.noop,
1364     $onhide: angular.noop,
1365     $onbeforesave: angular.noop,
1366     $onaftersave: angular.noop
1367   };
1368
1369   return function() {
1370     return angular.extend({
1371       $editables: [],
1372       /**
1373        * Form visibility flag.
1374        * 
1375        * @var {bool} $visible
1376        * @memberOf editable-form
1377        */
1378       $visible: false,
1379       /**
1380        * Form waiting flag. It becomes `true` when form is loading or saving data.
1381        * 
1382        * @var {bool} $waiting
1383        * @memberOf editable-form
1384        */
1385       $waiting: false,
1386       $data: {},
1387       _clicked: false,
1388       _blur: null
1389     }, base);
1390   };
1391 }]);
1392
1393 /**
1394  * EditableForm directive. Should be defined in <form> containing editable controls.  
1395  * It add some usefull methods to form variable exposed to scope by `name="myform"` attribute.
1396  *
1397  * @namespace editable-form
1398  */
1399 angular.module('xeditable').directive('editableForm',
1400   ['$rootScope', '$parse', 'editableFormController', 'editableOptions',
1401   function($rootScope, $parse, editableFormController, editableOptions) {
1402     return {
1403       restrict: 'A',
1404       require: ['form'],
1405       //require: ['form', 'editableForm'],
1406       //controller: EditableFormController,
1407       compile: function() {
1408         return {
1409           pre: function(scope, elem, attrs, ctrl) {
1410             var form = ctrl[0];
1411             var eForm;
1412
1413             //if `editableForm` has value - publish smartly under this value
1414             //this is required only for single editor form that is created and removed
1415             if(attrs.editableForm) {
1416               if(scope[attrs.editableForm] && scope[attrs.editableForm].$show) {
1417                 eForm = scope[attrs.editableForm];
1418                 angular.extend(form, eForm);
1419               } else {
1420                 eForm = editableFormController();
1421                 scope[attrs.editableForm] = eForm;
1422                 angular.extend(eForm, form);
1423               }
1424             } else { //just merge to form and publish if form has name
1425               eForm = editableFormController();
1426               angular.extend(form, eForm);
1427             }
1428
1429             //read editables from buffer (that appeared before FORM tag)
1430             var buf = $rootScope.$$editableBuffer;
1431             var name = form.$name;
1432             if(name && buf && buf[name]) {
1433               angular.forEach(buf[name], function(editable) {
1434                 eForm.$addEditable(editable);
1435               });
1436               delete buf[name];
1437             }
1438           },
1439           post: function(scope, elem, attrs, ctrl) {
1440             var eForm;
1441
1442             if(attrs.editableForm && scope[attrs.editableForm] && scope[attrs.editableForm].$show) {
1443               eForm = scope[attrs.editableForm];
1444             } else {
1445               eForm = ctrl[0];
1446             }
1447
1448             /**
1449              * Called when form is shown.
1450              * 
1451              * @var {method|attribute} onshow 
1452              * @memberOf editable-form
1453              */
1454             if(attrs.onshow) {
1455               eForm.$onshow = angular.bind(eForm, $parse(attrs.onshow), scope);
1456             }
1457
1458             /**
1459              * Called when form hides after both save or cancel.
1460              * 
1461              * @var {method|attribute} onhide 
1462              * @memberOf editable-form
1463              */
1464             if(attrs.onhide) {
1465               eForm.$onhide = angular.bind(eForm, $parse(attrs.onhide), scope);
1466             }
1467
1468             /**
1469              * Called when form is cancelled.
1470              * 
1471              * @var {method|attribute} oncancel
1472              * @memberOf editable-form
1473              */
1474             if(attrs.oncancel) {
1475               eForm.$oncancel = angular.bind(eForm, $parse(attrs.oncancel), scope);
1476             }
1477
1478             /**
1479              * Whether form initially rendered in shown state.
1480              *
1481              * @var {bool|attribute} shown
1482              * @memberOf editable-form
1483              */
1484             if(attrs.shown && $parse(attrs.shown)(scope)) {
1485               eForm.$show();
1486             }
1487
1488             /**
1489              * Action when form losses focus. Values: `cancel|submit|ignore`.
1490              * Default is `ignore`.
1491              * 
1492              * @var {string|attribute} blur
1493              * @memberOf editable-form
1494              */
1495             eForm._blur = attrs.blur || editableOptions.blurForm;
1496
1497             // onbeforesave, onaftersave
1498             if(!attrs.ngSubmit && !attrs.submit) {
1499               /**
1500                * Called after all children `onbeforesave` callbacks but before saving form values
1501                * to model.  
1502                * If at least one children callback returns `non-string` - it will not not be called.  
1503                * See [editable-form demo](#editable-form) for details.
1504                * 
1505                * @var {method|attribute} onbeforesave
1506                * @memberOf editable-form
1507                * 
1508                */
1509               if(attrs.onbeforesave) {
1510                 eForm.$onbeforesave = function() {
1511                   return $parse(attrs.onbeforesave)(scope, {$data: eForm.$data});
1512                 };
1513               }
1514
1515               /**
1516                * Called when form values are saved to model.  
1517                * See [editable-form demo](#editable-form) for details.
1518                * 
1519                * @var {method|attribute} onaftersave 
1520                * @memberOf editable-form
1521                * 
1522                */
1523               if(attrs.onaftersave) {
1524                 eForm.$onaftersave = function() {
1525                   return $parse(attrs.onaftersave)(scope, {$data: eForm.$data});
1526                 };
1527               }
1528
1529               elem.bind('submit', function(event) {
1530                 event.preventDefault();
1531                 scope.$apply(function() {
1532                   eForm.$submit();
1533                 });
1534               });
1535             }
1536
1537
1538             // click - mark form as clicked to exclude in document click handler
1539             elem.bind('click', function(e) {
1540               // ignore right/middle button click
1541               if (e.which && e.which !== 1) {
1542                 return;
1543               }
1544
1545               if (eForm.$visible) {
1546                 eForm._clicked = true;
1547               }
1548             });   
1549
1550           }
1551         };
1552       }
1553     };
1554 }]);
1555 /**
1556  * editablePromiseCollection
1557  *  
1558  * Collect results of function calls. Shows waiting if there are promises. 
1559  * Finally, applies callbacks if:
1560  * - onTrue(): all results are true and all promises resolved to true
1561  * - onFalse(): at least one result is false or promise resolved to false
1562  * - onString(): at least one result is string or promise rejected or promise resolved to string
1563  */
1564
1565 angular.module('xeditable').factory('editablePromiseCollection', ['$q', function($q) { 
1566
1567   function promiseCollection() {
1568     return {
1569       promises: [],
1570       hasFalse: false,
1571       hasString: false,
1572       when: function(result, noPromise) {
1573         if (result === false) {
1574           this.hasFalse = true;
1575         } else if (!noPromise && angular.isObject(result)) {
1576           this.promises.push($q.when(result));
1577         } else if (angular.isString(result)){
1578           this.hasString = true;
1579         } else { //result === true || result === undefined || result === null
1580           return;
1581         }
1582       },
1583       //callbacks: onTrue, onFalse, onString
1584       then: function(callbacks) {
1585         callbacks = callbacks || {};
1586         var onTrue = callbacks.onTrue || angular.noop;
1587         var onFalse = callbacks.onFalse || angular.noop;
1588         var onString = callbacks.onString || angular.noop;
1589         var onWait = callbacks.onWait || angular.noop;
1590
1591         var self = this;
1592
1593         if (this.promises.length) {
1594           onWait(true);
1595           $q.all(this.promises).then(
1596             //all resolved       
1597             function(results) {
1598               onWait(false);
1599               //check all results via same `when` method (without checking promises)
1600               angular.forEach(results, function(result) {
1601                 self.when(result, true);  
1602               });
1603               applyCallback();
1604             },
1605             //some rejected
1606             function(error) { 
1607               onWait(false);
1608               onString();
1609             }
1610             );
1611         } else {
1612           applyCallback();
1613         }
1614
1615         function applyCallback() {
1616           if (!self.hasString && !self.hasFalse) {
1617             onTrue();
1618           } else if (!self.hasString && self.hasFalse) {
1619             onFalse();
1620           } else {
1621             onString();
1622           }
1623         }
1624
1625       }
1626     };
1627   }
1628
1629   return promiseCollection;
1630
1631 }]);
1632
1633 /**
1634  * editableUtils
1635  */
1636  angular.module('xeditable').factory('editableUtils', [function() {
1637   return {
1638     indexOf: function (array, obj) {
1639       if (array.indexOf) return array.indexOf(obj);
1640
1641       for ( var i = 0; i < array.length; i++) {
1642         if (obj === array[i]) return i;
1643       }
1644       return -1;
1645     },
1646
1647     arrayRemove: function (array, value) {
1648       var index = this.indexOf(array, value);
1649       if (index >= 0) {
1650         array.splice(index, 1);
1651       }
1652       return value;
1653     },
1654
1655     // copy from https://github.com/angular/angular.js/blob/master/src/Angular.js
1656     camelToDash: function(str) {
1657       var SNAKE_CASE_REGEXP = /[A-Z]/g;
1658       return str.replace(SNAKE_CASE_REGEXP, function(letter, pos) {
1659         return (pos ? '-' : '') + letter.toLowerCase();
1660       });
1661     },
1662
1663     dashToCamel: function(str) {
1664       var SPECIAL_CHARS_REGEXP = /([\:\-\_]+(.))/g;
1665       var MOZ_HACK_REGEXP = /^moz([A-Z])/;
1666       return str.
1667       replace(SPECIAL_CHARS_REGEXP, function(_, separator, letter, offset) {
1668         return offset ? letter.toUpperCase() : letter;
1669       }).
1670       replace(MOZ_HACK_REGEXP, 'Moz$1');
1671     }
1672   };
1673 }]);
1674
1675 /**
1676  * editableNgOptionsParser
1677  *
1678  * see: https://github.com/angular/angular.js/blob/master/src/ng/directive/select.js#L131
1679  */
1680  angular.module('xeditable').factory('editableNgOptionsParser', [function() {
1681   //0000111110000000000022220000000000000000000000333300000000000000444444444444444000000000555555555555555000000066666666666666600000000000000007777000000000000000000088888
1682   var NG_OPTIONS_REGEXP = /^\s*(.*?)(?:\s+as\s+(.*?))?(?:\s+group\s+by\s+(.*))?\s+for\s+(?:([\$\w][\$\w]*)|(?:\(\s*([\$\w][\$\w]*)\s*,\s*([\$\w][\$\w]*)\s*\)))\s+in\s+(.*?)(?:\s+track\s+by\s+(.*?))?$/;
1683
1684   function parser(optionsExp) {
1685     var match;
1686
1687     if (! (match = optionsExp.match(NG_OPTIONS_REGEXP))) {
1688       throw 'ng-options parse error';
1689     }
1690
1691     var 
1692     displayFn = match[2] || match[1],
1693     valueName = match[4] || match[6],
1694     keyName = match[5],
1695     groupByFn = match[3] || '',
1696     valueFn = match[2] ? match[1] : valueName,
1697     valuesFn = match[7],
1698     track = match[8],
1699     trackFn = track ? match[8] : null;
1700
1701     var ngRepeat;
1702     if (keyName === undefined) { // array
1703       ngRepeat = valueName + ' in ' + valuesFn;
1704       if (track !== undefined) {
1705         ngRepeat += ' track by '+trackFn;
1706       }
1707     } else { // object
1708       ngRepeat = '('+keyName+', '+valueName+') in '+valuesFn;
1709     }
1710     
1711     // group not supported yet
1712     return {
1713       ngRepeat: ngRepeat,
1714       locals: {
1715         valueName: valueName,
1716         keyName: keyName,
1717         valueFn: valueFn,
1718         displayFn: displayFn
1719       }
1720     };
1721   }
1722
1723   return parser;
1724 }]);
1725
1726 /**
1727  * editableCombodate
1728  *
1729  * angular version of https://github.com/vitalets/combodate
1730  */
1731 angular.module('xeditable').factory('editableCombodate', [function() {
1732   function Combodate(element, options) {
1733     this.$element = angular.element(element);
1734
1735     if(this.$element[0].nodeName != 'INPUT') {
1736       throw 'Combodate should be applied to INPUT element';
1737     }
1738
1739     var currentYear = new Date().getFullYear();
1740     this.defaults = {
1741       //in this format value stored in original input
1742       format: 'YYYY-MM-DD HH:mm',
1743       //in this format items in dropdowns are displayed
1744       template: 'D / MMM / YYYY   H : mm',
1745       //initial value, can be `new Date()`
1746       value: null,
1747       minYear: 1970,
1748       maxYear: currentYear,
1749       yearDescending: true,
1750       minuteStep: 5,
1751       secondStep: 1,
1752       firstItem: 'empty', //'name', 'empty', 'none'
1753       errorClass: null,
1754       customClass: '',
1755       roundTime: true, // whether to round minutes and seconds if step > 1
1756       smartDays: true // whether days in combo depend on selected month: 31, 30, 28
1757     };
1758
1759     this.options = angular.extend({}, this.defaults, options);
1760     this.init();
1761   }
1762
1763   Combodate.prototype = {
1764     constructor: Combodate,
1765     init: function () {
1766       this.map = {
1767         //key   regexp    moment.method
1768         day:    ['D',    'date'], 
1769         month:  ['M',    'month'], 
1770         year:   ['Y',    'year'], 
1771         hour:   ['[Hh]', 'hours'],
1772         minute: ['m',    'minutes'], 
1773         second: ['s',    'seconds'],
1774         ampm:   ['[Aa]', ''] 
1775       };
1776       
1777       this.$widget = angular.element('<span class="combodate"></span>').html(this.getTemplate());
1778       
1779       this.initCombos();
1780       
1781       if (this.options.smartDays) {
1782         var combo = this;
1783         this.$widget.find('select').bind('change', function(e) {
1784           // update days count if month or year changes
1785           if (angular.element(e.target).hasClass('month') || angular.element(e.target).hasClass('year')) {
1786             combo.fillCombo('day');
1787           }
1788         });        
1789       }
1790
1791       this.$widget.find('select').css('width', 'auto');
1792
1793       // hide original input and insert widget                                       
1794       this.$element.css('display', 'none').after(this.$widget);
1795       
1796       // set initial value
1797       this.setValue(this.$element.val() || this.options.value);
1798     },
1799     
1800     /*
1801      Replace tokens in template with <select> elements 
1802      */         
1803      getTemplate: function() {
1804       var tpl = this.options.template;
1805       var customClass = this.options.customClass;
1806
1807       //first pass
1808       angular.forEach(this.map, function(v, k) {
1809         v = v[0]; 
1810         var r = new RegExp(v+'+');
1811         var token = v.length > 1 ? v.substring(1, 2) : v;
1812         
1813         tpl = tpl.replace(r, '{'+token+'}');
1814       });
1815
1816       //replace spaces with &nbsp;
1817       tpl = tpl.replace(/ /g, '&nbsp;');
1818
1819       //second pass
1820       angular.forEach(this.map, function(v, k) {
1821         v = v[0];
1822         var token = v.length > 1 ? v.substring(1, 2) : v;
1823
1824         tpl = tpl.replace('{'+token+'}', '<select class="'+k+' '+customClass+'"></select>');
1825       });   
1826
1827       return tpl;
1828     },
1829     
1830     /*
1831      Initialize combos that presents in template 
1832      */        
1833      initCombos: function() {
1834       for (var k in this.map) {
1835         var c = this.$widget[0].querySelectorAll('.'+k);
1836         // set properties like this.$day, this.$month etc.
1837         this['$'+k] = c.length ? angular.element(c) : null;
1838         // fill with items
1839         this.fillCombo(k);
1840       }
1841     },
1842
1843     /*
1844      Fill combo with items 
1845      */        
1846      fillCombo: function(k) {
1847       var $combo = this['$'+k];
1848       if (!$combo) {
1849         return;
1850       }
1851
1852       // define method name to fill items, e.g `fillDays`
1853       var f = 'fill' + k.charAt(0).toUpperCase() + k.slice(1); 
1854       var items = this[f]();
1855       var value = $combo.val();
1856
1857       $combo.html('');
1858       for(var i=0; i<items.length; i++) {
1859         $combo.append('<option value="'+items[i][0]+'">'+items[i][1]+'</option>');
1860       }
1861
1862       $combo.val(value);
1863     },
1864
1865     /*
1866      Initialize items of combos. Handles `firstItem` option 
1867      */
1868      fillCommon: function(key) {
1869       var values = [], relTime;
1870
1871       if(this.options.firstItem === 'name') {
1872         //need both to support moment ver < 2 and  >= 2
1873         relTime = moment.relativeTime || moment.langData()._relativeTime; 
1874         var header = typeof relTime[key] === 'function' ? relTime[key](1, true, key, false) : relTime[key];
1875         //take last entry (see momentjs lang files structure) 
1876         header = header.split(' ').reverse()[0];                
1877         values.push(['', header]);
1878       } else if(this.options.firstItem === 'empty') {
1879         values.push(['', '']);
1880       }
1881       return values;
1882     },  
1883
1884
1885     /*
1886     fill day
1887     */
1888     fillDay: function() {
1889       var items = this.fillCommon('d'), name, i,
1890       twoDigit = this.options.template.indexOf('DD') !== -1,
1891       daysCount = 31;
1892
1893       // detect days count (depends on month and year)
1894       // originally https://github.com/vitalets/combodate/pull/7
1895       if (this.options.smartDays && this.$month && this.$year) {
1896         var month = parseInt(this.$month.val(), 10);
1897         var year = parseInt(this.$year.val(), 10);
1898
1899         if (!isNaN(month) && !isNaN(year)) {
1900           daysCount = moment([year, month]).daysInMonth();
1901         }
1902       }
1903
1904       for (i = 1; i <= daysCount; i++) {
1905         name = twoDigit ? this.leadZero(i) : i;
1906         items.push([i, name]);
1907       }
1908       return items;
1909     },
1910     
1911     /*
1912     fill month
1913     */
1914     fillMonth: function() {
1915       var items = this.fillCommon('M'), name, i, 
1916       longNames = this.options.template.indexOf('MMMM') !== -1,
1917       shortNames = this.options.template.indexOf('MMM') !== -1,
1918       twoDigit = this.options.template.indexOf('MM') !== -1;
1919
1920       for(i=0; i<=11; i++) {
1921         if(longNames) {
1922           //see https://github.com/timrwood/momentjs.com/pull/36
1923           name = moment().date(1).month(i).format('MMMM');
1924         } else if(shortNames) {
1925           name = moment().date(1).month(i).format('MMM');
1926         } else if(twoDigit) {
1927           name = this.leadZero(i+1);
1928         } else {
1929           name = i+1;
1930         }
1931         items.push([i, name]);
1932       } 
1933       return items;
1934     },
1935     
1936     /*
1937     fill year
1938     */
1939     fillYear: function() {
1940       var items = [], name, i, 
1941       longNames = this.options.template.indexOf('YYYY') !== -1;
1942
1943       for(i=this.options.maxYear; i>=this.options.minYear; i--) {
1944         name = longNames ? i : (i+'').substring(2);
1945         items[this.options.yearDescending ? 'push' : 'unshift']([i, name]);
1946       }
1947       
1948       items = this.fillCommon('y').concat(items);
1949       
1950       return items;
1951     },
1952     
1953     /*
1954     fill hour
1955     */
1956     fillHour: function() {
1957       var items = this.fillCommon('h'), name, i,
1958       h12 = this.options.template.indexOf('h') !== -1,
1959       h24 = this.options.template.indexOf('H') !== -1,
1960       twoDigit = this.options.template.toLowerCase().indexOf('hh') !== -1,
1961       min = h12 ? 1 : 0, 
1962       max = h12 ? 12 : 23;
1963
1964       for(i=min; i<=max; i++) {
1965         name = twoDigit ? this.leadZero(i) : i;
1966         items.push([i, name]);
1967       } 
1968       return items;
1969     },
1970
1971     /*
1972     fill minute
1973     */
1974     fillMinute: function() {
1975       var items = this.fillCommon('m'), name, i,
1976       twoDigit = this.options.template.indexOf('mm') !== -1;
1977
1978       for(i=0; i<=59; i+= this.options.minuteStep) {
1979         name = twoDigit ? this.leadZero(i) : i;
1980         items.push([i, name]);
1981       }
1982       return items;
1983     },
1984     
1985     /*
1986     fill second
1987     */
1988     fillSecond: function() {
1989       var items = this.fillCommon('s'), name, i,
1990       twoDigit = this.options.template.indexOf('ss') !== -1;
1991
1992       for(i=0; i<=59; i+= this.options.secondStep) {
1993         name = twoDigit ? this.leadZero(i) : i;
1994         items.push([i, name]);
1995       }    
1996       return items;
1997     },
1998     
1999     /*
2000     fill ampm
2001     */
2002     fillAmpm: function() {
2003       var ampmL = this.options.template.indexOf('a') !== -1,
2004       ampmU = this.options.template.indexOf('A') !== -1,            
2005       items = [
2006       ['am', ampmL ? 'am' : 'AM'],
2007       ['pm', ampmL ? 'pm' : 'PM']
2008       ];
2009       return items;
2010     },
2011
2012     /*
2013      Returns current date value from combos. 
2014      If format not specified - `options.format` used.
2015      If format = `null` - Moment object returned.
2016      */
2017      getValue: function(format) {
2018       var dt, values = {}, 
2019       that = this,
2020       notSelected = false;
2021
2022       //getting selected values    
2023       angular.forEach(this.map, function(v, k) {
2024         if(k === 'ampm') {
2025           return;
2026         }
2027         var def = k === 'day' ? 1 : 0;
2028
2029         values[k] = that['$'+k] ? parseInt(that['$'+k].val(), 10) : def; 
2030         
2031         if(isNaN(values[k])) {
2032          notSelected = true;
2033          return false; 
2034        }
2035      });
2036       
2037       //if at least one visible combo not selected - return empty string
2038       if(notSelected) {
2039        return '';
2040      }
2041
2042       //convert hours 12h --> 24h 
2043       if(this.$ampm) {
2044         //12:00 pm --> 12:00 (24-h format, midday), 12:00 am --> 00:00 (24-h format, midnight, start of day)
2045         if(values.hour === 12) {
2046           values.hour = this.$ampm.val() === 'am' ? 0 : 12;                    
2047         } else {
2048           values.hour = this.$ampm.val() === 'am' ? values.hour : values.hour+12;
2049         }
2050       }
2051       
2052       dt = moment([values.year, values.month, values.day, values.hour, values.minute, values.second]);
2053       
2054       //highlight invalid date
2055       this.highlight(dt);
2056
2057       format = format === undefined ? this.options.format : format;
2058       if(format === null) {
2059        return dt.isValid() ? dt : null; 
2060      } else {
2061        return dt.isValid() ? dt.format(format) : ''; 
2062      }
2063    },
2064
2065    setValue: function(value) {
2066     if(!value) {
2067       return;
2068     }
2069
2070       // parse in strict mode (third param `true`)
2071       var dt = typeof value === 'string' ? moment(value, this.options.format, true) : moment(value),
2072       that = this,
2073       values = {};
2074       
2075       //function to find nearest value in select options
2076       function getNearest($select, value) {
2077         var delta = {};
2078         angular.forEach($select.children('option'), function(opt, i){
2079           var optValue = angular.element(opt).attr('value');
2080
2081           if(optValue === '') return;
2082           var distance = Math.abs(optValue - value); 
2083           if(typeof delta.distance === 'undefined' || distance < delta.distance) {
2084             delta = {value: optValue, distance: distance};
2085           } 
2086         }); 
2087         return delta.value;
2088       }
2089       
2090       if(dt.isValid()) {
2091         //read values from date object
2092         angular.forEach(this.map, function(v, k) {
2093           if(k === 'ampm') {
2094             return; 
2095           }
2096           values[k] = dt[v[1]]();
2097         });
2098
2099         if(this.$ampm) {
2100           //12:00 pm --> 12:00 (24-h format, midday), 12:00 am --> 00:00 (24-h format, midnight, start of day)
2101           if(values.hour >= 12) {
2102             values.ampm = 'pm';
2103             if(values.hour > 12) {
2104               values.hour -= 12;
2105             }
2106           } else {
2107             values.ampm = 'am';
2108             if(values.hour === 0) {
2109               values.hour = 12;
2110             }
2111           }
2112         }
2113
2114         angular.forEach(values, function(v, k) {
2115           //call val() for each existing combo, e.g. this.$hour.val()
2116           if(that['$'+k]) {
2117
2118             if(k === 'minute' && that.options.minuteStep > 1 && that.options.roundTime) {
2119              v = getNearest(that['$'+k], v);
2120            }
2121            
2122            if(k === 'second' && that.options.secondStep > 1 && that.options.roundTime) {
2123              v = getNearest(that['$'+k], v);
2124            }                       
2125            
2126            that['$'+k].val(v);
2127          }
2128        });
2129
2130         // update days count
2131         if (this.options.smartDays) {
2132           this.fillCombo('day');
2133         }
2134
2135         this.$element.val(dt.format(this.options.format)).triggerHandler('change');
2136       }
2137     },
2138     
2139     /*
2140      highlight combos if date is invalid
2141      */
2142      highlight: function(dt) {
2143       if(!dt.isValid()) {
2144         if(this.options.errorClass) {
2145           this.$widget.addClass(this.options.errorClass);
2146         } else {
2147           //store original border color
2148           if(!this.borderColor) {
2149             this.borderColor = this.$widget.find('select').css('border-color'); 
2150           }
2151           this.$widget.find('select').css('border-color', 'red');
2152         }
2153       } else {
2154         if(this.options.errorClass) {
2155           this.$widget.removeClass(this.options.errorClass);
2156         } else {
2157           this.$widget.find('select').css('border-color', this.borderColor);
2158         }  
2159       }
2160     },
2161     
2162     leadZero: function(v) {
2163       return v <= 9 ? '0' + v : v; 
2164     },
2165     
2166     destroy: function() {
2167       this.$widget.remove();
2168       this.$element.removeData('combodate').show();
2169     }
2170
2171   };
2172
2173   return {
2174     getInstance: function(element, options) {
2175       return new Combodate(element, options);
2176     }
2177   };
2178 }]);
2179
2180 /*
2181 Editable icons:
2182 - default
2183 - font-awesome
2184
2185 */
2186 angular.module('xeditable').factory('editableIcons', function() {
2187
2188   var icons = {
2189     //Icon-set to use, defaults to bootstrap icons
2190     default: {
2191       'bs2': {
2192         ok: 'icon-ok icon-white',
2193         cancel: 'icon-remove'
2194       },
2195       'bs3': {
2196         ok: 'glyphicon glyphicon-ok',
2197         cancel: 'glyphicon glyphicon-remove'
2198       }
2199     },
2200     external: {
2201       'font-awesome': {
2202         ok: 'fa fa-check',
2203         cancel: 'fa fa-times'
2204       }
2205     }
2206   };
2207
2208   return icons;
2209 });
2210
2211 /*
2212 Editable themes:
2213 - default
2214 - bootstrap 2
2215 - bootstrap 3
2216 - semantic-ui
2217
2218 Note: in postrender() `this` is instance of editableController
2219 */
2220 angular.module('xeditable').factory('editableThemes', function() {
2221   var themes = {
2222     //default
2223     'default': {
2224       formTpl:      '<form class="editable-wrap"></form>',
2225       noformTpl:    '<span class="editable-wrap"></span>',
2226       controlsTpl:  '<span class="editable-controls"></span>',
2227       inputTpl:     '',
2228       errorTpl:     '<div class="editable-error" ng-show="$error" ng-bind="$error"></div>',
2229       buttonsTpl:   '<span class="editable-buttons"></span>',
2230       submitTpl:    '<button type="submit">save</button>',
2231       cancelTpl:    '<button type="button" ng-click="$form.$cancel()">cancel</button>'
2232     },
2233
2234     //bs2
2235     'bs2': {
2236       formTpl:     '<form class="form-inline editable-wrap" role="form"></form>',
2237       noformTpl:   '<span class="editable-wrap"></span>',
2238       controlsTpl: '<div class="editable-controls controls control-group" ng-class="{\'error\': $error}"></div>',
2239       inputTpl:    '',
2240       errorTpl:    '<div class="editable-error help-block" ng-show="$error" ng-bind="$error"></div>',
2241       buttonsTpl:  '<span class="editable-buttons"></span>',
2242       submitTpl:   '<button type="submit" class="btn btn-primary"><span></span></button>',
2243       cancelTpl:   '<button type="button" class="btn" ng-click="$form.$cancel()">'+
2244                       '<span></span>'+
2245                    '</button>'
2246
2247     },
2248
2249     //bs3
2250     'bs3': {
2251       formTpl:     '<form class="form-inline editable-wrap" role="form"></form>',
2252       noformTpl:   '<span class="editable-wrap"></span>',
2253       controlsTpl: '<div class="editable-controls form-group" ng-class="{\'has-error\': $error}"></div>',
2254       inputTpl:    '',
2255       errorTpl:    '<div class="editable-error help-block" ng-show="$error" ng-bind="$error"></div>',
2256       buttonsTpl:  '<span class="editable-buttons"></span>',
2257       submitTpl:   '<button type="submit" class="btn btn-primary"><span></span></button>',
2258       cancelTpl:   '<button type="button" class="btn btn-default" ng-click="$form.$cancel()">'+
2259                      '<span></span>'+
2260                    '</button>',
2261
2262       //bs3 specific prop to change buttons class: btn-sm, btn-lg
2263       buttonsClass: '',
2264       //bs3 specific prop to change standard inputs class: input-sm, input-lg
2265       inputClass: '',
2266       postrender: function() {
2267         //apply `form-control` class to std inputs
2268         switch(this.directiveName) {
2269           case 'editableText':
2270           case 'editableSelect':
2271           case 'editableTextarea':
2272           case 'editableEmail':
2273           case 'editableTel':
2274           case 'editableNumber':
2275           case 'editableUrl':
2276           case 'editableSearch':
2277           case 'editableDate':
2278           case 'editableDatetime':
2279           case 'editableBsdate':
2280           case 'editableTime':
2281           case 'editableMonth':
2282           case 'editableWeek':
2283           case 'editablePassword':
2284             this.inputEl.addClass('form-control');
2285             if(this.theme.inputClass) {
2286               // don`t apply `input-sm` and `input-lg` to select multiple
2287               // should be fixed in bs itself!
2288               if(this.inputEl.attr('multiple') &&
2289                 (this.theme.inputClass === 'input-sm' || this.theme.inputClass === 'input-lg')) {
2290                   break;
2291               }
2292               this.inputEl.addClass(this.theme.inputClass);
2293             }
2294           break;
2295           case 'editableCheckbox':
2296               this.editorEl.addClass('checkbox');
2297         }
2298
2299         //apply buttonsClass (bs3 specific!)
2300         if(this.buttonsEl && this.theme.buttonsClass) {
2301           this.buttonsEl.find('button').addClass(this.theme.buttonsClass);
2302         }
2303       }
2304     },
2305     
2306     //semantic-ui
2307     'semantic': {
2308       formTpl:     '<form class="editable-wrap ui form" ng-class="{\'error\': $error}" role="form"></form>',
2309       noformTpl:   '<span class="editable-wrap"></span>',
2310       controlsTpl: '<div class="editable-controls ui fluid input" ng-class="{\'error\': $error}"></div>',
2311       inputTpl:    '',
2312       errorTpl:    '<div class="editable-error ui error message" ng-show="$error" ng-bind="$error"></div>',
2313       buttonsTpl:  '<span class="mini ui buttons"></span>',
2314       submitTpl:   '<button type="submit" class="ui primary button"><i class="ui check icon"></i></button>',
2315       cancelTpl:   '<button type="button" class="ui button" ng-click="$form.$cancel()">'+
2316                       '<i class="ui cancel icon"></i>'+
2317                    '</button>'
2318     }
2319   };
2320
2321   return themes;
2322 });