datepicker.js 23 KB

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