Source: argos-sdk/src/Calendar.js

/* Copyright (c) 2010, Sage Software, Inc. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * @class argos.Calendar
 */
import declare from 'dojo/_base/declare';
import _ActionMixin from './_ActionMixin';
import _WidgetBase from 'dijit/_WidgetBase';
import _Templated from './_Templated';
import Dropdown from './Dropdown';
import getResource from './I18n';


const resource = getResource('calendar');

const __class = declare('argos.Calendar', [_WidgetBase, _ActionMixin, _Templated], {
  widgetTemplate: new Simplate([
    '<div id="{%= $.id %}" class="calendar panel">',
    '{%! $.calendarHeaderTemplate %}',
    '{%! $.calendarTableTemplate %}',
    '{%! $.calendarFooterTemplate %}',
    '</div>',
  ]),
  calendarHeaderTemplate: new Simplate([
    '<div class="calendar__header">',
    `<button type="button" class="btn-icon hide-focus" data-action="decrementMonth">
      <svg class="icon" focusable="false" aria-hidden="true" role="presentation">
        <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#icon-previous-page"></use>
      </svg>
    </button>`,
    '<div class="month" data-dojo-attach-point="monthNode" data-action="toggleMonthModal"></div>',
    '<div class="year" data-dojo-attach-point="yearNode" data-action="toggleYearModal"></div>',
    `<button type="button" class="btn-icon hide-focus" data-action="incrementMonth">
      <svg class="icon" focusable="false" aria-hidden="true" role="presentation">
        <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#icon-next-page"></use>
      </svg>
    </button>`,
    '</div>',
  ]),
  calendarTableTemplate: new Simplate([
    '<table class="calendar-table">',
    '<thead>',
    '{%! $.calendarWeekDaysTemplate %}',
    '</thead>',
    '<tbody data-dojo-attach-point="weeksNode"></tbody>',
    '</table>',
  ]),
  calendarFooterTemplate: new Simplate([
    '<div class="calendar-footer" data-dojo-attach-point="footerNode">',
    '<div class="button button--secondary clear" data-action="clearCalendar" data-dojo-attach-point="clearButton">{%= $.clearText %}</div>',
    '<div class="button button--secondary toToday" data-action="goToToday" data-dojo-attach-point="todayButton">{%= $.todayText %}</div>',
    '</div>',
  ]),
  calendarTableDayTemplate: new Simplate([
    '<td class="day {%= $.month %} {%= $.weekend %} {%= $.selected %} {%= $.isToday %}" data-action="changeDay" data-date="{%= $.date %}">',
    '<span>{%= $.day %}</span>',
    '</td>',
  ]),
  calendarTableDayActiveTemplate: new Simplate([
    '<div class="day__active">',
    '</div>',
  ]),
  calendarTableWeekStartTemplate: new Simplate([
    '<tr class="calendar-week">',
  ]),
  calendarTableWeekEndTemplate: new Simplate([
    '</tr>',
  ]),
  calendarWeekDaysTemplate: new Simplate([
    '<tr class="calendar-weekdays">',
    '<th>{%= $.weekDaysShortText[0] %}</th>',
    '<th>{%= $.weekDaysShortText[1] %}</th>',
    '<th>{%= $.weekDaysShortText[2] %}</th>',
    '<th>{%= $.weekDaysShortText[3] %}</th>',
    '<th>{%= $.weekDaysShortText[4] %}</th>',
    '<th>{%= $.weekDaysShortText[5] %}</th>',
    '<th>{%= $.weekDaysShortText[6] %}</th>',
    '</tr>',
  ]),

  // Localization
  titleText: resource.titleText,
  amText: resource.amText,
  pmText: resource.pmText,
  clearText: resource.clearText,
  todayText: resource.todayText,

  // Date is an object containing selected day, month, year, time, todayMoment (today), selectedDateMoment, etc.
  date: null,
  id: 'generic_calendar',
  // This boolean value is used to trigger the modal hide and show and must be used by each entity
  isModal: false,
  noClearButton: false,
  showTimePicker: true,
  showSubValues: true,
  _currentMonth: null,
  _currentYear: null,
  _monthDropdown: null,
  _selectWeek: false,
  _selectedDay: null,
  _widgetName: 'calendar',
  _yearDropdown: null,
  constructor: function constructor() {
    const m = this.getCurrentDateMoment();
    let monthsText = m._locale._months;

    if (monthsText.standalone) {
      monthsText = monthsText.standalone;
    }
    this.monthsText = monthsText.map((val, i) => {
      return {
        text: val,
        value: i,
        key: i,
      };
    });
    this.weekDaysShortText = m._locale._weekdaysMin;
  },
  changeDay: function changeDay(params) {
    if (!this._selectWeek) {
      this.changeSingleDay(params);
    } else {
      this.changeWeek(params);
    }
    this.onChangeDay(params);
    return this;
  },
  onChangeDay: function onChangeDay() {
  },
  changeMonthShown: function changeMonthShown({ monthNumber }) {
    this._monthDropdown.setValue(monthNumber);
    return this;
  },
  changeSingleDay: function changeSingleDay(params) {
    if (params) {
      const selected = $('.is-selected', this.weeksNode);

      if (selected) {
        selected.each((i, day) => {
          $(day).removeClass('is-selected');
        });
      }

      if (selected) {
        $(selected).removeClass('is-selected');
      }

      if (params.$source) {
        this._selectedDay = params.$source;
        $(params.$source).addClass('is-selected');
      }

      if (params.date) {
        this.date.selectedDateMoment = moment(params.date, 'YYYY-MM-DD');
      }

      if (this.date.monthNumber !== this.date.selectedDateMoment.month()) {
        this.refreshCalendar(this.date);
      }
    }
    return this;
  },
  changeWeek: function changeWeek(params) {
    if (params) {
      const selected = $('.is-selected', this.weeksNode);

      if (selected) {
        selected.each((i, day) => {
          $(day).removeClass('is-selected');
        });
      }

      if (params.$source.parentNode) {
        this._selectedDay = params.$source;
        $(params.$source.parentNode).children().each((i, day) => {
          $(day).addClass('is-selected');
        });
      }

      if (params.date) {
        this.date.selectedDateMoment = moment(params.date, 'YYYY-MM-DD');
      }

      if (this.date.monthNumber !== this.date.selectedDateMoment.month()) {
        this.refreshCalendar(this.date);
      }
    }
    return this;
  },
  changeYearShown: function changeYearShown({ year }) {
    this._yearDropdown.setValue(year);
    return this;
  },
  checkAndRenderDay: function checkAndRenderDay(data = {}) {
    const dayIndexer = data.day + data.startingDay - 1;
    if (data.day === data.todayMoment.date() && data.todayMoment.month() === data.dateMoment.month() && data.todayMoment.year() === data.dateMoment.year()) {
      data.isToday = 'is-today';
    } else {
      data.isToday = '';
    }
    if (dayIndexer % 7 === data.weekEnds.Sunday || dayIndexer % 7 === data.weekEnds.Saturday) {
      data.weekend = 'weekend';
    } else {
      data.weekend = '';
    }
    data.date = data.dateMoment.clone().date(data.day).format('YYYY-MM-DD');
    const day = $(this.calendarTableDayTemplate.apply(data, this));
    if (data.day === this.date.dayNode && data.month.indexOf('alternate') === -1) {
      this._selectedDay = day[0];
    }
    if (this.showSubValues) {
      this.setSubValue(day, data)
          .setActiveDay(day);
    }
    $(data.week).append(day);
  },
  clearCalendar: function clearCalendar() {
    const selected = $('.is-selected', this.weeksNode)[0];

    if (selected) {
      $(selected).removeClass('is-selected');
    }
    this.date.selectedDateMoment = null;
  },
  createMonthDropdown: function createMonthDropdown() {
    if (!this._monthDropdown) {
      this._monthDropdown = new Dropdown({ id: `month-dropdown-${this.id}`, dropdownClass: 'dropdown--medium input-sm', onSelect: this.setMonth, onSelectScope: this });
      this._monthDropdown.createList({ items: this.monthsText, defaultValue: this.date.selectedDateMoment.month() });
      $(this.monthNode).append(this._monthDropdown.domNode);
    }
    return this;
  },
  createYearDropdown: function createYearDropdown() {
    if (!this._yearDropdown) {
      this._yearDropdown = new Dropdown({ id: `year-dropdown-${this.id}`, onSelect: this.setYear, dropdownClass: 'dropdown-mx', onSelectScope: this });
      this._yearDropdown.createList({ items: this.getYearRange(), defaultValue: this.date.selectedDateMoment.format('YYYY') });
      $(this.yearNode).append(this._yearDropdown.domNode);
    }
    return this;
  },
  decrementMonth: function decrementMonth() {
    this.date.selectedDateMoment.subtract({ months: 1 });
    this.refreshCalendar(this.date);
  },
  destroy: function destroy() {
    this._yearDropdown.destroy();
    this._monthDropdown.destroy();
    this.inherited(arguments);
  },
  getContent: function getContent() {
    if (this.options.timeless) {
      // Revert back to timeless
      this.date.selectedDateMoment.add({
        minutes: this.date.selectedDateMoment.utcOffset(),
      });
    }
    return this.date;
  },
  goToToday: function goToToday() {
    this.date.todayMoment = this.getCurrentDateMoment();
    this.date.selectedDateMoment = this.date.todayMoment;
    this.refreshCalendar(this.date); // This will reload the data.
    const day = $('.is-today', this.weeksNode)[0];
    let params = {};
    if (day) {
      params = { $source: day, date: day.dataset.date };
    }
    this.changeDay(params);
  },
  getDateTime: function getDateTime() {
    const result = this.date.selectedDateMoment;
    return result.toDate();
  },
  getCurrentDateMoment: function getCurrentDateMoment() {
    return moment();
  },
  getSelectedDateMoment: function getSelectedDateMoment() {
    return this.date.selectedDateMoment;
  },
  getYearRange: function getYearRange() {
    const items = [];
    const thisYear = this.date.todayMoment.year();
    for (let i = thisYear - 10; i <= thisYear + 10; i++) {
      items.push(
        {
          value: `${i}`,
          key: `${i}`,
        }
      );
    }
    return items;
  },
  incrementMonth: function incrementMonth() {
    this.date.selectedDateMoment.add({ months: 1 });
    this.refreshCalendar(this.date);
  },
  init: function init() {
    this.inherited(arguments);
  },
  isActive: function isActive(day) {
    if (day) {
      return $('.day__active', day)[0];
    }
  },
  postRenderCalendar: function postRenderCalendar() {
    if (this._selectWeek) {
      this.changeWeek({ $source: this._selectedDay });
    }
  },
  refreshCalendar: function refreshCalendar(date = {}) {
    this._refreshCalendar(date);
    this.onRefreshCalendar(true);
    return this;
  },
  _refreshCalendar: function refreshCalendar(date = {}) {
    $(this.weeksNode).empty();
    this.renderCalendar(date)
        .changeMonthShown(date)
        .changeYearShown(date);
    return this;
  },
  onRefreshCalendar: function onRefreshCalendar() {
  },
  removeActive: function removeActive(day) {
    if (day) {
      const active = this.isActive(day);
      if (active) {
        $(active).remove();
      }
    }
    return this;
  },
  refresh: function refresh(options) {
    this.date.todayMoment = this.getCurrentDateMoment();
    this._refreshCalendar(this.date);
    this.onRefreshCalendar(options);
  },
  renderCalendar: function renderCalendar({ todayMoment, selectedDateMoment }) {
    const daysInMonth = selectedDateMoment.daysInMonth();
    const startingDay = selectedDateMoment.clone().startOf('month').day();
    const endPrevMonth = selectedDateMoment.clone().startOf('month').subtract({ days: startingDay });
    const startNextMonth = selectedDateMoment.clone().endOf('month').add({ days: 1 });
    const data = {
      todayMoment,
      selectedDateMoment,
      dateMoment: endPrevMonth.clone(),
      week: $(this.calendarTableWeekStartTemplate.apply()),
      startingDay: endPrevMonth.clone().startOf('month').day(),
      weekEnds: {
        Sunday: 0,
        Saturday: 6,
      },
    };

    // Iterate through the days that are in the start week of the current month but are in the previous month
    data.month = 'alternate prev-month';
    for (let day = endPrevMonth.date(); day < endPrevMonth.date() + startingDay; day++) {
      data.day = day;
      this.checkAndRenderDay(data);
    }

    data.month = '';
    data.startingDay = startingDay;
    data.dateMoment = selectedDateMoment.clone();
    for (let day = 1; day <= daysInMonth; day++) {
      if (day === selectedDateMoment.date()) {
        data.selected = 'is-selected';
        this.date.dayNode = day;
      } else {
        data.selected = '';
      }
      data.day = day;
      this.checkAndRenderDay(data);
      if ((day + startingDay) % 7 === 0) {
        $(data.week).append(this.calendarTableWeekEndTemplate.apply());
        $(this.weeksNode).append(data.week);
        data.week = $(this.calendarTableWeekStartTemplate.apply());
      }
    }

    data.selected = '';
    data.startingDay = startNextMonth.day();
    data.dateMoment = startNextMonth.clone();
    // Iterate through remaining days of the week based on 7 days in the week and ensure there are 6 weeks shown (for consistency)
    data.month = 'alternate next-month';
    for (let day = 1; day <= (1 + data.weekEnds.Saturday - startNextMonth.day()); day++) {
      data.day = day;
      this.checkAndRenderDay(data);
    }
    $(data.week).append($(this.calendarTableWeekEndTemplate.apply()));
    $(this.weeksNode).append(data.week);

    if (this.weeksNode.children.length === 5) {
      data.week = $(this.calendarTableWeekStartTemplate.apply());
      for (let day = 2 + data.weekEnds.Saturday - startNextMonth.day(); day <= (8 + data.weekEnds.Saturday - startNextMonth.day()); day++) {
        data.day = day;
        this.checkAndRenderDay(data);
      }
      $(data.week).append($(this.calendarTableWeekEndTemplate.apply()));
      $(this.weeksNode).append(data.week);
    }

    this.setDateObject(selectedDateMoment);

    this.postRenderCalendar();

    return this;
  },
  setActiveDay: function setActiveDay(day = {}) {
    if (day.subValue) {
      const active = this.calendarTableDayActiveTemplate.apply({ count: day.subValue }, this);
      $(day).append(active);
    }
    return this;
  },
  setDateObject: function setDateObject(dateMoment = {}) {
    this.date.day = dateMoment.date();
    this.date.month = dateMoment.format('MMMM');
    this.date.monthNumber = dateMoment.month();
    this.date.year = dateMoment.year();
    this.date.date = moment(dateMoment).toDate();

    return this;
  },
  setMonth: function setMonth() {
    const monthNumber = Number(this._monthDropdown.getValue());
    this.date.selectedDateMoment.month(monthNumber);
    this.refreshCalendar(this.date);
  },
  setSubValue: function setSubValue() {
    return this;
  },
  setYear: function setYear() {
    this.date.selectedDateMoment.year(this._yearDropdown.getValue());
    this.refreshCalendar(this.date);
  },
  show: function show(options = {}) {
    this.date = {};
    this.options = options || this.options;

    this.titleText = this.options.label ? this.options.label : this.titleText;
    this.showTimePicker = this.options && this.options.showTimePicker;
    if (this.options.timeless) {
      // Undo timeless
      const startDate = moment(this.options && this.options.date);
      startDate.subtract({
        minutes: startDate.utcOffset(),
      });
      this.date.selectedDateMoment = startDate;
    } else {
      this.date.selectedDateMoment = moment((this.options && this.options.date) || moment().clone());
    }
    this.date.todayMoment = moment();
    if (this.isModal || this.options.isModal || this.noClearButton || this.options.noClearButton) {
      this.clearButton.style.display = 'none';
    }
    this.createMonthDropdown()
        .createYearDropdown();

    this.refreshCalendar(this.date);
  },
  toggleSelectWeek: function toggleSelectWeek() {
    this._selectWeek = !this._selectWeek;
    this.changeDay({ $source: this._selectedDay });
  },
});

export default __class;