datepicker.js 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605
  1. /**
  2. * angular-strap
  3. * @version v2.3.9 - 2016-06-10
  4. * @link http://mgcrea.github.io/angular-strap
  5. * @author Olivier Louvignes <olivier@mg-crea.com> (https://github.com/mgcrea)
  6. * @license MIT License, http://www.opensource.org/licenses/MIT
  7. */
  8. 'use strict';
  9. angular.module('mgcrea.ngStrap.datepicker', [ 'mgcrea.ngStrap.helpers.dateParser', 'mgcrea.ngStrap.helpers.dateFormatter', 'mgcrea.ngStrap.tooltip' ]).provider('$datepicker', function() {
  10. var defaults = this.defaults = {
  11. animation: 'am-fade',
  12. prefixClass: 'datepicker',
  13. placement: 'bottom-left',
  14. templateUrl: 'datepicker/datepicker.tpl.html',
  15. trigger: 'focus',
  16. container: false,
  17. keyboard: true,
  18. html: false,
  19. delay: 0,
  20. useNative: false,
  21. dateType: 'date',
  22. dateFormat: 'shortDate',
  23. timezone: null,
  24. modelDateFormat: null,
  25. dayFormat: 'dd',
  26. monthFormat: 'MMM',
  27. yearFormat: 'yyyy',
  28. monthTitleFormat: 'MMMM yyyy',
  29. yearTitleFormat: 'yyyy',
  30. strictFormat: false,
  31. autoclose: false,
  32. minDate: -Infinity,
  33. maxDate: +Infinity,
  34. startView: 0,
  35. minView: 0,
  36. startWeek: 0,
  37. daysOfWeekDisabled: '',
  38. hasToday: false,
  39. hasClear: false,
  40. iconLeft: 'glyphicon glyphicon-chevron-left',
  41. iconRight: 'glyphicon glyphicon-chevron-right'
  42. };
  43. this.$get = [ '$window', '$document', '$rootScope', '$sce', '$dateFormatter', 'datepickerViews', '$tooltip', '$timeout', function($window, $document, $rootScope, $sce, $dateFormatter, datepickerViews, $tooltip, $timeout) {
  44. var isNative = /(ip[ao]d|iphone|android)/gi.test($window.navigator.userAgent);
  45. var isTouch = 'createTouch' in $window.document && isNative;
  46. if (!defaults.lang) defaults.lang = $dateFormatter.getDefaultLocale();
  47. function DatepickerFactory(element, controller, config) {
  48. var $datepicker = $tooltip(element, angular.extend({}, defaults, config));
  49. var parentScope = config.scope;
  50. var options = $datepicker.$options;
  51. var scope = $datepicker.$scope;
  52. if (options.startView) options.startView -= options.minView;
  53. var pickerViews = datepickerViews($datepicker);
  54. $datepicker.$views = pickerViews.views;
  55. var viewDate = pickerViews.viewDate;
  56. scope.$mode = options.startView;
  57. scope.$iconLeft = options.iconLeft;
  58. scope.$iconRight = options.iconRight;
  59. scope.$hasToday = options.hasToday;
  60. scope.$hasClear = options.hasClear;
  61. var $picker = $datepicker.$views[scope.$mode];
  62. scope.$select = function(date) {
  63. $datepicker.select(date);
  64. };
  65. scope.$selectPane = function(value) {
  66. $datepicker.$selectPane(value);
  67. };
  68. scope.$toggleMode = function() {
  69. $datepicker.setMode((scope.$mode + 1) % $datepicker.$views.length);
  70. };
  71. scope.$setToday = function() {
  72. if (options.autoclose) {
  73. $datepicker.setMode(0);
  74. $datepicker.select(new Date());
  75. } else {
  76. $datepicker.select(new Date(), true);
  77. }
  78. };
  79. scope.$clear = function() {
  80. if (options.autoclose) {
  81. $datepicker.setMode(0);
  82. $datepicker.select(null);
  83. } else {
  84. $datepicker.select(null, true);
  85. }
  86. };
  87. $datepicker.update = function(date) {
  88. if (angular.isDate(date) && !isNaN(date.getTime())) {
  89. $datepicker.$date = date;
  90. $picker.update.call($picker, date);
  91. }
  92. $datepicker.$build(true);
  93. };
  94. $datepicker.updateDisabledDates = function(dateRanges) {
  95. options.disabledDateRanges = dateRanges;
  96. for (var i = 0, l = scope.rows.length; i < l; i++) {
  97. angular.forEach(scope.rows[i], $datepicker.$setDisabledEl);
  98. }
  99. };
  100. $datepicker.select = function(date, keep) {
  101. if (angular.isDate(date)) {
  102. if (!angular.isDate(controller.$dateValue) || isNaN(controller.$dateValue.getTime())) {
  103. controller.$dateValue = new Date(date);
  104. }
  105. } else {
  106. controller.$dateValue = null;
  107. }
  108. if (!scope.$mode || keep) {
  109. controller.$setViewValue(angular.copy(date));
  110. controller.$render();
  111. if (options.autoclose && !keep) {
  112. $timeout(function() {
  113. $datepicker.hide(true);
  114. });
  115. }
  116. } else {
  117. angular.extend(viewDate, {
  118. year: date.getFullYear(),
  119. month: date.getMonth(),
  120. date: date.getDate()
  121. });
  122. $datepicker.setMode(scope.$mode - 1);
  123. $datepicker.$build();
  124. }
  125. };
  126. $datepicker.setMode = function(mode) {
  127. scope.$mode = mode;
  128. $picker = $datepicker.$views[scope.$mode];
  129. $datepicker.$build();
  130. };
  131. $datepicker.$build = function(pristine) {
  132. if (pristine === true && $picker.built) return;
  133. if (pristine === false && !$picker.built) return;
  134. $picker.build.call($picker);
  135. };
  136. $datepicker.$updateSelected = function() {
  137. for (var i = 0, l = scope.rows.length; i < l; i++) {
  138. angular.forEach(scope.rows[i], updateSelected);
  139. }
  140. };
  141. $datepicker.$isSelected = function(date) {
  142. return $picker.isSelected(date);
  143. };
  144. $datepicker.$setDisabledEl = function(el) {
  145. el.disabled = $picker.isDisabled(el.date);
  146. };
  147. $datepicker.$selectPane = function(value) {
  148. var steps = $picker.steps;
  149. var targetDate = new Date(Date.UTC(viewDate.year + (steps.year || 0) * value, viewDate.month + (steps.month || 0) * value, 1));
  150. angular.extend(viewDate, {
  151. year: targetDate.getUTCFullYear(),
  152. month: targetDate.getUTCMonth(),
  153. date: targetDate.getUTCDate()
  154. });
  155. $datepicker.$build();
  156. };
  157. $datepicker.$onMouseDown = function(evt) {
  158. evt.preventDefault();
  159. evt.stopPropagation();
  160. if (isTouch) {
  161. var targetEl = angular.element(evt.target);
  162. if (targetEl[0].nodeName.toLowerCase() !== 'button') {
  163. targetEl = targetEl.parent();
  164. }
  165. targetEl.triggerHandler('click');
  166. }
  167. };
  168. $datepicker.$onKeyDown = function(evt) {
  169. if (!/(38|37|39|40|13)/.test(evt.keyCode) || evt.shiftKey || evt.altKey) return;
  170. evt.preventDefault();
  171. evt.stopPropagation();
  172. if (evt.keyCode === 13) {
  173. if (!scope.$mode) {
  174. $datepicker.hide(true);
  175. } else {
  176. scope.$apply(function() {
  177. $datepicker.setMode(scope.$mode - 1);
  178. });
  179. }
  180. return;
  181. }
  182. $picker.onKeyDown(evt);
  183. parentScope.$digest();
  184. };
  185. function updateSelected(el) {
  186. el.selected = $datepicker.$isSelected(el.date);
  187. }
  188. function focusElement() {
  189. element[0].focus();
  190. }
  191. var _init = $datepicker.init;
  192. $datepicker.init = function() {
  193. if (isNative && options.useNative) {
  194. element.prop('type', 'date');
  195. element.css('-webkit-appearance', 'textfield');
  196. return;
  197. } else if (isTouch) {
  198. element.prop('type', 'text');
  199. element.attr('readonly', 'true');
  200. element.on('click', focusElement);
  201. }
  202. _init();
  203. };
  204. var _destroy = $datepicker.destroy;
  205. $datepicker.destroy = function() {
  206. if (isNative && options.useNative) {
  207. element.off('click', focusElement);
  208. }
  209. _destroy();
  210. };
  211. var _show = $datepicker.show;
  212. $datepicker.show = function() {
  213. if (!isTouch && element.attr('readonly') || element.attr('disabled')) return;
  214. _show();
  215. $timeout(function() {
  216. if (!$datepicker.$isShown) return;
  217. $datepicker.$element.on(isTouch ? 'touchstart' : 'mousedown', $datepicker.$onMouseDown);
  218. if (options.keyboard) {
  219. element.on('keydown', $datepicker.$onKeyDown);
  220. }
  221. }, 0, false);
  222. };
  223. var _hide = $datepicker.hide;
  224. $datepicker.hide = function(blur) {
  225. if (!$datepicker.$isShown) return;
  226. $datepicker.$element.off(isTouch ? 'touchstart' : 'mousedown', $datepicker.$onMouseDown);
  227. if (options.keyboard) {
  228. element.off('keydown', $datepicker.$onKeyDown);
  229. }
  230. _hide(blur);
  231. };
  232. return $datepicker;
  233. }
  234. DatepickerFactory.defaults = defaults;
  235. return DatepickerFactory;
  236. } ];
  237. }).directive('bsDatepicker', [ '$window', '$parse', '$q', '$dateFormatter', '$dateParser', '$datepicker', function($window, $parse, $q, $dateFormatter, $dateParser, $datepicker) {
  238. var isNative = /(ip[ao]d|iphone|android)/gi.test($window.navigator.userAgent);
  239. return {
  240. restrict: 'EAC',
  241. require: 'ngModel',
  242. link: function postLink(scope, element, attr, controller) {
  243. var options = {
  244. scope: scope
  245. };
  246. angular.forEach([ 'template', 'templateUrl', 'controller', 'controllerAs', 'placement', 'container', 'delay', 'trigger', 'html', 'animation', 'autoclose', 'dateType', 'dateFormat', 'timezone', 'modelDateFormat', 'dayFormat', 'strictFormat', 'startWeek', 'startDate', 'useNative', 'lang', 'startView', 'minView', 'iconLeft', 'iconRight', 'daysOfWeekDisabled', 'id', 'prefixClass', 'prefixEvent', 'hasToday', 'hasClear' ], function(key) {
  247. if (angular.isDefined(attr[key])) options[key] = attr[key];
  248. });
  249. var falseValueRegExp = /^(false|0|)$/i;
  250. angular.forEach([ 'html', 'container', 'autoclose', 'useNative', 'hasToday', 'hasClear' ], function(key) {
  251. if (angular.isDefined(attr[key]) && falseValueRegExp.test(attr[key])) {
  252. options[key] = false;
  253. }
  254. });
  255. angular.forEach([ 'onBeforeShow', 'onShow', 'onBeforeHide', 'onHide' ], function(key) {
  256. var bsKey = 'bs' + key.charAt(0).toUpperCase() + key.slice(1);
  257. if (angular.isDefined(attr[bsKey])) {
  258. options[key] = scope.$eval(attr[bsKey]);
  259. }
  260. });
  261. var datepicker = $datepicker(element, controller, options);
  262. options = datepicker.$options;
  263. if (isNative && options.useNative) options.dateFormat = 'yyyy-MM-dd';
  264. var lang = options.lang;
  265. var formatDate = function(date, format) {
  266. return $dateFormatter.formatDate(date, format, lang);
  267. };
  268. var dateParser = $dateParser({
  269. format: options.dateFormat,
  270. lang: lang,
  271. strict: options.strictFormat
  272. });
  273. if (attr.bsShow) {
  274. scope.$watch(attr.bsShow, function(newValue, oldValue) {
  275. if (!datepicker || !angular.isDefined(newValue)) return;
  276. if (angular.isString(newValue)) newValue = !!newValue.match(/true|,?(datepicker),?/i);
  277. if (newValue === true) {
  278. datepicker.show();
  279. } else {
  280. datepicker.hide();
  281. }
  282. });
  283. }
  284. angular.forEach([ 'minDate', 'maxDate' ], function(key) {
  285. if (angular.isDefined(attr[key])) {
  286. attr.$observe(key, function(newValue) {
  287. datepicker.$options[key] = dateParser.getDateForAttribute(key, newValue);
  288. if (!isNaN(datepicker.$options[key])) datepicker.$build(false);
  289. validateAgainstMinMaxDate(controller.$dateValue);
  290. });
  291. }
  292. });
  293. if (angular.isDefined(attr.dateFormat)) {
  294. attr.$observe('dateFormat', function(newValue) {
  295. datepicker.$options.dateFormat = newValue;
  296. });
  297. }
  298. scope.$watch(attr.ngModel, function(newValue, oldValue) {
  299. datepicker.update(controller.$dateValue);
  300. }, true);
  301. function normalizeDateRanges(ranges) {
  302. if (!ranges || !ranges.length) return null;
  303. return ranges;
  304. }
  305. if (angular.isDefined(attr.disabledDates)) {
  306. scope.$watch(attr.disabledDates, function(disabledRanges, previousValue) {
  307. disabledRanges = normalizeDateRanges(disabledRanges);
  308. previousValue = normalizeDateRanges(previousValue);
  309. if (disabledRanges) {
  310. datepicker.updateDisabledDates(disabledRanges);
  311. }
  312. });
  313. }
  314. function validateAgainstMinMaxDate(parsedDate) {
  315. if (!angular.isDate(parsedDate)) return;
  316. var isMinValid = isNaN(datepicker.$options.minDate) || parsedDate.getTime() >= datepicker.$options.minDate;
  317. var isMaxValid = isNaN(datepicker.$options.maxDate) || parsedDate.getTime() <= datepicker.$options.maxDate;
  318. var isValid = isMinValid && isMaxValid;
  319. controller.$setValidity('date', isValid);
  320. controller.$setValidity('min', isMinValid);
  321. controller.$setValidity('max', isMaxValid);
  322. if (isValid) controller.$dateValue = parsedDate;
  323. }
  324. controller.$parsers.unshift(function(viewValue) {
  325. var date;
  326. if (!viewValue) {
  327. controller.$setValidity('date', true);
  328. return null;
  329. }
  330. var parsedDate = dateParser.parse(viewValue, controller.$dateValue);
  331. if (!parsedDate || isNaN(parsedDate.getTime())) {
  332. controller.$setValidity('date', false);
  333. return;
  334. }
  335. validateAgainstMinMaxDate(parsedDate);
  336. if (options.dateType === 'string') {
  337. date = dateParser.timezoneOffsetAdjust(parsedDate, options.timezone, true);
  338. return formatDate(date, options.modelDateFormat || options.dateFormat);
  339. }
  340. date = dateParser.timezoneOffsetAdjust(controller.$dateValue, options.timezone, true);
  341. if (options.dateType === 'number') {
  342. return date.getTime();
  343. } else if (options.dateType === 'unix') {
  344. return date.getTime() / 1e3;
  345. } else if (options.dateType === 'iso') {
  346. return date.toISOString();
  347. }
  348. return new Date(date);
  349. });
  350. controller.$formatters.push(function(modelValue) {
  351. var date;
  352. if (angular.isUndefined(modelValue) || modelValue === null) {
  353. date = NaN;
  354. } else if (angular.isDate(modelValue)) {
  355. date = modelValue;
  356. } else if (options.dateType === 'string') {
  357. date = dateParser.parse(modelValue, null, options.modelDateFormat);
  358. } else if (options.dateType === 'unix') {
  359. date = new Date(modelValue * 1e3);
  360. } else {
  361. date = new Date(modelValue);
  362. }
  363. controller.$dateValue = dateParser.timezoneOffsetAdjust(date, options.timezone);
  364. return getDateFormattedString();
  365. });
  366. controller.$render = function() {
  367. element.val(getDateFormattedString());
  368. };
  369. function getDateFormattedString() {
  370. return !controller.$dateValue || isNaN(controller.$dateValue.getTime()) ? '' : formatDate(controller.$dateValue, options.dateFormat);
  371. }
  372. scope.$on('$destroy', function() {
  373. if (datepicker) datepicker.destroy();
  374. options = null;
  375. datepicker = null;
  376. });
  377. }
  378. };
  379. } ]).provider('datepickerViews', function() {
  380. function split(arr, size) {
  381. var arrays = [];
  382. while (arr.length > 0) {
  383. arrays.push(arr.splice(0, size));
  384. }
  385. return arrays;
  386. }
  387. function mod(n, m) {
  388. return (n % m + m) % m;
  389. }
  390. this.$get = [ '$dateFormatter', '$dateParser', '$sce', function($dateFormatter, $dateParser, $sce) {
  391. return function(picker) {
  392. var scope = picker.$scope;
  393. var options = picker.$options;
  394. var lang = options.lang;
  395. var formatDate = function(date, format) {
  396. return $dateFormatter.formatDate(date, format, lang);
  397. };
  398. var dateParser = $dateParser({
  399. format: options.dateFormat,
  400. lang: lang,
  401. strict: options.strictFormat
  402. });
  403. var weekDaysMin = $dateFormatter.weekdaysShort(lang);
  404. var weekDaysLabels = weekDaysMin.slice(options.startWeek).concat(weekDaysMin.slice(0, options.startWeek));
  405. var weekDaysLabelsHtml = $sce.trustAsHtml('<th class="dow text-center">' + weekDaysLabels.join('</th><th class="dow text-center">') + '</th>');
  406. var startDate = picker.$date || (options.startDate ? dateParser.getDateForAttribute('startDate', options.startDate) : new Date());
  407. var viewDate = {
  408. year: startDate.getFullYear(),
  409. month: startDate.getMonth(),
  410. date: startDate.getDate()
  411. };
  412. var views = [ {
  413. format: options.dayFormat,
  414. split: 7,
  415. steps: {
  416. month: 1
  417. },
  418. update: function(date, force) {
  419. if (!this.built || force || date.getFullYear() !== viewDate.year || date.getMonth() !== viewDate.month) {
  420. angular.extend(viewDate, {
  421. year: picker.$date.getFullYear(),
  422. month: picker.$date.getMonth(),
  423. date: picker.$date.getDate()
  424. });
  425. picker.$build();
  426. } else if (date.getDate() !== viewDate.date || date.getDate() === 1) {
  427. viewDate.date = picker.$date.getDate();
  428. picker.$updateSelected();
  429. }
  430. },
  431. build: function() {
  432. var firstDayOfMonth = new Date(viewDate.year, viewDate.month, 1);
  433. var firstDayOfMonthOffset = firstDayOfMonth.getTimezoneOffset();
  434. var firstDate = new Date(+firstDayOfMonth - mod(firstDayOfMonth.getDay() - options.startWeek, 7) * 864e5);
  435. var firstDateOffset = firstDate.getTimezoneOffset();
  436. var today = dateParser.timezoneOffsetAdjust(new Date(), options.timezone).toDateString();
  437. if (firstDateOffset !== firstDayOfMonthOffset) firstDate = new Date(+firstDate + (firstDateOffset - firstDayOfMonthOffset) * 6e4);
  438. var days = [];
  439. var day;
  440. for (var i = 0; i < 42; i++) {
  441. day = dateParser.daylightSavingAdjust(new Date(firstDate.getFullYear(), firstDate.getMonth(), firstDate.getDate() + i));
  442. days.push({
  443. date: day,
  444. isToday: day.toDateString() === today,
  445. label: formatDate(day, this.format),
  446. selected: picker.$date && this.isSelected(day),
  447. muted: day.getMonth() !== viewDate.month,
  448. disabled: this.isDisabled(day)
  449. });
  450. }
  451. scope.title = formatDate(firstDayOfMonth, options.monthTitleFormat);
  452. scope.showLabels = true;
  453. scope.labels = weekDaysLabelsHtml;
  454. scope.rows = split(days, this.split);
  455. scope.isTodayDisabled = this.isDisabled(new Date());
  456. this.built = true;
  457. },
  458. isSelected: function(date) {
  459. return picker.$date && date.getFullYear() === picker.$date.getFullYear() && date.getMonth() === picker.$date.getMonth() && date.getDate() === picker.$date.getDate();
  460. },
  461. isDisabled: function(date) {
  462. var time = date.getTime();
  463. if (time < options.minDate || time > options.maxDate) return true;
  464. if (options.daysOfWeekDisabled.indexOf(date.getDay()) !== -1) return true;
  465. if (options.disabledDateRanges) {
  466. for (var i = 0; i < options.disabledDateRanges.length; i++) {
  467. if (time >= options.disabledDateRanges[i].start && time <= options.disabledDateRanges[i].end) {
  468. return true;
  469. }
  470. }
  471. }
  472. return false;
  473. },
  474. onKeyDown: function(evt) {
  475. if (!picker.$date) {
  476. return;
  477. }
  478. var actualTime = picker.$date.getTime();
  479. var newDate;
  480. if (evt.keyCode === 37) newDate = new Date(actualTime - 1 * 864e5); else if (evt.keyCode === 38) newDate = new Date(actualTime - 7 * 864e5); else if (evt.keyCode === 39) newDate = new Date(actualTime + 1 * 864e5); else if (evt.keyCode === 40) newDate = new Date(actualTime + 7 * 864e5);
  481. if (!this.isDisabled(newDate)) picker.select(newDate, true);
  482. }
  483. }, {
  484. name: 'month',
  485. format: options.monthFormat,
  486. split: 4,
  487. steps: {
  488. year: 1
  489. },
  490. update: function(date, force) {
  491. if (!this.built || date.getFullYear() !== viewDate.year) {
  492. angular.extend(viewDate, {
  493. year: picker.$date.getFullYear(),
  494. month: picker.$date.getMonth(),
  495. date: picker.$date.getDate()
  496. });
  497. picker.$build();
  498. } else if (date.getMonth() !== viewDate.month) {
  499. angular.extend(viewDate, {
  500. month: picker.$date.getMonth(),
  501. date: picker.$date.getDate()
  502. });
  503. picker.$updateSelected();
  504. }
  505. },
  506. build: function() {
  507. var months = [];
  508. var month;
  509. for (var i = 0; i < 12; i++) {
  510. month = new Date(viewDate.year, i, 1);
  511. months.push({
  512. date: month,
  513. label: formatDate(month, this.format),
  514. selected: picker.$isSelected(month),
  515. disabled: this.isDisabled(month)
  516. });
  517. }
  518. scope.title = formatDate(month, options.yearTitleFormat);
  519. scope.showLabels = false;
  520. scope.rows = split(months, this.split);
  521. this.built = true;
  522. },
  523. isSelected: function(date) {
  524. return picker.$date && date.getFullYear() === picker.$date.getFullYear() && date.getMonth() === picker.$date.getMonth();
  525. },
  526. isDisabled: function(date) {
  527. var lastDate = +new Date(date.getFullYear(), date.getMonth() + 1, 0);
  528. return lastDate < options.minDate || date.getTime() > options.maxDate;
  529. },
  530. onKeyDown: function(evt) {
  531. if (!picker.$date) {
  532. return;
  533. }
  534. var actualMonth = picker.$date.getMonth();
  535. var newDate = new Date(picker.$date);
  536. if (evt.keyCode === 37) newDate.setMonth(actualMonth - 1); else if (evt.keyCode === 38) newDate.setMonth(actualMonth - 4); else if (evt.keyCode === 39) newDate.setMonth(actualMonth + 1); else if (evt.keyCode === 40) newDate.setMonth(actualMonth + 4);
  537. if (!this.isDisabled(newDate)) picker.select(newDate, true);
  538. }
  539. }, {
  540. name: 'year',
  541. format: options.yearFormat,
  542. split: 4,
  543. steps: {
  544. year: 12
  545. },
  546. update: function(date, force) {
  547. if (!this.built || force || parseInt(date.getFullYear() / 20, 10) !== parseInt(viewDate.year / 20, 10)) {
  548. angular.extend(viewDate, {
  549. year: picker.$date.getFullYear(),
  550. month: picker.$date.getMonth(),
  551. date: picker.$date.getDate()
  552. });
  553. picker.$build();
  554. } else if (date.getFullYear() !== viewDate.year) {
  555. angular.extend(viewDate, {
  556. year: picker.$date.getFullYear(),
  557. month: picker.$date.getMonth(),
  558. date: picker.$date.getDate()
  559. });
  560. picker.$updateSelected();
  561. }
  562. },
  563. build: function() {
  564. var firstYear = viewDate.year - viewDate.year % (this.split * 3);
  565. var years = [];
  566. var year;
  567. for (var i = 0; i < 12; i++) {
  568. year = new Date(firstYear + i, 0, 1);
  569. years.push({
  570. date: year,
  571. label: formatDate(year, this.format),
  572. selected: picker.$isSelected(year),
  573. disabled: this.isDisabled(year)
  574. });
  575. }
  576. scope.title = years[0].label + '-' + years[years.length - 1].label;
  577. scope.showLabels = false;
  578. scope.rows = split(years, this.split);
  579. this.built = true;
  580. },
  581. isSelected: function(date) {
  582. return picker.$date && date.getFullYear() === picker.$date.getFullYear();
  583. },
  584. isDisabled: function(date) {
  585. var lastDate = +new Date(date.getFullYear() + 1, 0, 0);
  586. return lastDate < options.minDate || date.getTime() > options.maxDate;
  587. },
  588. onKeyDown: function(evt) {
  589. if (!picker.$date) {
  590. return;
  591. }
  592. var actualYear = picker.$date.getFullYear();
  593. var newDate = new Date(picker.$date);
  594. if (evt.keyCode === 37) newDate.setYear(actualYear - 1); else if (evt.keyCode === 38) newDate.setYear(actualYear - 4); else if (evt.keyCode === 39) newDate.setYear(actualYear + 1); else if (evt.keyCode === 40) newDate.setYear(actualYear + 4);
  595. if (!this.isDisabled(newDate)) picker.select(newDate, true);
  596. }
  597. } ];
  598. return {
  599. views: options.minView ? Array.prototype.slice.call(views, options.minView) : views,
  600. viewDate: viewDate
  601. };
  602. };
  603. } ];
  604. });