// https://github.com/Gillardo/bootstrap-ui-datetime-picker
// Version: 1.1.0
// Released: 2015-05-28
angular.module('ui.bootstrap.datetimepicker', ['ui.bootstrap.dateparser', 'ui.bootstrap.position'])
.constant('uiDatetimePickerConfig', {
dateFormat: 'yyyy-MM-dd HH:mm',
enableDate: true,
enableTime: true,
todayText: 'Today',
nowText: 'Now',
clearText: 'Clear',
closeText: 'Done',
dateText: 'Date',
timeText: 'Time',
closeOnDateSelection: true,
appendToBody: false,
showButtonBar: true
})
.directive('datetimePicker', ['$compile', '$parse', '$document', '$timeout', '$position', 'dateFilter', 'dateParser', 'uiDatetimePickerConfig',
function ($compile, $parse, $document, $timeout, $position, dateFilter, dateParser, uiDatetimePickerConfig) {
return {
restrict: 'A',
require: 'ngModel',
scope: {
isOpen: '=?',
enableDate: '=?',
enableTime: '=?',
todayText: '@',
nowText: '@',
dateText: '@',
timeText: '@',
clearText: '@',
closeText: '@',
dateDisabled: '&'
},
link: function (scope, element, attrs, ngModel) {
var dateFormat = uiDatetimePickerConfig.dateFormat, currentDate,
closeOnDateSelection = angular.isDefined(attrs.closeOnDateSelection) ? scope.$parent.$eval(attrs.closeOnDateSelection) : uiDatetimePickerConfig.closeOnDateSelection,
appendToBody = angular.isDefined(attrs.datepickerAppendToBody) ? scope.$parent.$eval(attrs.datepickerAppendToBody) : uiDatetimePickerConfig.appendToBody;
scope.showButtonBar = angular.isDefined(attrs.showButtonBar) ? scope.$parent.$eval(attrs.showButtonBar) : uiDatetimePickerConfig.showButtonBar;
// determine which pickers should be available. Defaults to date and time
scope.enableDate = angular.isDefined(scope.enableDate) ? scope.enableDate : uiDatetimePickerConfig.enableDate;
scope.enableTime = angular.isDefined(scope.enableTime) ? scope.enableTime : uiDatetimePickerConfig.enableTime;
// default picker view
scope.showPicker = scope.enableDate ? 'date' : 'time';
// default text
scope.todayText = scope.todayText || 'Today';
scope.nowText = scope.nowText || 'Now';
scope.clearText = scope.clearText || 'Clear';
scope.closeText = scope.closeText || 'Close';
scope.dateText = scope.dateText || 'Date';
scope.timeText = scope.timeText || 'Time';
scope.getText = function (key) {
return scope[key + 'Text'] || uiDatetimePickerConfig[key + 'Text'];
};
attrs.$observe('datetimePicker', function (value) {
dateFormat = value || uiDatetimePickerConfig.dateFormat;
ngModel.$render();
});
// popup element used to display calendar
var popupEl = angular.element('' +
'
' +
'');
// get attributes from directive
popupEl.attr({
'ng-model': 'date',
'ng-change': 'dateSelection()'
});
function cameltoDash(string) {
return string.replace(/([A-Z])/g, function ($1) { return '-' + $1.toLowerCase(); });
}
// datepicker element
var datepickerEl = angular.element(popupEl.children()[0]);
if (attrs.datepickerOptions) {
angular.forEach(scope.$parent.$eval(attrs.datepickerOptions), function (value, option) {
datepickerEl.attr(cameltoDash(option), value);
});
}
// timepicker element
var timepickerEl = angular.element(popupEl.children()[1]);
if (attrs.timepickerOptions) {
angular.forEach(scope.$parent.$eval(attrs.timepickerOptions), function (value, option) {
timepickerEl.attr(cameltoDash(option), value);
});
}
// set datepickerMode to day by default as need to create watch
// this gets round issue#5 where by the highlight is not shown
if (!attrs['datepickerMode']) attrs['datepickerMode'] = 'day';
scope.watchData = {};
angular.forEach(['minDate', 'maxDate', 'datepickerMode'], function (key) {
if (attrs[key]) {
var getAttribute = $parse(attrs[key]);
scope.$parent.$watch(getAttribute, function (value) {
scope.watchData[key] = value;
});
datepickerEl.attr(cameltoDash(key), 'watchData.' + key);
// Propagate changes from datepicker to outside
if (key === 'datepickerMode') {
var setAttribute = getAttribute.assign;
scope.$watch('watchData.' + key, function (value, oldvalue) {
if (value !== oldvalue) {
setAttribute(scope.$parent, value);
}
});
}
}
});
if (attrs.dateDisabled) {
datepickerEl.attr('date-disabled', 'dateDisabled({ date: date, mode: mode })');
}
function isDateDisabled(dt) {
return attrs.dateDisabled && angular.isDefined(dt) && scope.dateDisabled({ date: dt, mode: scope.watchData['datepickerMode']});
}
function parseDate(viewValue) {
if (!viewValue) {
ngModel.$setValidity('date', true);
return null;
} else if (angular.isDate(viewValue) && !isNaN(viewValue)) {
ngModel.$setValidity('date', true);
return viewValue;
} else if (angular.isString(viewValue)) {
var date = dateParser.parse(viewValue, dateFormat) || new Date(viewValue);
if (isNaN(date)) {
ngModel.$setValidity('date', false);
return undefined;
} else {
ngModel.$setValidity('date', true);
return date;
}
} else {
ngModel.$setValidity('date', false);
return undefined;
}
}
ngModel.$parsers.unshift(parseDate);
// Inner change
scope.dateSelection = function (dt) {
// check which picker is being shown, if its date, all is fine and this is the date
// we will use, if its the timePicker but enableDate = true, we need to merge
// the values, else timePicker will reset the date
if (scope.enableDate && scope.enableTime && scope.showPicker === 'time') {
if (currentDate && currentDate !== null && (scope.date !== null || dt || dt != null)) {
// dt will not be undefined if the now or today button is pressed
if (dt && dt != null) {
currentDate.setHours(dt.getHours());
currentDate.setMinutes(dt.getMinutes());
currentDate.setSeconds(dt.getSeconds());
currentDate.setMilliseconds(dt.getMilliseconds());
dt = new Date(currentDate);
} else {
currentDate.setHours(scope.date.getHours());
currentDate.setMinutes(scope.date.getMinutes());
currentDate.setSeconds(scope.date.getSeconds());
currentDate.setMilliseconds(scope.date.getMilliseconds());
}
}
}
if (angular.isDefined(dt)) {
scope.date = dt;
}
// store currentDate
currentDate = scope.date;
ngModel.$setViewValue(scope.date);
ngModel.$render();
if (closeOnDateSelection) {
// do not close when using timePicker
if (scope.showPicker != 'time') {
// if time is enabled, swap to timePicker
if (scope.enableTime) {
scope.showPicker = 'time';
} else {
scope.isOpen = false;
element[0].focus();
}
}
}
};
element.bind('input change keyup', function () {
scope.$apply(function () {
scope.date = ngModel.$modelValue;
});
});
// Outer change
ngModel.$render = function () {
var date = ngModel.$viewValue ? parseDate(ngModel.$viewValue) : null;
var display = date ? dateFilter(date, dateFormat) : '';
element.val(display);
scope.date = date;
};
var documentClickBind = function (event) {
if (scope.isOpen && event.target !== element[0]) {
scope.$apply(function () {
scope.isOpen = false;
});
}
};
var keydown = function (evt, noApply) {
scope.keydown(evt);
};
element.bind('keydown', keydown);
scope.keydown = function (evt) {
if (evt.which === 27) {
evt.preventDefault();
if (scope.isOpen) {
evt.stopPropagation();
}
scope.close();
} else if (evt.which === 40 && !scope.isOpen) {
scope.isOpen = true;
}
};
scope.$watch('isOpen', function (value) {
if (value) {
scope.$broadcast('datepicker.focus');
scope.position = appendToBody ? $position.offset(element) : $position.position(element);
scope.position.top = scope.position.top + element.prop('offsetHeight');
$document.bind('mousedown', documentClickBind);
} else {
$document.unbind('mousedown', documentClickBind);
}
});
scope.isTodayDisabled = function() {
return isDateDisabled(new Date());
};
scope.select = function (date) {
if (date === 'today' || date == 'now') {
var now = new Date();
if (angular.isDate(ngModel.$modelValue)) {
date = new Date(ngModel.$modelValue);
date.setFullYear(now.getFullYear(), now.getMonth(), now.getDate());
date.setHours(now.getHours(), now.getMinutes(), now.getSeconds(), now.getMilliseconds());
} else {
date = now;
}
}
scope.dateSelection(date);
};
scope.close = function () {
scope.isOpen = false;
element[0].focus();
};
scope.changePicker = function (e) {
scope.showPicker = e;
};
var $popup = $compile(popupEl)(scope);
// Prevent jQuery cache memory leak (template is now redundant after linking)
popupEl.remove();
if (appendToBody) {
$document.find('body').append($popup);
} else {
element.after($popup);
}
scope.$on('$destroy', function () {
$popup.remove();
element.unbind('keydown', keydown);
$document.unbind('mousedown', documentClickBind);
});
}
};
}])
.directive('datePickerWrap', function () {
return {
restrict: 'EA',
replace: true,
transclude: true,
templateUrl: 'template/datetime-picker.html',
link: function (scope, element, attrs) {
element.bind('mousedown', function (event) {
event.preventDefault();
event.stopPropagation();
});
}
};
})
.directive('timePickerWrap', function () {
return {
restrict: 'EA',
replace: true,
transclude: true,
templateUrl: 'template/datetime-picker.html',
link: function (scope, element, attrs) {
element.bind('mousedown', function (event) {
event.preventDefault();
event.stopPropagation();
});
}
};
});
angular.module('ui.bootstrap.datetimepicker').run(['$templateCache', function($templateCache) {
'use strict';
$templateCache.put('template/datetime-picker.html',
""
);
}]);