MonthsView.js 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195
  1. import {hasProperty, pushUnique, createTagRepeat} from '../../lib/utils.js';
  2. import {dateValue} from '../../lib/date.js';
  3. import {parseHTML} from '../../lib/dom.js';
  4. import View from './View.js';
  5. function computeMonthRange(range, thisYear) {
  6. if (!range || !range[0] || !range[1]) {
  7. return;
  8. }
  9. const [[startY, startM], [endY, endM]] = range;
  10. if (startY > thisYear || endY < thisYear) {
  11. return;
  12. }
  13. return [
  14. startY === thisYear ? startM : -1,
  15. endY === thisYear ? endM : 12,
  16. ];
  17. }
  18. export default class MonthsView extends View {
  19. constructor(picker) {
  20. super(picker, {
  21. id: 1,
  22. name: 'months',
  23. cellClass: 'month',
  24. });
  25. }
  26. init(options, onConstruction = true) {
  27. if (onConstruction) {
  28. this.grid = this.element;
  29. this.element.classList.add('months', 'datepicker-grid');
  30. this.grid.appendChild(parseHTML(createTagRepeat('span', 12, {'data-month': ix => ix})));
  31. }
  32. super.init(options);
  33. }
  34. setOptions(options) {
  35. if (options.locale) {
  36. this.monthNames = options.locale.monthsShort;
  37. }
  38. if (hasProperty(options, 'minDate')) {
  39. if (options.minDate === undefined) {
  40. this.minYear = this.minMonth = this.minDate = undefined;
  41. } else {
  42. const minDateObj = new Date(options.minDate);
  43. this.minYear = minDateObj.getFullYear();
  44. this.minMonth = minDateObj.getMonth();
  45. this.minDate = minDateObj.setDate(1);
  46. }
  47. }
  48. if (hasProperty(options, 'maxDate')) {
  49. if (options.maxDate === undefined) {
  50. this.maxYear = this.maxMonth = this.maxDate = undefined;
  51. } else {
  52. const maxDateObj = new Date(options.maxDate);
  53. this.maxYear = maxDateObj.getFullYear();
  54. this.maxMonth = maxDateObj.getMonth();
  55. this.maxDate = dateValue(this.maxYear, this.maxMonth + 1, 0);
  56. }
  57. }
  58. if (options.beforeShowMonth !== undefined) {
  59. this.beforeShow = typeof options.beforeShowMonth === 'function'
  60. ? options.beforeShowMonth
  61. : undefined;
  62. }
  63. }
  64. // Update view's settings to reflect the viewDate set on the picker
  65. updateFocus() {
  66. const viewDate = new Date(this.picker.viewDate);
  67. this.year = viewDate.getFullYear();
  68. this.focused = viewDate.getMonth();
  69. }
  70. // Update view's settings to reflect the selected dates
  71. updateSelection() {
  72. const {dates, rangepicker} = this.picker.datepicker;
  73. this.selected = dates.reduce((selected, timeValue) => {
  74. const date = new Date(timeValue);
  75. const year = date.getFullYear();
  76. const month = date.getMonth();
  77. if (selected[year] === undefined) {
  78. selected[year] = [month];
  79. } else {
  80. pushUnique(selected[year], month);
  81. }
  82. return selected;
  83. }, {});
  84. if (rangepicker && rangepicker.dates) {
  85. this.range = rangepicker.dates.map(timeValue => {
  86. const date = new Date(timeValue);
  87. return isNaN(date) ? undefined : [date.getFullYear(), date.getMonth()];
  88. });
  89. }
  90. }
  91. // Update the entire view UI
  92. render() {
  93. // refresh disabled months on every render in order to clear the ones added
  94. // by beforeShow hook at previous render
  95. this.disabled = [];
  96. this.picker.setViewSwitchLabel(this.year);
  97. this.picker.setPrevBtnDisabled(this.year <= this.minYear);
  98. this.picker.setNextBtnDisabled(this.year >= this.maxYear);
  99. const selected = this.selected[this.year] || [];
  100. const yrOutOfRange = this.year < this.minYear || this.year > this.maxYear;
  101. const isMinYear = this.year === this.minYear;
  102. const isMaxYear = this.year === this.maxYear;
  103. const range = computeMonthRange(this.range, this.year);
  104. Array.from(this.grid.children).forEach((el, index) => {
  105. const classList = el.classList;
  106. const date = dateValue(this.year, index, 1);
  107. el.className = `datepicker-cell ${this.cellClass}`;
  108. if (this.isMinView) {
  109. el.dataset.date = date;
  110. }
  111. // reset text on every render to clear the custom content set
  112. // by beforeShow hook at previous render
  113. el.textContent = this.monthNames[index];
  114. if (
  115. yrOutOfRange
  116. || isMinYear && index < this.minMonth
  117. || isMaxYear && index > this.maxMonth
  118. ) {
  119. classList.add('disabled');
  120. }
  121. if (range) {
  122. const [rangeStart, rangeEnd] = range;
  123. if (index > rangeStart && index < rangeEnd) {
  124. classList.add('range');
  125. }
  126. if (index === rangeStart) {
  127. classList.add('range-start');
  128. }
  129. if (index === rangeEnd) {
  130. classList.add('range-end');
  131. }
  132. }
  133. if (selected.includes(index)) {
  134. classList.add('selected');
  135. }
  136. if (index === this.focused) {
  137. classList.add('focused');
  138. }
  139. if (this.beforeShow) {
  140. this.performBeforeHook(el, index, date);
  141. }
  142. });
  143. }
  144. // Update the view UI by applying the changes of selected and focused items
  145. refresh() {
  146. const selected = this.selected[this.year] || [];
  147. const [rangeStart, rangeEnd] = computeMonthRange(this.range, this.year) || [];
  148. this.grid
  149. .querySelectorAll('.range, .range-start, .range-end, .selected, .focused')
  150. .forEach((el) => {
  151. el.classList.remove('range', 'range-start', 'range-end', 'selected', 'focused');
  152. });
  153. Array.from(this.grid.children).forEach((el, index) => {
  154. const classList = el.classList;
  155. if (index > rangeStart && index < rangeEnd) {
  156. classList.add('range');
  157. }
  158. if (index === rangeStart) {
  159. classList.add('range-start');
  160. }
  161. if (index === rangeEnd) {
  162. classList.add('range-end');
  163. }
  164. if (selected.includes(index)) {
  165. classList.add('selected');
  166. }
  167. if (index === this.focused) {
  168. classList.add('focused');
  169. }
  170. });
  171. }
  172. // Update the view UI by applying the change of focused item
  173. refreshFocus() {
  174. this.grid.querySelectorAll('.focused').forEach((el) => {
  175. el.classList.remove('focused');
  176. });
  177. this.grid.children[this.focused].classList.add('focused');
  178. }
  179. }