Update license headers
[vid.git] / vid-app-common / src / main / webapp / app / vid / scripts / directives / angularjs-datetime-picker.js
1 /*-
2  * ============LICENSE_START=======================================================
3  * VID
4  * ================================================================================
5  * Copyright (C) 2017 - 2019 AT&T Intellectual Property. All rights reserved.
6  * ================================================================================
7  * Licensed under the Apache License, Version 2.0 (the "License");
8  * you may not use this file except in compliance with the License.
9  * You may obtain a copy of the License at
10  * 
11  *      http://www.apache.org/licenses/LICENSE-2.0
12  * 
13  * Unless required by applicable law or agreed to in writing, software
14  * distributed under the License is distributed on an "AS IS" BASIS,
15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * See the License for the specific language governing permissions and
17  * limitations under the License.
18  * ============LICENSE_END=========================================================
19  */
20
21   'use strict';
22
23
24
25   var getTimezoneOffset = function(date) {
26     (typeof date == 'string')  && (date = new Date(date));
27     var jan = new Date(date.getFullYear(), 0, 1);
28     var jul = new Date(date.getFullYear(), 6, 1);
29     var stdTimezoneOffset = Math.max(jan.getTimezoneOffset(), jul.getTimezoneOffset());
30     var isDST = date.getTimezoneOffset() < stdTimezoneOffset;
31     var offset = isDST ? stdTimezoneOffset - 60 : stdTimezoneOffset;
32     var diff = offset >=0 ? '-' : '+';
33     return diff +
34       ("0"+ (offset / 60)).slice(-2) + ':' +
35       ("0"+ (offset % 60)).slice(-2);
36   };
37
38   var DatetimePicker = function($compile, $document, $controller){
39     var datetimePickerCtrl = $controller('DatetimePickerCtrl'); //directive controller
40     return {
41       open: function(options) {
42         datetimePickerCtrl.openDatetimePicker(options);
43       },
44       close: function() {
45         datetimePickerCtrl.closeDatetimePicker();
46       }
47     };
48   };
49   DatetimePicker.$inject = ['$compile', '$document', '$controller'];
50   appDS2.factory('DatetimePicker', DatetimePicker);
51
52   var DatetimePickerCtrl = function($compile, $document) {
53     var datetimePickerEl;
54     var _this = this;
55     var removeEl = function(el) {
56       el && el.remove();
57       $document[0].body.removeEventListener('click', _this.closeDatetimePicker);
58     };
59
60     this.openDatetimePicker = function(options) {
61       this.closeDatetimePicker();
62       var div = angular.element('<div datetime-picker-popup ng-cloak></div>');
63       options.dateFormat && div.attr('date-format', options.dateFormat);
64       options.ngModel  && div.attr('ng-model', options.ngModel);
65       options.year     && div.attr('year', parseInt(options.year));
66       options.month    && div.attr('month', parseInt(options.month));
67       options.day      && div.attr('day', parseInt(options.day));
68       options.hour     && div.attr('hour', parseInt(options.hour));
69       options.minute   && div.attr('minute', parseInt(options.minute));
70       if (options.dateOnly === '' || options.dateOnly === true) {
71         div.attr('date-only', 'true');
72       }
73       if (options.closeOnSelect === 'false') {
74         div.attr('close-on-select', 'false');
75       }
76
77       var triggerEl = options.triggerEl;
78       options.scope = options.scope || angular.element(triggerEl).scope();
79       datetimePickerEl = $compile(div)(options.scope)[0];
80       datetimePickerEl.triggerEl = options.triggerEl;
81
82       $document[0].body.appendChild(datetimePickerEl);
83
84       //show datetimePicker below triggerEl
85       var bcr = triggerEl.getBoundingClientRect();
86
87
88       options.scope.$apply();
89
90       var datePickerElBcr = datetimePickerEl.getBoundingClientRect();
91
92       datetimePickerEl.style.position='absolute';
93       if(bcr.width > datePickerElBcr.width){
94         datetimePickerEl.style.left= (bcr.left + bcr.width - datePickerElBcr.width + window.scrollX) + 'px';
95       } else {
96         datetimePickerEl.style.left= (bcr.left + window.scrollX) + 'px';
97       }
98
99       if (bcr.top < 300 || window.innerHeight - bcr.bottom > 300) {
100         datetimePickerEl.style.top = (bcr.bottom + window.scrollY) + 'px';
101       } else {
102         datetimePickerEl.style.top = (bcr.top - datePickerElBcr.height + window.scrollY) + 'px';
103       }
104
105       $document[0].body.addEventListener('click', this.closeDatetimePicker);
106     };
107
108     this.closeDatetimePicker = function(evt) {
109       var target = evt && evt.target;
110       var popupEl = $document[0].querySelector('div[datetime-picker-popup]');
111       if (evt && target) {
112         if (target.hasAttribute('datetime-picker')) {  // element with datetimePicker behaviour
113           // do nothing
114         } else if (popupEl && popupEl.contains(target)) { // datetimePicker itself
115           // do nothing
116         } else {
117           removeEl(popupEl);
118         }
119       } else {
120         removeEl(popupEl);
121       }
122     }
123   };
124   DatetimePickerCtrl.$inject = ['$compile', '$document'];
125   appDS2.controller('DatetimePickerCtrl', DatetimePickerCtrl);
126
127   var tmpl = [
128     '<div class="angularjs-datetime-picker">' ,
129     '  <div class="adp-month">',
130     '    <button type="button" class="adp-prev" ng-click="addMonth(-1)">&laquo;</button>',
131     '    <span title="{{months[mv.month].fullName}}">{{months[mv.month].shortName}}</span> {{mv.year}}',
132     '    <button type="button" class="adp-next" ng-click="addMonth(1)">&raquo;</button>',
133     '  </div>',
134     '  <div class="adp-days" ng-click="setDate($event)">',
135     '    <div class="adp-day-of-week" ng-repeat="dayOfWeek in ::daysOfWeek" title="{{dayOfWeek.fullName}}">{{::dayOfWeek.firstLetter}}</div>',
136     '    <div class="adp-day" ng-show="mv.leadingDays.length < 7" ng-repeat="day in mv.leadingDays">{{::day}}</div>',
137     '    <div class="adp-day selectable" ng-repeat="day in mv.days" ',
138     '      today="{{today}}" d2="{{mv.year + \'-\' + (mv.month + 1) + \'-\' + day}}"',
139     '      ng-class="{',
140     '        selected: (day == selectedDay),',
141     '        today: (today == (mv.year + \'-\' + (mv.month + 1) + \'-\' + day)),',
142     '        weekend: (mv.leadingDays.length + day)%7 == 1 || (mv.leadingDays.length + day)%7 == 0',
143     '      }">',
144     '      {{::day}}',
145     '    </div>',
146     '    <div class="adp-day" ng-show="mv.trailingDays.length < 7" ng-repeat="day in mv.trailingDays">{{::day}}</div>',
147     '  </div>',
148     '  <div class="adp-days" id="adp-time"> ',
149     '    <label class="timeLabel">Time:</label> <span class="timeValue">{{("0"+inputHour).slice(-2)}} : {{("0"+inputMinute).slice(-2)}}</span><br/>',
150     '    <label class="hourLabel">Hour:</label> <input class="hourInput" type="range" min="0" max="23" ng-model="inputHour" ng-change="updateNgModel()" />',
151     '    <label class="minutesLabel">Min:</label> <input class="minutesInput" type="range" min="0" max="59" ng-model="inputMinute"  ng-change="updateNgModel()"/> ',
152     '  </div>',
153     '</div>'].join("\n");
154
155   var datetimePickerPopup = function($locale, dateFilter){
156     var days, months, daysOfWeek, firstDayOfWeek;
157
158     var initVars = function() {
159       days =[], months=[]; daysOfWeek=[], firstDayOfWeek=0;
160       for (var i = 1; i <= 31; i++) {
161         days.push(i);
162       }
163
164       for (var i = 0; i < 12; i++) { //jshint ignore:line
165         months.push({
166           fullName: $locale.DATETIME_FORMATS.MONTH[i],
167           shortName: $locale.DATETIME_FORMATS.SHORTMONTH[i]
168         });
169       }
170
171       for (var i = 0; i < 7; i++) { //jshint ignore:line
172         var day = $locale.DATETIME_FORMATS.DAY[(i + firstDayOfWeek) % 7];
173
174         daysOfWeek.push({
175           fullName: day,
176           firstLetter: day.substr(0, 1)
177         });
178       }
179       firstDayOfWeek = 0;
180     };
181
182     var getMonthView = function(year, month) {
183       if (month>11) {
184         year++;
185       } else if (month < 0) {
186         year--;
187       }
188       month = (month + 12) % 12;
189       var firstDayOfMonth = new Date(year, month, 1),
190         lastDayOfMonth = new Date(year, month + 1, 0),
191         lastDayOfPreviousMonth = new Date(year, month, 0),
192         daysInMonth = lastDayOfMonth.getDate(),
193         daysInLastMonth = lastDayOfPreviousMonth.getDate(),
194         dayOfWeek = firstDayOfMonth.getDay(),
195         leadingDays = (dayOfWeek - firstDayOfWeek + 7) % 7 || 7, // Ensure there are always leading days to give context
196         trailingDays = days.slice(0, 6 * 7 - (leadingDays + daysInMonth));
197       if (trailingDays.length > 7) {
198         trailingDays = trailingDays.slice(0, trailingDays.length-7);
199       }
200
201       return {
202         year: year,
203         month: month,
204         days: days.slice(0, daysInMonth),
205         leadingDays: days.slice(- leadingDays - (31 - daysInLastMonth), daysInLastMonth),
206         trailingDays: trailingDays
207       };
208     };
209
210     var linkFunc = function(scope, element, attrs, ctrl) { //jshint ignore:line
211       initVars(); //initialize days, months, daysOfWeek, and firstDayOfWeek;
212       var dateFormat = attrs.dateFormat || 'short';
213       scope.months = months;
214       scope.daysOfWeek = daysOfWeek;
215       scope.inputHour;
216       scope.inputMinute;
217
218       if (scope.dateOnly === true){
219         element[0].querySelector('#adp-time').style.display = 'none';
220       }
221
222       scope.$applyAsync( function() {
223         ctrl.triggerEl = angular.element(element[0].triggerEl);
224         if (attrs.ngModel) { // need to parse date string
225           var dateStr = ''+ctrl.triggerEl.scope().$eval(attrs.ngModel);
226           if (dateStr) {
227             if (!dateStr.match(/[0-9]{2}:/)) {  // if no time is given, add 00:00:00 at the end
228               dateStr += " 00:00:00";
229             }
230             dateStr = dateStr.replace(/([0-9]{2}-[0-9]{2})-([0-9]{4})/,'$2-$1');      //mm-dd-yyyy to yyyy-mm-dd
231             dateStr = dateStr.replace(/([\/-][0-9]{2,4})\ ([0-9]{2}\:[0-9]{2}\:)/,'$1T$2'); //reformat for FF
232             dateStr = dateStr.replace(/EDT|EST|CDT|CST|MDT|PDT|PST|UT|GMT/g,''); //remove timezone
233             dateStr = dateStr.replace(/\s*\(\)\s*/,'');                          //remove timezone
234             dateStr = dateStr.replace(/[\-\+][0-9]{2}:?[0-9]{2}$/,'');           //remove timezone
235             dateStr += getTimezoneOffset(dateStr);
236             var d = new Date(dateStr);
237             scope.selectedDate = new Date(
238               d.getFullYear(),
239               d.getMonth(),
240               d.getDate(),
241               d.getHours(),
242               d.getMinutes(),
243               d.getSeconds()
244             );
245           }
246         }
247
248         if (!scope.selectedDate || isNaN(scope.selectedDate.getTime())) { // no predefined date
249           var today = new Date();
250           var year = scope.year || today.getFullYear();
251           var month = scope.month ? (scope.month-1) : today.getMonth();
252           var day = scope.day || today.getDate();
253           var hour = scope.hour || today.getHours();
254           var minute = scope.minute || today.getMinutes();
255           scope.selectedDate = new Date(year, month, day, hour, minute, 0);
256         }
257         scope.inputHour   = scope.selectedDate.getHours();
258         scope.inputMinute = scope.selectedDate.getMinutes();
259
260         // Default to current year and month
261         scope.mv = getMonthView(scope.selectedDate.getFullYear(), scope.selectedDate.getMonth());
262         scope.today = dateFilter(new Date(), 'yyyy-M-d');
263         if (scope.mv.year == scope.selectedDate.getFullYear() && scope.mv.month == scope.selectedDate.getMonth()) {
264           scope.selectedDay = scope.selectedDate.getDate();
265         } else {
266           scope.selectedDay = null;
267         }
268       });
269
270 /*   disable previous months.not current months 
271      scope.addMonth = function (amount) {
272           var today = new Date();
273           if((amount==-1) && (scope.mv.month==today.getMonth())){
274                 
275                           
276           }
277           else{
278                   
279                   scope.mv = getMonthView(scope.mv.year, scope.mv.month + amount);
280           }
281       };*/
282       
283       scope.addMonth = function (amount) {
284           scope.mv = getMonthView(scope.mv.year, scope.mv.month + amount);
285         };
286
287       scope.setDate = function (evt) {
288           
289         var target = angular.element(evt.target)[0];
290         if (target.className.indexOf('selectable') !== -1) {
291           scope.updateNgModel(parseInt(target.innerHTML));
292           if (scope.closeOnSelect !== false) {
293             ctrl.closeDatetimePicker();
294           }
295         }
296       };
297
298       scope.updateNgModel = function(day) {
299         day = day ? day : scope.selectedDate.getDate();
300         scope.selectedDate = new Date(
301           scope.mv.year, scope.mv.month, day, scope.inputHour, scope.inputMinute, 0
302         );
303         scope.selectedDay = scope.selectedDate.getDate();
304         if (attrs.ngModel) {
305           //console.log('attrs.ngModel',attrs.ngModel);
306           var elScope = ctrl.triggerEl.scope(), dateValue;
307           if (elScope.$eval(attrs.ngModel) && elScope.$eval(attrs.ngModel).constructor.name === 'Date') {
308             dateValue = new Date(dateFilter(scope.selectedDate, dateFormat));
309           } else {
310             dateValue = dateFilter(scope.selectedDate, dateFormat);
311           }
312            elScope.$eval(attrs.ngModel + '= date', {date: dateValue}); // this line for have the date in the format of mm/dd/yyyy
313          // elScope.$eval(attrs.ngModel + '= date', {date: scope.selectedDate});// this line in the format of Thu Jul 20 2017 23:59:00 GMT-0400 (Eastern Daylight Time)
314         }
315       };
316
317       scope.$on('$destroy', ctrl.closeDatetimePicker);
318     };
319
320     return {
321       restrict: 'A',
322       template: tmpl,
323       controller: 'DatetimePickerCtrl',
324       replace: true,
325       scope: {
326         year: '=',
327         month: '=',
328         day: '=',
329         hour: '=',
330         minute: '=',
331         dateOnly: '=',
332         closeOnSelect: '=',
333         futureOnly:'='
334       },
335       link: linkFunc
336     };
337   };
338   datetimePickerPopup.$inject = ['$locale', 'dateFilter'];
339   appDS2.directive('datetimePickerPopup', datetimePickerPopup);
340
341   var datetimePicker  = function($parse, DatetimePicker) {
342     return {
343       // An ngModel is required to get the controller argument
344       require: 'ngModel',
345       link: function(scope, element, attrs, ctrl) {
346         // Attach validation watcher
347         scope.$watch(attrs.ngModel, function(value) {
348           if( !value || value == '' ){
349             return;
350           }
351           // The value has already been cleaned by the above code
352           var date = new Date(value);
353           ctrl.$setValidity('date', !date? false : true);
354           var now = new Date();
355           if( attrs.hasOwnProperty('futureOnly') ){
356             ctrl.$setValidity('future-only', date < now? false : true);
357           }
358         });
359
360         element[0].addEventListener('click', function() {
361           DatetimePicker.open({
362             triggerEl: element[0],
363             dateFormat: attrs.dateFormat,
364             ngModel: attrs.ngModel,
365             year: attrs.year,
366             month: attrs.month,
367             day: attrs.day,
368             hour: attrs.hour,
369             minute: attrs.minute,
370             dateOnly: attrs.dateOnly,
371             futureOnly: attrs.futureOnly,
372             closeOnSelect: attrs.closeOnSelect
373           });
374         });
375       }
376     };
377   };
378   datetimePicker.$inject=['$parse', 'DatetimePicker'];
379   appDS2.directive('datetimePicker', datetimePicker);
380