cmsify 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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
+ });