cmsify 0.0.1

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