Razique Mahroua
2019-11-28 1759c24ad2d2b35ec5c756e3dd3a60185fe944b7
commit | author | age
1759c2 1 /**
RM 2  * angular-strap
3  * @version v2.0.3 - 2014-05-30
4  * @link http://mgcrea.github.io/angular-strap
5  * @author Olivier Louvignes (olivier@mg-crea.com)
6  * @license MIT License, http://www.opensource.org/licenses/MIT
7  */
8 'use strict';
9 angular.module('mgcrea.ngStrap.datepicker', [
10   'mgcrea.ngStrap.helpers.dateParser',
11   'mgcrea.ngStrap.tooltip'
12 ]).provider('$datepicker', function () {
13   var defaults = this.defaults = {
14       animation: 'am-fade',
15       prefixClass: 'datepicker',
16       placement: 'bottom-left',
17       template: 'datepicker/datepicker.tpl.html',
18       trigger: 'focus',
19       container: false,
20       keyboard: true,
21       html: false,
22       delay: 0,
23       useNative: false,
24       dateType: 'date',
25       dateFormat: 'shortDate',
26       modelDateFormat: null,
27       dayFormat: 'dd',
28       strictFormat: false,
29       autoclose: false,
30       minDate: -Infinity,
31       maxDate: +Infinity,
32       startView: 0,
33       minView: 0,
34       startWeek: 0,
35       iconLeft: 'glyphicon glyphicon-chevron-left',
36       iconRight: 'glyphicon glyphicon-chevron-right'
37     };
38   this.$get = [
39     '$window',
40     '$document',
41     '$rootScope',
42     '$sce',
43     '$locale',
44     'dateFilter',
45     'datepickerViews',
46     '$tooltip',
47     function ($window, $document, $rootScope, $sce, $locale, dateFilter, datepickerViews, $tooltip) {
48       var bodyEl = angular.element($window.document.body);
49       var isTouch = 'createTouch' in $window.document;
50       var isNative = /(ip(a|o)d|iphone|android)/gi.test($window.navigator.userAgent);
51       if (!defaults.lang)
52         defaults.lang = $locale.id;
53       function DatepickerFactory(element, controller, config) {
54         var $datepicker = $tooltip(element, angular.extend({}, defaults, config));
55         var parentScope = config.scope;
56         var options = $datepicker.$options;
57         var scope = $datepicker.$scope;
58         if (options.startView)
59           options.startView -= options.minView;
60         // View vars
61         var pickerViews = datepickerViews($datepicker);
62         $datepicker.$views = pickerViews.views;
63         var viewDate = pickerViews.viewDate;
64         scope.$mode = options.startView;
65         scope.$iconLeft = options.iconLeft;
66         scope.$iconRight = options.iconRight;
67         var $picker = $datepicker.$views[scope.$mode];
68         // Scope methods
69         scope.$select = function (date) {
70           $datepicker.select(date);
71         };
72         scope.$selectPane = function (value) {
73           $datepicker.$selectPane(value);
74         };
75         scope.$toggleMode = function () {
76           $datepicker.setMode((scope.$mode + 1) % $datepicker.$views.length);
77         };
78         // Public methods
79         $datepicker.update = function (date) {
80           // console.warn('$datepicker.update() newValue=%o', date);
81           if (angular.isDate(date) && !isNaN(date.getTime())) {
82             $datepicker.$date = date;
83             $picker.update.call($picker, date);
84           }
85           // Build only if pristine
86           $datepicker.$build(true);
87         };
88         $datepicker.select = function (date, keep) {
89           // console.warn('$datepicker.select', date, scope.$mode);
90           if (!angular.isDate(controller.$dateValue))
91             controller.$dateValue = new Date(date);
92           controller.$dateValue.setFullYear(date.getFullYear(), date.getMonth(), date.getDate());
93           if (!scope.$mode || keep) {
94             controller.$setViewValue(controller.$dateValue);
95             controller.$render();
96             if (options.autoclose && !keep) {
97               $datepicker.hide(true);
98             }
99           } else {
100             angular.extend(viewDate, {
101               year: date.getFullYear(),
102               month: date.getMonth(),
103               date: date.getDate()
104             });
105             $datepicker.setMode(scope.$mode - 1);
106             $datepicker.$build();
107           }
108         };
109         $datepicker.setMode = function (mode) {
110           // console.warn('$datepicker.setMode', mode);
111           scope.$mode = mode;
112           $picker = $datepicker.$views[scope.$mode];
113           $datepicker.$build();
114         };
115         // Protected methods
116         $datepicker.$build = function (pristine) {
117           // console.warn('$datepicker.$build() viewDate=%o', viewDate);
118           if (pristine === true && $picker.built)
119             return;
120           if (pristine === false && !$picker.built)
121             return;
122           $picker.build.call($picker);
123         };
124         $datepicker.$updateSelected = function () {
125           for (var i = 0, l = scope.rows.length; i < l; i++) {
126             angular.forEach(scope.rows[i], updateSelected);
127           }
128         };
129         $datepicker.$isSelected = function (date) {
130           return $picker.isSelected(date);
131         };
132         $datepicker.$selectPane = function (value) {
133           var steps = $picker.steps;
134           var targetDate = new Date(Date.UTC(viewDate.year + (steps.year || 0) * value, viewDate.month + (steps.month || 0) * value, viewDate.date + (steps.day || 0) * value));
135           angular.extend(viewDate, {
136             year: targetDate.getUTCFullYear(),
137             month: targetDate.getUTCMonth(),
138             date: targetDate.getUTCDate()
139           });
140           $datepicker.$build();
141         };
142         $datepicker.$onMouseDown = function (evt) {
143           // Prevent blur on mousedown on .dropdown-menu
144           evt.preventDefault();
145           evt.stopPropagation();
146           // Emulate click for mobile devices
147           if (isTouch) {
148             var targetEl = angular.element(evt.target);
149             if (targetEl[0].nodeName.toLowerCase() !== 'button') {
150               targetEl = targetEl.parent();
151             }
152             targetEl.triggerHandler('click');
153           }
154         };
155         $datepicker.$onKeyDown = function (evt) {
156           if (!/(38|37|39|40|13)/.test(evt.keyCode) || evt.shiftKey || evt.altKey)
157             return;
158           evt.preventDefault();
159           evt.stopPropagation();
160           if (evt.keyCode === 13) {
161             if (!scope.$mode) {
162               return $datepicker.hide(true);
163             } else {
164               return scope.$apply(function () {
165                 $datepicker.setMode(scope.$mode - 1);
166               });
167             }
168           }
169           // Navigate with keyboard
170           $picker.onKeyDown(evt);
171           parentScope.$digest();
172         };
173         // Private
174         function updateSelected(el) {
175           el.selected = $datepicker.$isSelected(el.date);
176         }
177         function focusElement() {
178           element[0].focus();
179         }
180         // Overrides
181         var _init = $datepicker.init;
182         $datepicker.init = function () {
183           if (isNative && options.useNative) {
184             element.prop('type', 'date');
185             element.css('-webkit-appearance', 'textfield');
186             return;
187           } else if (isTouch) {
188             element.prop('type', 'text');
189             element.attr('readonly', 'true');
190             element.on('click', focusElement);
191           }
192           _init();
193         };
194         var _destroy = $datepicker.destroy;
195         $datepicker.destroy = function () {
196           if (isNative && options.useNative) {
197             element.off('click', focusElement);
198           }
199           _destroy();
200         };
201         var _show = $datepicker.show;
202         $datepicker.show = function () {
203           _show();
204           setTimeout(function () {
205             $datepicker.$element.on(isTouch ? 'touchstart' : 'mousedown', $datepicker.$onMouseDown);
206             if (options.keyboard) {
207               element.on('keydown', $datepicker.$onKeyDown);
208             }
209           });
210         };
211         var _hide = $datepicker.hide;
212         $datepicker.hide = function (blur) {
213           $datepicker.$element.off(isTouch ? 'touchstart' : 'mousedown', $datepicker.$onMouseDown);
214           if (options.keyboard) {
215             element.off('keydown', $datepicker.$onKeyDown);
216           }
217           _hide(blur);
218         };
219         return $datepicker;
220       }
221       DatepickerFactory.defaults = defaults;
222       return DatepickerFactory;
223     }
224   ];
225 }).directive('bsDatepicker', [
226   '$window',
227   '$parse',
228   '$q',
229   '$locale',
230   'dateFilter',
231   '$datepicker',
232   '$dateParser',
233   '$timeout',
234   function ($window, $parse, $q, $locale, dateFilter, $datepicker, $dateParser, $timeout) {
235     var defaults = $datepicker.defaults;
236     var isNative = /(ip(a|o)d|iphone|android)/gi.test($window.navigator.userAgent);
237     var isNumeric = function (n) {
238       return !isNaN(parseFloat(n)) && isFinite(n);
239     };
240     return {
241       restrict: 'EAC',
242       require: 'ngModel',
243       link: function postLink(scope, element, attr, controller) {
244         // Directive options
245         var options = {
246             scope: scope,
247             controller: controller
248           };
249         angular.forEach([
250           'placement',
251           'container',
252           'delay',
253           'trigger',
254           'keyboard',
255           'html',
256           'animation',
257           'template',
258           'autoclose',
259           'dateType',
260           'dateFormat',
261           'modelDateFormat',
262           'dayFormat',
263           'strictFormat',
264           'startWeek',
265           'useNative',
266           'lang',
267           'startView',
268           'minView'
269         ], function (key) {
270           if (angular.isDefined(attr[key]))
271             options[key] = attr[key];
272         });
273         // Initialize datepicker
274         if (isNative && options.useNative)
275           options.dateFormat = 'yyyy-MM-dd';
276         var datepicker = $datepicker(element, controller, options);
277         options = datepicker.$options;
278         // Observe attributes for changes
279         angular.forEach([
280           'minDate',
281           'maxDate'
282         ], function (key) {
283           // console.warn('attr.$observe(%s)', key, attr[key]);
284           angular.isDefined(attr[key]) && attr.$observe(key, function (newValue) {
285             // console.warn('attr.$observe(%s)=%o', key, newValue);
286             if (newValue === 'today') {
287               var today = new Date();
288               datepicker.$options[key] = +new Date(today.getFullYear(), today.getMonth(), today.getDate() + (key === 'maxDate' ? 1 : 0), 0, 0, 0, key === 'minDate' ? 0 : -1);
289             } else if (angular.isString(newValue) && newValue.match(/^".+"$/)) {
290               // Support {{ dateObj }}
291               datepicker.$options[key] = +new Date(newValue.substr(1, newValue.length - 2));
292             } else if (isNumeric(newValue)) {
293               datepicker.$options[key] = +new Date(parseInt(newValue, 10));
294             } else {
295               datepicker.$options[key] = +new Date(newValue);
296             }
297             // Build only if dirty
298             !isNaN(datepicker.$options[key]) && datepicker.$build(false);
299           });
300         });
301         // Watch model for changes
302         scope.$watch(attr.ngModel, function (newValue, oldValue) {
303           datepicker.update(controller.$dateValue);
304         }, true);
305         var dateParser = $dateParser({
306             format: options.dateFormat,
307             lang: options.lang,
308             strict: options.strictFormat
309           });
310         // viewValue -> $parsers -> modelValue
311         controller.$parsers.unshift(function (viewValue) {
312           // console.warn('$parser("%s"): viewValue=%o', element.attr('ng-model'), viewValue);
313           // Null values should correctly reset the model value & validity
314           if (!viewValue) {
315             controller.$setValidity('date', true);
316             return;
317           }
318           var parsedDate = dateParser.parse(viewValue, controller.$dateValue);
319           if (!parsedDate || isNaN(parsedDate.getTime())) {
320             controller.$setValidity('date', false);
321             return;
322           } else {
323             var isMinValid = isNaN(datepicker.$options.minDate) || parsedDate.getTime() >= datepicker.$options.minDate;
324             var isMaxValid = isNaN(datepicker.$options.maxDate) || parsedDate.getTime() <= datepicker.$options.maxDate;
325             var isValid = isMinValid && isMaxValid;
326             controller.$setValidity('date', isValid);
327             controller.$setValidity('min', isMinValid);
328             controller.$setValidity('max', isMaxValid);
329             // Only update the model when we have a valid date
330             if (isValid)
331               controller.$dateValue = parsedDate;
332           }
333           if (options.dateType === 'string') {
334             return dateFilter(parsedDate, options.modelDateFormat || options.dateFormat);
335           } else if (options.dateType === 'number') {
336             return controller.$dateValue.getTime();
337           } else if (options.dateType === 'iso') {
338             return controller.$dateValue.toISOString();
339           } else {
340             return new Date(controller.$dateValue);
341           }
342         });
343         // modelValue -> $formatters -> viewValue
344         controller.$formatters.push(function (modelValue) {
345           // console.warn('$formatter("%s"): modelValue=%o (%o)', element.attr('ng-model'), modelValue, typeof modelValue);
346           var date;
347           if (angular.isUndefined(modelValue) || modelValue === null) {
348             date = NaN;
349           } else if (angular.isDate(modelValue)) {
350             date = modelValue;
351           } else if (options.dateType === 'string') {
352             date = dateParser.parse(modelValue, null, options.modelDateFormat);
353           } else {
354             date = new Date(modelValue);
355           }
356           // Setup default value?
357           // if(isNaN(date.getTime())) {
358           //   var today = new Date();
359           //   date = new Date(today.getFullYear(), today.getMonth(), today.getDate(), 0, 0, 0, 0);
360           // }
361           controller.$dateValue = date;
362           return controller.$dateValue;
363         });
364         // viewValue -> element
365         controller.$render = function () {
366           // console.warn('$render("%s"): viewValue=%o', element.attr('ng-model'), controller.$viewValue);
367           element.val(!controller.$dateValue || isNaN(controller.$dateValue.getTime()) ? '' : dateFilter(controller.$dateValue, options.dateFormat));
368         };
369         // Garbage collection
370         scope.$on('$destroy', function () {
371           datepicker.destroy();
372           options = null;
373           datepicker = null;
374         });
375       }
376     };
377   }
378 ]).provider('datepickerViews', function () {
379   var defaults = this.defaults = {
380       dayFormat: 'dd',
381       daySplit: 7
382     };
383   // Split array into smaller arrays
384   function split(arr, size) {
385     var arrays = [];
386     while (arr.length > 0) {
387       arrays.push(arr.splice(0, size));
388     }
389     return arrays;
390   }
391   // Modulus operator
392   function mod(n, m) {
393     return (n % m + m) % m;
394   }
395   this.$get = [
396     '$locale',
397     '$sce',
398     'dateFilter',
399     function ($locale, $sce, dateFilter) {
400       return function (picker) {
401         var scope = picker.$scope;
402         var options = picker.$options;
403         var weekDaysMin = $locale.DATETIME_FORMATS.SHORTDAY;
404         var weekDaysLabels = weekDaysMin.slice(options.startWeek).concat(weekDaysMin.slice(0, options.startWeek));
405         var weekDaysLabelsHtml = $sce.trustAsHtml('<th class="dow text-center">' + weekDaysLabels.join('</th><th class="dow text-center">') + '</th>');
406         var startDate = picker.$date || new Date();
407         var viewDate = {
408             year: startDate.getFullYear(),
409             month: startDate.getMonth(),
410             date: startDate.getDate()
411           };
412         var timezoneOffset = startDate.getTimezoneOffset() * 60000;
413         var views = [
414             {
415               format: options.dayFormat,
416               split: 7,
417               steps: { month: 1 },
418               update: function (date, force) {
419                 if (!this.built || force || date.getFullYear() !== viewDate.year || date.getMonth() !== viewDate.month) {
420                   angular.extend(viewDate, {
421                     year: picker.$date.getFullYear(),
422                     month: picker.$date.getMonth(),
423                     date: picker.$date.getDate()
424                   });
425                   picker.$build();
426                 } else if (date.getDate() !== viewDate.date) {
427                   viewDate.date = picker.$date.getDate();
428                   picker.$updateSelected();
429                 }
430               },
431               build: function () {
432                 var firstDayOfMonth = new Date(viewDate.year, viewDate.month, 1), firstDayOfMonthOffset = firstDayOfMonth.getTimezoneOffset();
433                 var firstDate = new Date(+firstDayOfMonth - mod(firstDayOfMonth.getDay() - options.startWeek, 7) * 86400000), firstDateOffset = firstDate.getTimezoneOffset();
434                 // Handle daylight time switch
435                 if (firstDateOffset !== firstDayOfMonthOffset)
436                   firstDate = new Date(+firstDate + (firstDateOffset - firstDayOfMonthOffset) * 60000);
437                 var days = [], day;
438                 for (var i = 0; i < 42; i++) {
439                   // < 7 * 6
440                   day = new Date(firstDate.getFullYear(), firstDate.getMonth(), firstDate.getDate() + i);
441                   days.push({
442                     date: day,
443                     label: dateFilter(day, this.format),
444                     selected: picker.$date && this.isSelected(day),
445                     muted: day.getMonth() !== viewDate.month,
446                     disabled: this.isDisabled(day)
447                   });
448                 }
449                 scope.title = dateFilter(firstDayOfMonth, 'MMMM yyyy');
450                 scope.showLabels = true;
451                 scope.labels = weekDaysLabelsHtml;
452                 scope.rows = split(days, this.split);
453                 this.built = true;
454               },
455               isSelected: function (date) {
456                 return picker.$date && date.getFullYear() === picker.$date.getFullYear() && date.getMonth() === picker.$date.getMonth() && date.getDate() === picker.$date.getDate();
457               },
458               isDisabled: function (date) {
459                 return date.getTime() < options.minDate || date.getTime() > options.maxDate;
460               },
461               onKeyDown: function (evt) {
462                 var actualTime = picker.$date.getTime();
463                 var newDate;
464                 if (evt.keyCode === 37)
465                   newDate = new Date(actualTime - 1 * 86400000);
466                 else if (evt.keyCode === 38)
467                   newDate = new Date(actualTime - 7 * 86400000);
468                 else if (evt.keyCode === 39)
469                   newDate = new Date(actualTime + 1 * 86400000);
470                 else if (evt.keyCode === 40)
471                   newDate = new Date(actualTime + 7 * 86400000);
472                 if (!this.isDisabled(newDate))
473                   picker.select(newDate, true);
474               }
475             },
476             {
477               name: 'month',
478               format: 'MMM',
479               split: 4,
480               steps: { year: 1 },
481               update: function (date, force) {
482                 if (!this.built || date.getFullYear() !== viewDate.year) {
483                   angular.extend(viewDate, {
484                     year: picker.$date.getFullYear(),
485                     month: picker.$date.getMonth(),
486                     date: picker.$date.getDate()
487                   });
488                   picker.$build();
489                 } else if (date.getMonth() !== viewDate.month) {
490                   angular.extend(viewDate, {
491                     month: picker.$date.getMonth(),
492                     date: picker.$date.getDate()
493                   });
494                   picker.$updateSelected();
495                 }
496               },
497               build: function () {
498                 var firstMonth = new Date(viewDate.year, 0, 1);
499                 var months = [], month;
500                 for (var i = 0; i < 12; i++) {
501                   month = new Date(viewDate.year, i, 1);
502                   months.push({
503                     date: month,
504                     label: dateFilter(month, this.format),
505                     selected: picker.$isSelected(month),
506                     disabled: this.isDisabled(month)
507                   });
508                 }
509                 scope.title = dateFilter(month, 'yyyy');
510                 scope.showLabels = false;
511                 scope.rows = split(months, this.split);
512                 this.built = true;
513               },
514               isSelected: function (date) {
515                 return picker.$date && date.getFullYear() === picker.$date.getFullYear() && date.getMonth() === picker.$date.getMonth();
516               },
517               isDisabled: function (date) {
518                 var lastDate = +new Date(date.getFullYear(), date.getMonth() + 1, 0);
519                 return lastDate < options.minDate || date.getTime() > options.maxDate;
520               },
521               onKeyDown: function (evt) {
522                 var actualMonth = picker.$date.getMonth();
523                 var newDate = new Date(picker.$date);
524                 if (evt.keyCode === 37)
525                   newDate.setMonth(actualMonth - 1);
526                 else if (evt.keyCode === 38)
527                   newDate.setMonth(actualMonth - 4);
528                 else if (evt.keyCode === 39)
529                   newDate.setMonth(actualMonth + 1);
530                 else if (evt.keyCode === 40)
531                   newDate.setMonth(actualMonth + 4);
532                 if (!this.isDisabled(newDate))
533                   picker.select(newDate, true);
534               }
535             },
536             {
537               name: 'year',
538               format: 'yyyy',
539               split: 4,
540               steps: { year: 12 },
541               update: function (date, force) {
542                 if (!this.built || force || parseInt(date.getFullYear() / 20, 10) !== parseInt(viewDate.year / 20, 10)) {
543                   angular.extend(viewDate, {
544                     year: picker.$date.getFullYear(),
545                     month: picker.$date.getMonth(),
546                     date: picker.$date.getDate()
547                   });
548                   picker.$build();
549                 } else if (date.getFullYear() !== viewDate.year) {
550                   angular.extend(viewDate, {
551                     year: picker.$date.getFullYear(),
552                     month: picker.$date.getMonth(),
553                     date: picker.$date.getDate()
554                   });
555                   picker.$updateSelected();
556                 }
557               },
558               build: function () {
559                 var firstYear = viewDate.year - viewDate.year % (this.split * 3);
560                 var years = [], year;
561                 for (var i = 0; i < 12; i++) {
562                   year = new Date(firstYear + i, 0, 1);
563                   years.push({
564                     date: year,
565                     label: dateFilter(year, this.format),
566                     selected: picker.$isSelected(year),
567                     disabled: this.isDisabled(year)
568                   });
569                 }
570                 scope.title = years[0].label + '-' + years[years.length - 1].label;
571                 scope.showLabels = false;
572                 scope.rows = split(years, this.split);
573                 this.built = true;
574               },
575               isSelected: function (date) {
576                 return picker.$date && date.getFullYear() === picker.$date.getFullYear();
577               },
578               isDisabled: function (date) {
579                 var lastDate = +new Date(date.getFullYear() + 1, 0, 0);
580                 return lastDate < options.minDate || date.getTime() > options.maxDate;
581               },
582               onKeyDown: function (evt) {
583                 var actualYear = picker.$date.getFullYear(), newDate = new Date(picker.$date);
584                 if (evt.keyCode === 37)
585                   newDate.setYear(actualYear - 1);
586                 else if (evt.keyCode === 38)
587                   newDate.setYear(actualYear - 4);
588                 else if (evt.keyCode === 39)
589                   newDate.setYear(actualYear + 1);
590                 else if (evt.keyCode === 40)
591                   newDate.setYear(actualYear + 4);
592                 if (!this.isDisabled(newDate))
593                   picker.select(newDate, true);
594               }
595             }
596           ];
597         return {
598           views: options.minView ? Array.prototype.slice.call(views, options.minView) : views,
599           viewDate: viewDate
600         };
601       };
602     }
603   ];
604 });