angularjs-datetime-picker.js 14 KB

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