4 angular.module('angularjs-datetime-picker', []);
6 var getTimezoneOffset = function(date) {
7 (typeof date == 'string') && (date = new Date(date));
8 var jan = new Date(date.getFullYear(), 0, 1);
9 var jul = new Date(date.getFullYear(), 6, 1);
10 var stdTimezoneOffset = Math.max(jan.getTimezoneOffset(), jul.getTimezoneOffset());
11 var isDST = date.getTimezoneOffset() < stdTimezoneOffset;
12 var offset = isDST ? stdTimezoneOffset - 60 : stdTimezoneOffset;
13 var diff = offset >=0 ? '-' : '+';
15 ("0"+ (offset / 60)).slice(-2) + ':' +
16 ("0"+ (offset % 60)).slice(-2);
19 var DatetimePicker = function($compile, $document, $controller){
20 var datetimePickerCtrl = $controller('DatetimePickerCtrl'); //directive controller
22 open: function(options) {
23 datetimePickerCtrl.openDatetimePicker(options);
26 datetimePickerCtrl.closeDatetimePicker();
30 DatetimePicker.$inject = ['$compile', '$document', '$controller'];
31 angular.module('angularjs-datetime-picker').factory('DatetimePicker', DatetimePicker);
33 var DatetimePickerCtrl = function($compile, $document) {
36 var removeEl = function(el) {
38 $document[0].body.removeEventListener('click', _this.closeDatetimePicker);
41 this.openDatetimePicker = function(options) {
42 this.closeDatetimePicker();
43 var div = angular.element('<div datetime-picker-popup ng-cloak></div>');
44 options.dateFormat && div.attr('date-format', options.dateFormat);
45 options.ngModel && div.attr('ng-model', options.ngModel);
46 options.year && div.attr('year', parseInt(options.year));
47 options.month && div.attr('month', parseInt(options.month));
48 options.day && div.attr('day', parseInt(options.day));
49 options.hour && div.attr('hour', parseInt(options.hour));
50 options.minute && div.attr('minute', parseInt(options.minute));
51 if (options.dateOnly === '' || options.dateOnly === true) {
52 div.attr('date-only', 'true');
54 if (options.closeOnSelect === 'false') {
55 div.attr('close-on-select', 'false');
58 var triggerEl = options.triggerEl;
59 options.scope = options.scope || angular.element(triggerEl).scope();
60 datetimePickerEl = $compile(div)(options.scope)[0];
61 datetimePickerEl.triggerEl = options.triggerEl;
63 $document[0].body.appendChild(datetimePickerEl);
65 //show datetimePicker below triggerEl
66 var bcr = triggerEl.getBoundingClientRect();
67 datetimePickerEl.style.position='absolute';
68 datetimePickerEl.style.left= (bcr.left + window.scrollX) + 'px';
70 options.scope.$apply();
72 var datePickerElBcr = datetimePickerEl.getBoundingClientRect();
74 if (bcr.top < 300 || window.innerHeight - bcr.bottom > 300) {
75 datetimePickerEl.style.top = (bcr.bottom + window.scrollY) + 'px';
77 datetimePickerEl.style.top = (bcr.top - datePickerElBcr.height + window.scrollY) + 'px';
80 $document[0].body.addEventListener('click', this.closeDatetimePicker);
83 this.closeDatetimePicker = function(evt) {
84 var target = evt && evt.target;
85 var popupEl = $document[0].querySelector('div[datetime-picker-popup]');
87 if (target.hasAttribute('datetime-picker')) { // element with datetimePicker behaviour
89 } else if (popupEl && popupEl.contains(target)) { // datetimePicker itself
99 DatetimePickerCtrl.$inject = ['$compile', '$document'];
100 angular.module('angularjs-datetime-picker').controller('DatetimePickerCtrl', DatetimePickerCtrl);
103 '<div class="angularjs-datetime-picker">' ,
104 ' <div class="adp-month">',
105 ' <button type="button" class="adp-prev" ng-click="addMonth(-1)">«</button>',
106 ' <span title="{{months[mv.month].fullName}}">{{months[mv.month].shortName}}</span> {{mv.year}}',
107 ' <button type="button" class="adp-next" ng-click="addMonth(1)">»</button>',
109 ' <div class="adp-days" ng-click="setDate($event)">',
110 ' <div class="adp-day-of-week" ng-repeat="dayOfWeek in ::daysOfWeek" title="{{dayOfWeek.fullName}}">{{::dayOfWeek.firstLetter}}</div>',
111 ' <div class="adp-day" ng-repeat="day in mv.leadingDays">{{::day}}</div>',
112 ' <div class="adp-day selectable" ng-repeat="day in mv.days" ',
113 ' ng-class="{selected: (day == selectedDay)}">{{::day}}</div>',
114 ' <div class="adp-day" ng-repeat="day in mv.trailingDays">{{::day}}</div>',
116 ' <div class="adp-days" id="adp-time"> ',
117 ' Time : {{("0"+inputHour).slice(-2)}} : {{("0"+inputMinute).slice(-2)}} <br/>',
118 ' <label>Hour:</label> <input type="range" min="0" max="23" ng-model="inputHour" ng-change="updateNgModel()" />',
119 ' <label>Min.:</label> <input type="range" min="0" max="59" ng-model="inputMinute" ng-change="updateNgModel()"/> ',
121 '</div>'].join("\n");
123 var datetimePickerPopup = function($locale, dateFilter){
124 var days, months, daysOfWeek, firstDayOfWeek;
126 var initVars = function() {
127 days =[], months=[]; daysOfWeek=[], firstDayOfWeek=0;
128 for (var i = 1; i <= 31; i++) {
132 for (var i = 0; i < 12; i++) { //jshint ignore:line
134 fullName: $locale.DATETIME_FORMATS.MONTH[i],
135 shortName: $locale.DATETIME_FORMATS.SHORTMONTH[i]
139 for (var i = 0; i < 7; i++) { //jshint ignore:line
140 var day = $locale.DATETIME_FORMATS.DAY[(i + firstDayOfWeek) % 7];
144 firstLetter: day.substr(0, 2)
147 firstDayOfWeek = $locale.DATETIME_FORMATS.FIRSTDAYOFWEEK || 0;
150 var getMonthView = function(year, month) {
153 } else if (month < 0) {
156 month = (month + 12) % 12;
157 var firstDayOfMonth = new Date(year, month, 1),
158 lastDayOfMonth = new Date(year, month + 1, 0),
159 lastDayOfPreviousMonth = new Date(year, month, 0),
160 daysInMonth = lastDayOfMonth.getDate(),
161 daysInLastMonth = lastDayOfPreviousMonth.getDate(),
162 dayOfWeek = firstDayOfMonth.getDay(),
163 leadingDays = (dayOfWeek - firstDayOfWeek + 7) % 7 || 7, // Ensure there are always leading days to give context
164 trailingDays = days.slice(0, 6 * 7 - (leadingDays + daysInMonth));
165 if (trailingDays.length > 7) {
166 trailingDays = trailingDays.slice(0, trailingDays.length-7);
172 days: days.slice(0, daysInMonth),
173 leadingDays: days.slice(- leadingDays - (31 - daysInLastMonth), daysInLastMonth),
174 trailingDays: trailingDays
178 var linkFunc = function(scope, element, attrs, ctrl) { //jshint ignore:line
179 initVars(); //initialize days, months, daysOfWeek, and firstDayOfWeek;
180 var dateFormat = attrs.dateFormat || 'short';
181 scope.months = months;
182 scope.daysOfWeek = daysOfWeek;
186 if (scope.dateOnly === true){
187 element[0].querySelector('#adp-time').style.display = 'none';
190 scope.$applyAsync( function() {
191 ctrl.triggerEl = angular.element(element[0].triggerEl);
192 if (attrs.ngModel) { // need to parse date string
193 var dateStr = ''+ctrl.triggerEl.scope().$eval(attrs.ngModel);
195 if (!dateStr.match(/[0-9]{2}:/)) { // if no time is given, add 00:00:00 at the end
196 dateStr += " 00:00:00";
198 dateStr = dateStr.replace(/([0-9]{2}-[0-9]{2})-([0-9]{4})/,'$2-$1'); //mm-dd-yyyy to yyyy-mm-dd
199 dateStr = dateStr.replace(/([\/-][0-9]{2,4})\ ([0-9]{2}\:[0-9]{2}\:)/,'$1T$2'); //reformat for FF
200 dateStr = dateStr.replace(/EDT|EST|CDT|CST|MDT|PDT|PST|UT|GMT/g,''); //remove timezone
201 dateStr = dateStr.replace(/\s*\(\)\s*/,''); //remove timezone
202 dateStr = dateStr.replace(/[\-\+][0-9]{2}:?[0-9]{2}$/,''); //remove timezone
203 dateStr += getTimezoneOffset(dateStr);
204 var d = new Date(dateStr);
205 scope.selectedDate = new Date(
216 if (!scope.selectedDate || isNaN(scope.selectedDate.getTime())) { // no predefined date
217 var today = new Date();
218 var year = scope.year || today.getFullYear();
219 var month = scope.month ? (scope.month-1) : today.getMonth();
220 var day = scope.day || today.getDate();
221 var hour = scope.hour || today.getHours();
222 var minute = scope.minute || today.getMinutes();
223 scope.selectedDate = new Date(year, month, day, hour, minute, 0);
225 scope.inputHour = scope.selectedDate.getHours();
226 scope.inputMinute = scope.selectedDate.getMinutes();
228 // Default to current year and month
229 scope.mv = getMonthView(scope.selectedDate.getFullYear(), scope.selectedDate.getMonth());
230 if (scope.mv.year == scope.selectedDate.getFullYear() && scope.mv.month == scope.selectedDate.getMonth()) {
231 scope.selectedDay = scope.selectedDate.getDate();
233 scope.selectedDay = null;
237 scope.addMonth = function (amount) {
238 scope.mv = getMonthView(scope.mv.year, scope.mv.month + amount);
241 scope.setDate = function (evt) {
242 var target = angular.element(evt.target)[0];
243 if (target.className.indexOf('selectable')) {
244 scope.updateNgModel(parseInt(target.innerHTML));
245 if (scope.closeOnSelect !== false) {
246 ctrl.closeDatetimePicker();
251 scope.updateNgModel = function(day) {
252 day = day ? day : scope.selectedDate.getDate();
253 scope.selectedDate = new Date(
254 scope.mv.year, scope.mv.month, day, scope.inputHour, scope.inputMinute, 0
256 scope.selectedDay = scope.selectedDate.getDate();
258 //console.log('attrs.ngModel',attrs.ngModel);
259 var elScope = ctrl.triggerEl.scope(), dateValue;
260 if (elScope.$eval(attrs.ngModel) && elScope.$eval(attrs.ngModel).constructor.name === 'Date') {
261 dateValue = new Date(dateFilter(scope.selectedDate, dateFormat));
263 dateValue = dateFilter(scope.selectedDate, dateFormat);
265 elScope.$eval(attrs.ngModel + '= date', {date: dateValue});
269 scope.$on('$destroy', ctrl.closeDatetimePicker);
275 controller: 'DatetimePickerCtrl',
289 datetimePickerPopup.$inject = ['$locale', 'dateFilter'];
290 angular.module('angularjs-datetime-picker').directive('datetimePickerPopup', datetimePickerPopup);
292 var datetimePicker = function($parse, DatetimePicker) {
294 // An ngModel is required to get the controller argument
296 link: function(scope, element, attrs, ctrl) {
297 // Attach validation watcher
298 scope.$watch(attrs.ngModel, function(value) {
299 if( !value || value == '' ){
302 // The value has already been cleaned by the above code
303 var date = new Date(value);
304 ctrl.$setValidity('date', !date? false : true);
305 var now = new Date();
306 if( attrs.hasOwnProperty('futureOnly') ){
307 ctrl.$setValidity('future-only', date < now? false : true);
311 element[0].addEventListener('click', function() {
312 DatetimePicker.open({
313 triggerEl: element[0],
314 dateFormat: attrs.dateFormat,
315 ngModel: attrs.ngModel,
320 minute: attrs.minute,
321 dateOnly: attrs.dateOnly,
322 futureOnly: attrs.futureOnly,
323 closeOnSelect: attrs.closeOnSelect
329 datetimePicker.$inject=['$parse', 'DatetimePicker'];
330 angular.module('angularjs-datetime-picker').directive('datetimePicker', datetimePicker);