ng-material-datetimepicker
Version:
A datetime picker for AngularJS Material
1,188 lines (1,069 loc) • 54.8 kB
JavaScript
(function () {
'use strict';
function ngMaterialDatePicker(moment) {
var moduleName = "ngMaterialDatePicker";
var mdAccesTime = 'data:image/svg+xml;base64,PHN2ZyBmaWxsPSIjMDAwMDAwIiBoZWlnaHQ9IjI0IiB2aWV3Qm94PSIwIDAgMjQgMjQiIHdpZHRoPSIyNCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4gICAgPHBhdGggZD0iTTExLjk5IDJDNi40NyAyIDIgNi40OCAyIDEyczQuNDcgMTAgOS45OSAxMEMxNy41MiAyMiAyMiAxNy41MiAyMiAxMlMxNy41MiAyIDExLjk5IDJ6TTEyIDIwYy00LjQyIDAtOC0zLjU4LTgtOHMzLjU4LTggOC04IDggMy41OCA4IDgtMy41OCA4LTggOHoiLz4gICAgPHBhdGggZD0iTTAgMGgyNHYyNEgweiIgZmlsbD0ibm9uZSIvPiAgICA8cGF0aCBkPSJNMTIuNSA3SDExdjZsNS4yNSAzLjE1Ljc1LTEuMjMtNC41LTIuNjd6Ii8+PC9zdmc+';
var mdCalendar = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0Ij48cGF0aCBkPSJNMTkgM2gtMVYxaC0ydjJIOFYxSDZ2Mkg1Yy0xLjExIDAtMS45OS45LTEuOTkgMkwzIDE5YzAgMS4xLjg5IDIgMiAyaDE0YzEuMSAwIDItLjkgMi0yVjVjMC0xLjEtLjktMi0yLTJ6bTAgMTZINVY4aDE0djExek03IDEwaDV2NUg3eiIvPjwvc3ZnPg==';
var VIEW_STATES = {DATE: 0, HOUR: 1, MINUTE: 2};
var css = function (el, name) {
el = angular.element(el);
return ('getComputedStyle' in window) ? window.getComputedStyle(el[0])[name] : el.css(name);
};
var template =
'<md-dialog class="dtp" layout="column">' +
' <md-dialog-content class="dtp-content">' +
' <div class="dtp-date-view">' +
' <header class="dtp-header">' +
' <div class="dtp-actual-day" ng-if="::picker.dateMode">{{picker.currentNearestMinute().format("dddd")}}</div>' +
' <div class="dtp-actual-day" ng-if="::!picker.timeMode&&picker.params.time">{{picker.params.shortTime?picker.currentDate.format("A"):" "}}</div>' +
' <div class="dtp-close text-right noselect">' +
' <a href="#" mdc-dtp-noclick ng-click="picker.hide()">×</a>' +
' </div>' +
' </header>' +
' <div class="dtp-date" ng-if="picker.params.date">' +
' <div layout="row">' +
' <div ng-click="picker.incrementMonth(-1)" class="dtp-month-btn dtp-month-btn-prev noselect" flex="30"><span ng-if="picker.isPreviousMonthVisible()">◄</span></div>' +
' <md-menu md-offset="8 10" flex>' +
' <div class="dtp-actual-month" flex ng-click="picker.openMenu($mdMenu, $event)">{{picker.currentDate.format("MMM")|uppercase}}</div>' +
' <md-menu-content class="dtp-month-list">' +
' <md-menu-item ng-repeat="itemMonth in picker.monthsAvailable() track by $index">' +
' <md-button ng-click="picker.selectMonth(itemMonth)">{{itemMonth}}</md-button>' +
' </md-menu-item>' +
' </md-menu-content>' +
' </md-menu>' +
' <div ng-click="picker.incrementMonth(1)" class="dtp-month-btn dtp-month-btn-next noselect" flex="30"><span ng-if="picker.isNextMonthVisible()">►</span></div>' +
' </div>' +
' <div class="dtp-actual-num">{{picker.currentDate.format("DD")}}</div>' +
' <div layout="row">' +
' <div ng-click="picker.incrementYear(-1)" class="dtp-year-btn dtp-year-btn-prev noselect" flex="30"><span ng-if="picker.isPreviousYearVisible()">◄</span></div>' +
' <md-menu md-offset="8 10" flex>' +
' <div class="dtp-actual-year" flex ng-click="picker.openMenu($mdMenu, $event)">{{picker.currentDate.format("YYYY")}}</div>' +
' <md-menu-content class="dtp-year-list">' +
' <md-menu-item ng-repeat="itemYear in picker.yearsAvailable() track by $index">' +
' <md-button ng-click="picker.selectYear(itemYear)">{{itemYear}}</md-button>' +
' </md-menu-item>' +
' </md-menu-content>' +
' </md-menu>' +
' <div ng-click="picker.incrementYear(1)" class="dtp-year-btn dtp-year-btn-next noselect" flex="30"><span ng-if="picker.isNextYearVisible()">►</span></div>' +
' </div>'+
' </div>' + //start time
' <div class="dtp-time" ng-if="picker.params.time&&!picker.params.date">' +
' <div class="dtp-actual-maxtime">' +
' <span ng-if="!picker.params.seconds"><span ng-class="{selected:picker.currentView===picker.VIEWS.HOUR}">{{picker.currentNearestMinute().format(picker.params.shortTime ? "hh":"HH")}}</span>:<span ng-class="{selected: picker.currentView===picker.VIEWS.MINUTE}">{{picker.currentNearestMinute().format("mm")}}</span></span>'+
' <span ng-if="picker.params.seconds"><span ng-class="{selected:picker.currentView===picker.VIEWS.HOUR}">{{picker.currentNearestMinute().format(picker.params.shortTime ? "hh":"HH")}}</span>:<span ng-class="{selected: picker.currentView===picker.VIEWS.MINUTE}">{{picker.currentNearestMinute().format("mm")}}</span>:<span ng-class="{selected: picker.currentView===picker.VIEWS.SECOND}">{{picker.currentNearestMinute().format("ss")}}</span></span>'+
' <span class="dtp-actual-meridien" ng-if="picker.params.shortTime">{{picker.currentDate.format("A")}}</span>'+
' </div>' +
' </div>' +
' <div class="dtp-picker">' +
' <mdc-datetime-picker-calendar date="picker.currentDate" picker="picker" class="dtp-picker-calendar" ng-if="picker.currentView===picker.VIEWS.DATE"></mdc-datetime-picker-calendar>' +
' <div class="dtp-picker-datetime" ng-cloak ng-if="picker.currentView!==picker.VIEWS.DATE">' +
' <div class="dtp-actual-meridien">' +
' <div ng-if="picker.params.shortTime" class="left p20">' +
' <a id="time-periods-am" href="#" mdc-dtp-noclick class="dtp-meridien-am" ng-class="{selected:picker.meridien===\'AM\'}" ng-click="picker.selectAM()">{{::picker.params.amText}}</a>' +
' </div>' +
' <div ng-if="!picker.timeMode&&!picker.params.seconds" class="dtp-actual-time p60">' +
' <span ng-class="{selected:picker.currentView===picker.VIEWS.HOUR}">{{picker.currentNearestMinute().format(picker.params.shortTime?"hh":"HH")}}</span>:<span ng-class="{selected:picker.currentView===picker.VIEWS.MINUTE}">{{picker.currentNearestMinute().format("mm")}}</span>' +
' </div>' +
' <div ng-if="!picker.timeMode&&picker.params.seconds" class="dtp-actual-time p60">' +
' <span ng-class="{selected: picker.currentView===picker.VIEWS.HOUR}">{{picker.currentNearestMinute().format(picker.params.shortTime?"hh":"HH")}}</span>:<span ng-class="{selected:picker.currentView===picker.VIEWS.MINUTE}">{{picker.currentNearestMinute().format("mm")}}</span>:<span ng-class="{selected:picker.currentView===picker.VIEWS.SECOND}">{{picker.currentNearestMinute().format("ss")}}</span>' +
' </div>' +
' <div ng-if="picker.params.shortTime" class="right p20">' +
' <a id="time-periods-pm" href="#" mdc-dtp-noclick class="dtp-meridien-pm" ng-class="{selected:picker.meridien===\'PM\'}" ng-click="picker.selectPM()">{{::picker.params.pmText}}</a>' +
' </div>' +
' <div class="clearfix"></div>' +
' </div>' +
' <mdc-datetime-picker-clock mode="hours" ng-if="picker.currentView===picker.VIEWS.HOUR"></mdc-datetime-picker-clock>' +
' <mdc-datetime-picker-clock mode="minutes" ng-if="picker.currentView===picker.VIEWS.MINUTE"></mdc-datetime-picker-clock>' +
' <mdc-datetime-picker-clock mode="seconds" ng-if="picker.currentView===picker.VIEWS.SECOND"></mdc-datetime-picker-clock>' +
' </div>' +
' </div>' +
' </div>' +
' </md-dialog-content>' +
' <md-dialog-actions class="dtp-buttons">' +
' <md-button ng-if="::picker.params.todayBtn" class="dtp-btn-today md-button" ng-click="picker.today()"> {{::picker.params.todayText}}</md-button>' +
' <md-button class="dtp-btn-cancel md-button" ng-click="picker.cancel()"> {{::picker.params.cancelText}}</md-button>' +
' <md-button class="dtp-btn-ok md-button" ng-click="picker.ok()"> {{::picker.params.okText}}</md-button>' +
' </md-dialog-actions>' +
'</md-dialog>';
angular.module(moduleName, ['ngMaterial'])
.factory('mdcDefaultParams', function () {
var default_params = {
date: true,
time: true,
minutes: true,
seconds: false,
format: 'YYYY-MM-DD',
minDate: null,
maxDate: null,
currentDate: null,
lang: window.navigator.userLanguage || window.navigator.language || 'en',
weekStart: 0,
shortTime: false,
cancelText: 'Cancel',
okText: 'OK',
amText: 'AM',
pmText: 'PM',
todayBtn: true,
todayText: 'Today',
disableDates: [],
weekDays: false,
disableParentScroll: false,
autoOk: false,
editInput: false,
clickOutsideToClose: false,
minuteSteps: 5,
showIcon: false,
showClear: true,
template: template,
templateUrl: '',
targetEvent: null,
openFrom: null,
closeTo: null,
dayOfWeekLen: 1,
hasBackdrop: true,
};
return function (params) {
if (params) {
for (var i in params) {
if (default_params.hasOwnProperty(i) && params.hasOwnProperty(i)) default_params[i] = params[i];
}
}
return default_params;
};
})
.directive('mdcDatetimePicker', ['$mdDialog', '$timeout', '$compile', '$parse',
function ($mdDialog, $timeout, $compile, $parse) {
return {
restrict: 'A',
require: 'ngModel',
scope: {
currentDate: '=ngModel',
ngModelOptions: '=',
ngChange: '&',
time: '=',
date: '=',
minutes: '=',
seconds: '=',
minDate: '=',
maxDate: '=',
disableDates: '=',
weekDays: '=',
shortTime: '=',
weekStart: '=',
format: '@',
cancelText: '@',
okText: '@',
lang: '@',
amText: '@',
pmText: '@',
showTodaysDate: '@',
todayBtn: '=',
todayText: '@',
disableParentScroll: '=',
autoOk: '=',
editInput: '=',
clickOutsideToClose: '=',
minuteSteps: '=',
showIcon: '=',
showClear: '=',
templateUrl: '@',
dayOfWeekLen: '=',
hasBackdrop: '='
},
link: function (scope, element, attrs, ngModel) {
var isOn = false;
if (!scope.format) {
if (scope.date && scope.time && scope.seconds) scope.format = 'YYYY-MM-DD HH:mm:ss';
else if (scope.date && scope.time) scope.format = 'YYYY-MM-DD HH:mm';
else if (scope.date) scope.format = 'YYYY-MM-DD';
else scope.format = 'HH:mm';
}
var dateOfTheDay = null;
if (scope.showTodaysDate !== undefined && scope.showTodaysDate !== "false") {
dateOfTheDay = moment();
}
if (angular.isString(scope.currentDate) && scope.currentDate !== '') {
scope.currentDate = moment(scope.currentDate, scope.format);
}
var offset;
if (ngModel) {
var ngModelOptions = {'*': '$inherit', debounce: 500};
if (angular.version.major === 1 && angular.version.minor > 5) {
ngModel.$options = ngModel.$options.createChild(ngModelOptions);
if (ngModel.$options.getOption('timezone')) offset = ngModel.$options.getOption('timezone');
} else {
if (scope.ngModelOptions && scope.ngModelOptions.timezone) ngModelOptions.timezone = scope.ngModelOptions.timezone;
ngModel.$options = ngModelOptions;
if (ngModel.$options.timezone) offset = ngModel.$options.timezone;
}
if (offset==='utc' || offset==='UTC') offset = 0;
ngModel.$formatters.push(function (value) {
if (typeof value === 'undefined') return;
var m = moment(value);
return m.isValid() ? m.format(scope.format) : '';
});
ngModel.$parsers.push(function (value) {
if (typeof value === 'undefined') return;
var m = moment(value, scope.format);
if (offset !== undefined) m.utcOffset(offset, m._tzm === undefined);
if (scope.minDate) ngModel.$setValidity('min', !m.isBefore(scope.minDate));
if (scope.maxDate) ngModel.$setValidity('max', !m.isAfter(scope.maxDate));
ngModel.$setValidity('format', moment(value, scope.format, true).isValid());
return m.isValid() ? (m._isUTC ? m : m.toDate()) : '';
});
}
function openCalendar(e) {
e.preventDefault();
element.blur();
element.parent().removeClass('md-input-focused');
if (isOn) {
return;
}
isOn = true;
var options = {};
for (var i in attrs) {
if (scope.hasOwnProperty(i) && !angular.isUndefined(scope[i])) {
options[i] = scope[i];
}
}
options.currentDate = scope.currentDate;
options.showTodaysDate = dateOfTheDay;
var dialogOptions = {
controller: PluginController,
controllerAs: 'picker',
locals: {options: options},
openFrom: element,
closeTo: element,
parent: angular.element(document.body),
bindToController: true,
clickOutsideToClose: options.clickOutsideToClose || false,
disableParentScroll: options.disableParentScroll || false,
hasBackdrop: options.hasBackdrop === undefined ? true : options.hasBackdrop,
skipHide: true,
multiple: true,
};
if (!options.templateUrl) dialogOptions.template = template;
else dialogOptions.templateUrl = options.templateUrl;
$mdDialog.show(dialogOptions).then(function(v) {
if (offset !== undefined) v.utcOffset(offset, true);
scope.currentDate = v && !v._isUTC ? v.toDate() : v;
ngModel.$setValidity('format', true);
ngModel.$setViewValue(scope.currentDate);
ngModel.$setDirty();
isOn = false;
if (!moment(scope.currentDate).isSame(options.currentDate)) {
$timeout(scope.ngChange, 0);
}
element.parent().removeClass('md-input-focused');
}, function () {
isOn = false;
element.parent().removeClass('md-input-focused');
});
}
if (!scope.editInput) {
if (scope.showIcon) {
element.on('click', openCalendar);
} else {
element.on('focus', openCalendar);
}
}
if (scope.showIcon) {
element.addClass('dtp-no-msclear dtp-input');
var calendarButton =
'<md-button class="dtp-btn-calendar md-icon-button" type="button"' +
'tabindex="-1" aria-hidden="true" ' +
($parse(attrs.ngDisabled)() ? 'disabled ' : '') +
'ng-click="openCalendarDiag($event)">' +
'<md-icon aria-label="md-calendar" md-svg-src="' + (scope.date ? mdCalendar : mdAccesTime) + '"></md-icon>' +
'</md-button>', clearButton = '';
if (scope.showClear === undefined || scope.showClear) {
clearButton = '<md-button ' +
($parse(attrs.ngDisabled)() ? 'disabled ' : '') +
'ng-show="currentDate" class="md-icon-button dtp-clear" aria-hidden="true" ng-click="clear()">✕</md-button>';
}
element.after($compile(calendarButton + clearButton)(scope));
scope.openCalendarDiag = function(e) {
openCalendar(e);
};
scope.clear = function() {
ngModel.$setViewValue(null);
scope.currentDate = null;
ngModel.$render();
$timeout(function() {
scope.ngChange();
element[0].focus();
}, 0, false);
};
}
}
};
}])
// Returns a service that opens a dialog when the attribute shown is called
.factory('mdcDateTimeDialog', ["$mdDialog", "$q", function ($mdDialog, $q) {
var service = {
show: function (options) {
var deferred = $q.defer();
if (options.showTodaysDate !== undefined && options.showTodaysDate !== "false") options.showTodaysDate = moment();
var dialogOptions = {
controller: PluginController,
controllerAs: 'picker',
locals: {options: options},
parent: angular.element(document.body),
bindToController: true,
clickOutsideToClose: options.clickOutsideToClose || false,
disableParentScroll: options.disableParentScroll || false,
skipHide: true,
multiple: true,
hasBackdrop: options.hasBackdrop === undefined ? true : options.hasBackdrop,
targetEvent: options.targetEvent,
openFrom: options.openFrom,
closeTo: options.closeTo,
};
if (!options.templateUrl) dialogOptions.template = template;
else dialogOptions.templateUrl = options.templateUrl;
$mdDialog.show(dialogOptions).then(function (v) {
deferred.resolve(v && !v._isUTC ? v.toDate() : v);
}, function () {
deferred.reject();
});
return deferred.promise;
}
};
return service;
}])
;
var PluginController = function ($mdDialog, mdcDefaultParams) {
this.currentView = VIEW_STATES.DATE;
this._dialog = $mdDialog;
this._attachedEvents = [];
this.VIEWS = VIEW_STATES;
this.params = angular.copy(mdcDefaultParams());
this.meridien = 'AM';
};
PluginController.$inject = ['$mdDialog', 'mdcDefaultParams'];
PluginController.prototype = {
$onInit: function () {
this.params = angular.extend(this.params, this.options);
this.timeMode = this.params.time && !this.params.date;
this.dateMode = this.params.date;
this.initDates();
this.start();
},
currentNearestMinute: function () {
var nearestMin = this.params.minuteSteps;
if (nearestMin < 1 || nearestMin > 59) nearestMin = 1;
var date = this.currentDate || moment();
var minutes = (nearestMin * Math.round(date.minute() / nearestMin));
if (minutes >= 60) minutes = 60 - nearestMin;
var seconds;
if (this.params.seconds) {
seconds = date.second();
if (seconds >= 60) seconds = 60 - 1;
} else seconds = 0;
return moment(date).minutes(minutes).seconds(seconds).millisecond(0);
},
initDates: function () {
var that = this;
var _dateParam = function (input, fallback) {
var ret = null;
if (angular.isDefined(input) && input !== null && input !== '') {
if (angular.isString(input)) {
if (typeof(that.params.format) !== 'undefined' && that.params.format !== null) {
ret = moment(input, that.params.format).locale(that.params.lang);
}
else {
ret = moment(input).locale(that.params.lang);
}
} else if (typeof input === 'number') {
ret = moment(input).locale(that.params.lang);
} else {
if (angular.isDate(input)) {
var x = input.getTime();
ret = moment(x, "x").locale(that.params.lang);
} else if (moment.isMoment(input)) {
if (input.isValid()) ret = input;
else ret = fallback;
}
}
} else {
ret = fallback;
}
return ret;
};
this.currentDate = _dateParam(this.params.currentDate, moment());
this.currentDate = this.currentNearestMinute();
this.minDate = _dateParam(this.params.minDate);
this.maxDate = _dateParam(this.params.maxDate);
this.disableDates = this.params.disableDates.map(function (x) {
return moment(x).format('MMMM Do YYYY');
});
this.selectDate(this.currentDate);
this.weekDays = this.params.weekDays;
},
initDate: function () {
this.currentView = VIEW_STATES.DATE;
},
initHours: function () {
this.currentView = VIEW_STATES.HOUR;
},
initMinutes: function () {
this.currentView = VIEW_STATES.MINUTE;
},
initSeconds: function () {
this.currentView = VIEW_STATES.SECOND;
},
isAfterMinDate: function (date, checkHour, checkMinute) {
var _return = true;
if (typeof(this.minDate) !== 'undefined' && this.minDate !== null) {
var _minDate = moment(this.minDate);
var _date = moment(date);
if (!checkHour && !checkMinute) {
_minDate.hour(0);
_minDate.minute(0);
_date.hour(0);
_date.minute(0);
}
_minDate.second(0);
_date.second(0);
_minDate.millisecond(0);
_date.millisecond(0);
if (!checkMinute) {
_date.minute(0);
_minDate.minute(0);
_return = (parseInt(_date.format("X")) >= parseInt(_minDate.format("X")));
}
else {
_return = (parseInt(_date.format("X")) >= parseInt(_minDate.format("X")));
}
}
return _return;
},
isBeforeMaxDate: function (date, checkTime, checkMinute) {
var _return = true;
if (typeof(this.maxDate) !== 'undefined' && this.maxDate !== null) {
var _maxDate = moment(this.maxDate);
var _date = moment(date);
if (!checkTime && !checkMinute) {
_maxDate.hour(0);
_maxDate.minute(0);
_date.hour(0);
_date.minute(0);
}
_maxDate.second(0);
_date.second(0);
_maxDate.millisecond(0);
_date.millisecond(0);
if (!checkMinute) {
_date.minute(0);
_maxDate.minute(0);
_return = (parseInt(_date.format("X")) <= parseInt(_maxDate.format("X")));
}
else {
_return = (parseInt(_date.format("X")) <= parseInt(_maxDate.format("X")));
}
}
return _return;
},
isInDisableDates: function (date) {
var dut = date.format('MMMM Do YYYY');
if (this.disableDates.indexOf(dut) > -1) {
return false;
}
return true;
},
isWeekDay: function(date) {
if (this.weekDays) {
if (date.isoWeekday() <= 5) {
return true;
}
return false;
}
return true;
},
selectDate: function (date) {
if (date) {
this.currentDate = moment(date);
if (!this.isAfterMinDate(this.currentDate, true, true)) {
this.currentDate = moment(this.minDate);
}
if (!this.isBeforeMaxDate(this.currentDate, true, true)) {
this.currentDate = moment(this.maxDate);
}
this.currentDate.locale(this.params.lang);
this.calendarStart = moment(this.currentDate);
this.meridien = this.currentDate.hour() >= 12 ? 'PM' : 'AM';
}
},
isPM: function () {
return this.meridien === 'PM';
},
incrementMonth: function (amount) {
if ((amount === 1 && this.isNextMonthVisible()) || (amount === -1 && this.isPreviousMonthVisible())) {
this.selectDate(this.currentDate.add(amount, 'month'));
}
},
incrementYear: function (amount) {
if ((amount === 1 && this.isNextYearVisible()) || (amount === -1 && this.isPreviousYearVisible())) {
this.selectDate(this.currentDate.add(amount, 'year'));
}
},
openMenu: function ($mdMenu, ev) {
$mdMenu.open(ev);
},
monthsAvailable: function () {
var monthsArr = [], _date = moment(this.currentDate);
for (var m = 0; m < 12; m++) {
var curMonth = _date.month(m);
if (this.isAfterMinDate(curMonth.endOf('month')) && this.isBeforeMaxDate(curMonth.startOf('month'))) {
monthsArr.push(curMonth.format('MMMM'));
}
}
return monthsArr;
},
selectMonth: function (month) {
this.selectDate(this.currentDate.month(month));
},
yearsAvailable: function () {
var _minDate, _maxDate, len, startYear, yearsArr = [],
_date = this.currentDate.year();
if (typeof(this.minDate) !== 'undefined' && this.minDate !== null) {
_minDate = moment(this.minDate).year();
}
if (typeof(this.maxDate) !== 'undefined' && this.maxDate !== null) {
_maxDate = moment(this.maxDate).year();
}
if (_maxDate && _minDate) {
len = _maxDate - _minDate;
startYear = _minDate;
} else if (_minDate) {
len = 115;
startYear = _minDate;
} else if (_maxDate) {
len = 30;
startYear = _maxDate - len;
} else {
len = 60;
startYear = _date - len/2;
}
for (var i=0; i <= len; i++) {
yearsArr.push(startYear+i);
}
return yearsArr;
},
selectYear: function (year) {
this.selectDate(this.currentDate.year(year));
},
isPreviousMonthVisible: function () {
return this.calendarStart && this.isAfterMinDate(moment(this.calendarStart).startOf('month'), false, false);
},
isNextMonthVisible: function () {
return this.calendarStart && this.isBeforeMaxDate(moment(this.calendarStart).endOf('month'), false, false);
},
isPreviousYearVisible: function () {
return this.calendarStart && this.isAfterMinDate(moment(this.calendarStart).startOf('year'), false, false);
},
isNextYearVisible: function () {
return this.calendarStart && this.isBeforeMaxDate(moment(this.calendarStart).endOf('year'), false, false);
},
isHourAvailable: function (hour) {
var _date = moment(this.currentDate);
if (this.params.shortTime) {
_date.hour(this.convertHours(hour)).minute(0).second(0);
} else {
_date.hour(hour).minute(0).second(0);
}
return this.isAfterMinDate(_date, true, false) && this.isBeforeMaxDate(_date, true, false);
},
isMinuteAvailable: function (minute) {
var _date = moment(this.currentDate);
_date.minute(minute).second(0);
var nearestMin = this.params.minuteSteps;
if (nearestMin > 1 && 5 % nearestMin !== 0){
var _curmin = _date.minute();
var minutes = (nearestMin * Math.round(_curmin / nearestMin));
if (minutes >= 60) minutes = 60 - nearestMin;
if (minutes !== _curmin) return false;
}
return this.isAfterMinDate(_date, true, true) && this.isBeforeMaxDate(_date, true, true);
},
isSecondAvailable: function (second) {
return true;
},
start: function () {
this.currentView = VIEW_STATES.DATE;
if (this.params.date) {
this.initDate();
} else {
if (this.params.time) {
this.initHours();
}
}
},
today: function () {
var nearestMin = this.params.minuteSteps;
var date = moment();
var minutes = (nearestMin * Math.round(date.minute() / nearestMin));
if (minutes >= 60) minutes = 60 - nearestMin;
var seconds;
if (this.params.seconds) {
seconds = date.second();
if (seconds >= 60) seconds = 60 - 1;
} else seconds = 0;
this.selectDate(moment(date).minutes(minutes).seconds(seconds).millisecond(0));
},
ok: function () {
switch (this.currentView) {
case VIEW_STATES.DATE:
if (this.params.time === true) {
this.initHours();
} else {
this.hide(true);
}
break;
case VIEW_STATES.HOUR:
if (this.params.minutes === true) {
this.initMinutes();
} else {
this.hide(true);
}
break;
case VIEW_STATES.MINUTE:
if (this.params.seconds === true) {
this.initSeconds();
} else {
this.hide(true);
}
break;
case VIEW_STATES.SECOND:
this.hide(true);
break;
}
},
cancel: function () {
if (this.params.time) {
switch (this.currentView) {
case VIEW_STATES.DATE:
this.hide();
break;
case VIEW_STATES.HOUR:
if (this.params.date) {
this.initDate();
}
else {
this.hide();
}
break;
case VIEW_STATES.MINUTE:
this.initHours();
break;
case VIEW_STATES.SECOND:
this.initMinutes();
break;
}
}
else {
this.hide();
}
},
selectMonthBefore: function () {
this.calendarStart.subtract(1, 'months');
},
selectMonthAfter: function () {
this.calendarStart.add(1, 'months');
},
selectYearBefore: function () {
this.calendarStart.subtract(1, 'years');
},
selectYearAfter: function () {
this.calendarStart.add(1, 'years');
},
selectAM: function () {
if (this.isHourAvailable(0) || this.isHourAvailable(12)) {
if (this.currentDate.hour() >= 12) {
this.selectDate(this.currentDate.subtract(12, 'hours'));
}
if (!this.isHourAvailable(this.currentDate.hour())) {
this.selectDate(this.currentDate.hour(this.minDate.hour()));
}
if (!this.isMinuteAvailable(this.currentDate.minute())) {
this.selectDate(this.currentDate.minute(this.minDate.minute()));
}
}
},
selectPM: function () {
if (this.isHourAvailable(12) || this.isHourAvailable(24)) {
if (this.currentDate.hour() < 12) {
this.selectDate(this.currentDate.add(12, 'hours'));
}
if (!this.isHourAvailable(this.currentDate.hour())) {
this.selectDate(this.currentDate.hour(this.maxDate.hour()));
}
if (!this.isMinuteAvailable(this.currentDate.minute())) {
this.selectDate(this.currentDate.minute(this.maxDate.minute()));
}
}
},
convertHours: function (h) {
var _return = h;
if (h < 12 && this.isPM()) _return += 12;
return _return;
},
hide: function (okBtn) {
if (okBtn) this._dialog.hide(this.currentDate);
else this._dialog.cancel();
}
};
angular.module(moduleName)
.directive('mdcDatetimePickerCalendar', [
function () {
var YEAR_MIN = 1920,
YEAR_MAX = new Date().getFullYear() + 30,
MONTHS_IN_ALL = (YEAR_MAX - YEAR_MIN + 1) * 12,
MONTHS = [];
for (var i = 0; i < MONTHS_IN_ALL; i++) {
MONTHS.push(i);
}
var currentMonthIndex = function (date, low) {
low = low ? low : 0;
return (((date.year() - YEAR_MIN) * 12) + date.month() - 1) - low;
};
return {
restrict: 'E',
scope: {
picker: '=',
date: '='
},
bindToController: true,
controllerAs: 'cal',
controller: ['$scope', function ($scope) {
var calendar = this, picker;
this.$onInit = function () {
picker = this.picker;
var days = [];
for (var i = picker.params.weekStart; days.length < 7; i++) {
if (i > 6) i = 0;
days.push(i.toString());
}
calendar.week = days;
if (!picker.maxDate && !picker.minDate) {
calendar.months = MONTHS;
} else {
var low = picker.minDate ? currentMonthIndex(picker.minDate) : 0;
var high = picker.maxDate ? (currentMonthIndex(picker.maxDate) +1) : MONTHS_IN_ALL;
calendar.months = MONTHS.slice(low, high);
}
calendar.topIndex = currentMonthIndex(picker.currentDate) - calendar.months[0];
};
if (angular.version.major === 1 && angular.version.minor < 5) this.$onInit();
calendar.getItemAtIndex = function (index) {
var month = ((index + 1) % 12) || 12;
var year = YEAR_MIN + Math.floor(index / 12);
var monthObj = moment(picker.currentDate).year(year).month(month);
return generateMonthCalendar(monthObj);
};
$scope.$watch(function () {
return picker.currentDate ? picker.currentDate.format('YYYY-MM') : '';
}, function (val2, val1) {
if (val2 != val1) {
var nDate = moment(val2, 'YYYY-MM');
var low = picker.minDate ? currentMonthIndex(picker.minDate): 0;
var index = currentMonthIndex(nDate, low);
if (calendar.topIndex != index) {
calendar.topIndex = index;
}
}
});
var generateMonthCalendar = function (date) {
var month = {};
if (date !== null) {
month.name = date.format('MMMM YYYY');
var startOfMonth = moment(date).locale(picker.params.lang).startOf('month')
.hour(date.hour())
.minute(date.minute());
var iNumDay = startOfMonth.format('d');
month.days = [];
for (var i = startOfMonth.date(); i <= startOfMonth.daysInMonth(); i++) {
if (i === startOfMonth.date()) {
var iWeek = calendar.week.indexOf(iNumDay.toString());
if (iWeek > 0) {
for (var x = 0; x < iWeek; x++) {
month.days.push(0);
}
}
}
month.days.push(moment(startOfMonth).locale(picker.params.lang).date(i));
}
var daysInAWeek = 7, daysTmp = [], slices = Math.ceil(month.days.length / daysInAWeek);
for (var j = 0; j < slices; j++) {
daysTmp.push(month.days.slice(j * daysInAWeek, (j + 1) * daysInAWeek));
}
month.days = daysTmp;
return month;
}
};
calendar.toDay = function (i) {
return moment(parseInt(i), "d")
.locale(picker.params.lang)
.format("ddd")
.substring(0, picker.params.dayOfWeekLen);
};
calendar.isInRange = function (date) {
return picker.isAfterMinDate(moment(date), false, false) &&
picker.isBeforeMaxDate(moment(date), false, false) &&
picker.isWeekDay(moment(date)) &&
picker.isInDisableDates(moment(date));
};
calendar.selectDate = function (date) {
if (date) {
if (calendar.isSelectedDay(date)) {
return picker.ok();
}
picker.selectDate(moment(date).hour(calendar.date.hour()).minute(calendar.date.minute()));
if (picker.params.autoOk) {
picker.ok();
}
}
};
calendar.isSelectedDay = function (m) {
return m && calendar.date.date() === m.date() && calendar.date.month() === m.month() && calendar.date.year() === m.year();
};
calendar.isDateOfTheDay = function (m) {
var today = calendar.picker.options.showTodaysDate;
if (!today) {
return false;
}
return m && today.date() === m.date() && today.month() === m.month() && today.year() === m.year();
};
}],
template:
'<md-virtual-repeat-container md-top-index="cal.topIndex" class="months">' +
'<div md-virtual-repeat="idx in cal.months">' +
' <div mdc-datetime-picker-calendar-month idx="idx"></div>' +
'</div>' +
'</md-virtual-repeat-container>'
};
}])
.directive('mdcDatetimePickerCalendarMonth', ['$compile',
function ($compile) {
var buildCalendarContent = function (element, scope) {
var tbody = angular.element(element[0].querySelector('tbody'));
var calendar = scope.cal, month = scope.month;
var tbodyHtml = [];
month.days.forEach(function (weekDays, i) {
tbodyHtml.push('<tr>');
weekDays.forEach(function (weekDay, j) {
tbodyHtml.push('<td>');
if (weekDay) {
if (calendar.isInRange(weekDay)) {
//build a
var scopeRef = 'month[\'days\'][' + i + '][' + j + ']';
tbodyHtml.push('<a id="date-' + weekDay.format('YYYY-MM-DD') + '" href="#" mdc-dtp-noclick class="dtp-select-day" ng-class="{selected: cal.isSelectedDay(' + scopeRef + '), hilite: cal.isDateOfTheDay(' + scopeRef + ')}" ng-click="cal.selectDate(' + scopeRef + ')">');
tbodyHtml.push(weekDay.format('D'));
tbodyHtml.push('</a>');
} else {
tbodyHtml.push('<span class="dtp-select-day">');
tbodyHtml.push(weekDay.format('D'));
tbodyHtml.push('</span>');
}
}
tbodyHtml.push('</td>');
});
tbodyHtml.push('</tr>');
});
tbody.html(tbodyHtml.join(''));
$compile(tbody)(scope);
};
return {
scope: {
idx: '='
},
require: '^mdcDatetimePickerCalendar',
restrict: 'AE',
template:
'<div class="dtp-picker-month">{{month.name}}</div>' +
'<table class="table dtp-picker-days">' +
' <thead>' +
' <tr>' +
' <th ng-repeat="day in cal.week track by $index">{{cal.toDay(day)}}</th>' +
' </tr>' +
' </thead>' +
' <tbody>' +
' </tbody>' +
'</table>',
link: function (scope, element, attrs, calendar) {
scope.cal = calendar;
scope.month = calendar.getItemAtIndex(parseInt(scope.idx));
buildCalendarContent(element, scope);
scope.$watch(function () {
return scope.idx;
}, function (idx, oldIdx) {
if (idx != oldIdx) {
scope.month = calendar.getItemAtIndex(parseInt(scope.idx));
buildCalendarContent(element, scope);
}
});
}
};
}
])
;
angular.module(moduleName)
.directive('mdcDtpNoclick', function () {
return {
link: function (scope, el) {
el.on('click', function (e) {
e.preventDefault();
});
el.on('dragstart', function (e) {
e.preventDefault();
});
}
};
});
angular.module(moduleName)
.directive('mdcDatetimePickerClock', ['$timeout',
function ($timeout) {
var template =
'<div id="timePicker" class="dtp-picker-clock"><span ng-if="!points||points.length < 1"> </span>' +
'<div ng-repeat="point in points" class="dtp-picker-time noselect" ng-style="point.style">' +
' <a href="#" id="time-{{mode}}-{{point.display}}" mdc-dtp-noclick ng-class="{selected:point.value===currentValue}" class="dtp-select-hour" ng-click="setTime(point.value)" ng-if="pointAvailable(point)">{{point.display}}</a>' +
' <a href="#" mdc-dtp-noclick class="disabled dtp-select-hour" ng-if="!pointAvailable(point)">{{point.display}}</a>' +
'</div>' +
'<div ng-if="points24.length" ng-repeat="point24 in points24" class="dtp-picker-time noselect" ng-style="point24.style">' +
' <a href="#" id="time-24hours-{{point24.display}}" mdc-dtp-noclick ng-class="{selected:point24.value===currentValue}" class="dtp-select-hour" ng-click="setTime(point24.value)" ng-if="pointAvailable(point24)">{{point24.display}}</a>' +
' <a href="#" mdc-dtp-noclick class="disabled dtp-select-hour" ng-if="!pointAvailable(point24)">{{point24.display}}</a>' +
'</div>' +
'<div class="dtp-hand dtp-hour-hand"></div>' +
'<div class="dtp-hand dtp-minute-hand"></div>' +
'<div ng-if="picker.params.seconds" class="dtp-hand dtp-second-hand"></div>' +
'<div class="dtp-clock-center"></div>' +
'</div>';
return {
restrict: 'E',
template: template,
link: function (scope, element, attrs) {
var minuteMode = attrs.mode === 'minutes';
var secondMode = attrs.mode === 'seconds';
var picker = scope.picker;
//banking on the fact that there will only be one at a time
var componentRoot = document.querySelector('md-dialog.dtp');
var setTimeDegRay = function(deg, ray) {
var val = 0;
deg = deg >= 360 ? 0 : deg;
if (deg !== 0) {
var divider = minuteMode||secondMode ? 60 : 12;
val = Math.round(divider / 360 * deg);
}
if (minuteMode) {
var nearestMin = picker.params.minuteSteps;
if (nearestMin < 1 || nearestMin > 59) nearestMin = 1;
var minutes = (nearestMin * Math.round(val / nearestMin));
if (minutes >= 60) minutes = 60 - nearestMin;
if (!scope.pointAvailable({value: val})) return;
picker.currentDate.minute(minutes);
} else if (!secondMode){
if (val === 12) val = 0;
if (!picker.params.shortTime) picker.meridien = ray > 84 ? 'AM' : 'PM';
if (picker.isPM()) val += 12;
if (!scope.pointAvailable({value: val})) return;
picker.currentDate.hour(val);
} else {
if (val >= 60) val = 0;
if (!scope.pointAvailable({value: val})) return;
picker.currentDate.second(val);
}
};
var isTouchSupported = ('ontouchstart' in window) ? true : false,
EVENTS = {
POINTER_DOWN : isTouchSupported ? 'touchstart' : 'mousedown',
POINTER_UP : isTouchSupported ? 'touchend' : 'mouseup',
POINTER_MOVE : isTouchSupported ? 'touchmove' : 'mousemove'
};
var onMoveEvent = function(e) {
e.preventDefault();
var closestTarget = e.currentTarget.closest('div'),
clientRect = closestTarget.getClientRects()[0];
if (isTouchSupported) e = e.changedTouches[0];
var x = ((closestTarget.offsetWidth / 2) - (e.pageX - clientRect.left)),
y = ((e.pageY - clientRect.top) - (closestTarget.offsetHeight / 2));
var ray = Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2)),
deg = Math.round((Math.atan2(x, y) * (180 / Math.PI)));
$timeout(function() {
setTimeDegRay(deg+180, ray);
});
};
element.on(EVENTS.POINTER_DOWN, function() {
element.on(EVENTS.POINTER_MOVE, onMoveEvent);
});
element.on(EVENTS.POINTER_UP, function() {
element.off(EVENTS.POINTER_MOVE);
});
scope.$on("$destroy", function() {
element.off(EVENTS.POINTER_MOVE, onMoveEvent);
});
var exec = function () {
var clock = angular.element(element[0].querySelector('.dtp-picker-clock')),
pickerEl = angular.element(componentRoot.querySelector('.dtp-picker'));
var w = componentRoot.querySelector('.dtp-content').offsetWidth;
var pL = parseInt(css(pickerEl, 'paddingLeft').replace('px', '')) || 0;
var pR = parseInt(css(pickerEl, 'paddingRight').replace('px', '')) || 0;
var pT = parseInt(css(pickerEl, 'paddingTop').replace('px', '')) || 0;
var mL = parseInt(css(clock, 'marginLeft').replace('px', '')) || 0;
var mR = parseInt(css(clock, 'marginRight').replace('px', '')) || 0;
var mT = parseInt(css(clock, 'marginTop').replace('px', '')) || 0;
//set width
var clockWidth = (w - (mL + mR + pL + pR));
clock.css('width', (clockWidth) + 'px');
var r = (clockWidth / 2);
var j = r / 1.2; // radius for low number
var points = [];
for (var h = 0; h < 12; ++h) {
var x = j * Math.sin(Math.PI * 2 * (h / 12));
var y = j * Math.cos(Math.PI * 2 * (h / 12));
var left = (r + x + pL / 2) - (pL + mL);
var top = (r - y - mT / 2) - (pT + mT);
var hour = {
left: left,
top: top,
value: (minuteMode||secondMode ? (h * 5) : h), //5 for minute 60/12
style: {'margin-left': left + 'px', 'margin-top': top + 'px'}
};
if (minuteMode || secondMode) {
hour.display = hour.value < 10 ? ('0' + hour.value) : hour.value;
} else {
if (picker.params.shortTime) {
hour.display = h === 0 ? 12 : h;
} else {
hour.display = h;
}
}
points.push(hour);
}
scope.points = points;
if (!picker.params.shortTime && !minuteMode && !secondMode) {
var points24 = [];
var j24 = r / 1.8; // radius for high number
for (var h24 = 12; h24 < 24; ++h24) {
var x24 = j24 * Math.sin(Math.PI * 2 * (h24 / 12));
var y24 = j24 * Math.cos(Math.PI * 2 * (h24 / 12));
var left24 = (r + x24 + pL / 2) - (pL + mL);
var top24 = (r - y24 - mT / 2) - (pT + mT);
points24.push({
left: left24,
top: top24,
value: h24,
display: h24,
style: {'margin-left': left24 + 'px', 'margin-top': top24 + 'px'}
});
}
scope.points24 = points24;
}
scope.mode = attrs.mode;
setCurrentValue();
clock.css('height', clockWidth + 'px');
var clockCenter = element[0].querySelector('.dtp-clock-center');
var centerWidth = (clockCenter.offsetWidth / 2) || 7.5;
var centerHeight = (clockCenter.offsetHeight / 2) || 7.5;
var _hL = r / (picker.params.shortTime ? 1.8 : 2.3);
var _mL = r / 1.4;
var _sL = r;