scripts/django_cradmin/utilities/Calendar.js | django_cradmin API Document

# scripts/django_cradmin/utilities/Calendar.js

    //NOTE: This file was originally converted from coffeescript to ES6 using decaffeinate. If you see something weird,
    //      please rewrite it to something readable :)

    /**
    Get an array of the short names for all the weekdays
    in the current locale, in the the correct order for the
    current locale.
    */
    let getWeekdaysShortForCurrentLocale = function() {
      let weekdays = [];
      let weekdaysWithSundayFirst = moment.weekdaysShort();
      let firstDayOfWeek = moment.localeData().firstDayOfWeek();
      for (let index = firstDayOfWeek; index <= firstDayOfWeek + 6; index++) {
        if (index > 6) {
          index = Math.abs(7 - index);
        }
        let weekday = weekdaysWithSundayFirst[index];
        weekdays.push(weekday);
      }
      return weekdays;
    };

    class Month {
      constructor(firstDayOfMonth) {
        this.firstDayOfMonth = firstDayOfMonth;
        this.lastDayOfMonth = this.firstDayOfMonth.clone().add({
          days: this.firstDayOfMonth.daysInMonth() - 1
        });
      }

      getDaysInMonth() {
        return this.firstDayOfMonth.daysInMonth();
      }
    }

    class CalendarDay {
      constructor(momentObject, isInCurrentMonth, isDisabled, nowMomentObject) {
        this.momentObject = momentObject;
        this.isInCurrentMonth = isInCurrentMonth;
        this.nowMomentObject = nowMomentObject;
        this._isDisabled = isDisabled;
      }

      getNumberInMonth() {
        return this.momentObject.format('D');
      }

      isToday() {
        return this.momentObject.isSame(this.nowMomentObject, 'day');
      }

      isDisabled() {
        return this._isDisabled;
      }
    }

    class CalendarWeek {
      constructor() {
        this.calendarDays = [];
      }

      addDay(calendarDay) {
        return this.calendarDays.push(calendarDay);
      }

      getDayCount() {
        return this.calendarDays.length;
      }

      prettyOneLineFormat() {
        let formattedDays = [];
        for (let calendarDay of this.calendarDays) {
          let formattedDay = calendarDay.momentObject.format('DD');
          if (calendarDay.isInCurrentMonth) {
            formattedDay = ` ${formattedDay} `;
          } else {
            formattedDay = `(${formattedDay})`;
          }
          formattedDays.push(formattedDay);
        }
        return formattedDays.join(' ');
      }
    }

    class CalendarMonth {
      constructor(calendarCoordinator, momentObject){
        this.calendarCoordinator = calendarCoordinator;
        this.changeMonth(momentObject);
      }

      changeMonth(momentObject) {
        let firstDayOfMonthMomentObject = momentObject.clone().set({
          date: 1,
          hour: 0,
          minute: 0,
          second: 0,
          millisecond: 0
        });
        this.month = new Month(firstDayOfMonthMomentObject);
        this.calendarWeeks = [new CalendarWeek()];
        this.currentWeekIndex = 0;
        this.daysPerWeek = 7;
        this.totalWeeks = 6;
        this.currentDayCount = 0;
        this.lastDay = null;
        return this._build();
      }

      _buildPrefixedDays() {
        if (this.month.firstDayOfMonth.weekday() > 0) {
          let momentObject;
          for (let index = 1; index <= this.month.firstDayOfMonth.weekday(); index++) {
            momentObject = this.month.firstDayOfMonth.clone().subtract({
              days: index
            });
            this._addMomentObject(momentObject, false);
          }
        }
      }

      _buildSuffixedDays() {
        let totalDayCount = this.totalWeeks * this.daysPerWeek;
        return (() => {
          let result = [];
          while (this.currentDayCount < totalDayCount) {
            let momentObject = this.lastDay.momentObject.clone().add({
              days: 1
            });
            result.push(this._addMomentObject(momentObject, false));
          }
          return result;
        })();
      }

      _buildDaysBelongingInMonth() {
        let momentObject;
        for (let dayIndex = 1; dayIndex <= this.month.getDaysInMonth(); dayIndex ++) {
          momentObject = this.month.firstDayOfMonth.clone().date(dayIndex);
          this._addMomentObject(momentObject, true);
        }
      }

      _build(momentFirstDayOfMonth) {
        this._buildPrefixedDays();
        this._buildDaysBelongingInMonth();
        return this._buildSuffixedDays();
      }

      _addMomentObject(momentObject, isInCurrentMonth) {
        let week = this.calendarWeeks[this.currentWeekIndex];
        if (week.getDayCount() >= this.daysPerWeek) {
          this.calendarWeeks.push(new CalendarWeek());
          this.currentWeekIndex += 1;
          week = this.calendarWeeks[this.currentWeekIndex];
        }

        let isDisabled = !this.calendarCoordinator.momentObjectIsAllowed(momentObject);
        let calendarDay = new CalendarDay(momentObject, isInCurrentMonth, isDisabled,
          this.calendarCoordinator.nowMomentObject);
        week.addDay(calendarDay);
        this.currentDayCount += 1;
        return this.lastDay = calendarDay;
      }

      prettyprint() {
        let rowFormatted;
        for (week in this.calendarWeeks) {
          rowFormatted = [];
          console.log(week.prettyOneLineFormat());
        }
      }
    }

    /**
    Coordinates the common calendar data no matter what kind of
    view we present.
    */
    export class CalendarCoordinator {
      constructor({selectedMomentObject,
                     minimumDatetime,
                     maximumDatetime,
                     nowMomentObject}) {
        // We operate with two momentObjects:
        // - selectedMomentObject: This is the actual moment object
        //   that the user has selected. This can be null if the user
        //   has not selected a value yet.
        // - shownMomentObject: This reflects the value shown on the screen
        //   at any given moment (E.g.: It changes each time a user changes
        //   the day, month, hour, etc). This is never ``null``.
        this.selectedMomentObject = selectedMomentObject;
        this.minimumDatetime = minimumDatetime;
        this.maximumDatetime = maximumDatetime;
        this.nowMomentObject = nowMomentObject;
        if (this.selectedMomentObject != null) {
          this.shownMomentObject = this.selectedMomentObject.clone();
        } else {
          // We set this to start the date picker on the current date
          this.setToNow();

          // If the current time is not allowed, pick the first allowed value
          if (!this.momentObjectIsAllowed(this.shownMomentObject)) {
            this.shownMomentObject = this.minimumDatetime.clone();
          }
        }
      }

      selectShownValue() {
        return this.selectedMomentObject = this.shownMomentObject.clone();
      }

      clearSelectedMomentObject() {
        return this.selectedMomentObject = null;
      }

      momentObjectIsAllowed(momentObject, ignoreTime=true) {
        let isAllowed = true;
        if (this.minimumDatetime != null) {
          let { minimumDatetime } = this;
          if (ignoreTime) {
            minimumDatetime = minimumDatetime.clone().set({
              hour: 0,
              minute: 0,
              second: 0
            });
          }
          isAllowed = !momentObject.isBefore(minimumDatetime);
        }
        if (isAllowed && (this.maximumDatetime != null)) {
          let { maximumDatetime } = this;
          if (ignoreTime) {
            maximumDatetime = maximumDatetime.clone().set({
              hour: 23,
              minute: 59,
              second: 59
            });
          }
          isAllowed = !momentObject.isAfter(maximumDatetime);
        }
        return isAllowed;
      }

      todayIsValidValue() {
        return this.momentObjectIsAllowed(this.nowMomentObject);
      }

      nowIsValidValue() {
        return this.momentObjectIsAllowed(this.nowMomentObject, false);
      }

      shownDateIsToday() {
        return this.shownMomentObject.isSame(this.nowMomentObject, 'day');
      }

      shownDateIsTodayAndNowIsValid() {
        return this.shownDateIsToday() && this.nowIsValidValue();
      }

      setToNow() {
        return this.shownMomentObject = this.nowMomentObject.clone();
      }
    }

    /**
    Coordinates the common calendar data for a month-view.
    */
    export class MonthlyCalendarCoordinator {
      constructor({calendarCoordinator,
                     yearselectValues,
                     hourselectValues,
                     minuteselectValues,
                     yearFormat,
                     monthFormat,
                     dayOfMonthSelectFormat,
                     dayOfMonthTableCellFormat,
                     hourFormat,
                     minuteFormat}) {
        this.calendarCoordinator = calendarCoordinator;
        this.yearselectValues = yearselectValues;
        this.hourselectValues = hourselectValues;
        this.minuteselectValues = minuteselectValues;
        this.yearFormat = yearFormat;
        this.monthFormat = monthFormat;
        this.dayOfMonthSelectFormat = dayOfMonthSelectFormat;
        this.dayOfMonthTableCellFormat = dayOfMonthTableCellFormat;
        this.hourFormat = hourFormat;
        this.minuteFormat = minuteFormat;
        this.dayobjects = null;  // Updated in @_changeSelectedDate()
        this._initWeekdays();
        this._initMonthObjects();
        this._initYearObjects();
        this._initHourObjects();
        this._initMinuteObjects();
        this._changeSelectedDate();
      }

      _sortConfigObjectsByValue(configObjects) {
        let compareFunction = function(a, b) {
          if (a.value < b.value) {
            return -1;
          }
          if (a.value > b.value) {
            return 1;
          }
          return 0;
        };
        return configObjects.sort(compareFunction);
      }

      _initWeekdays() {
        return this.shortWeekdays = getWeekdaysShortForCurrentLocale();
      }

      _initYearObjects() {
        let selectedYearValue = this.calendarCoordinator.shownMomentObject.year();
        let hasSelectedYearValue = false;

        let formatMomentObject = this.calendarCoordinator.shownMomentObject.clone().set({
          month: 0,
          date: 0,
          hour: 0,
          minute: 0,
          second: 0
        });

        this._yearsMap = {};
        this.yearselectConfig = [];
        for (let year of this.yearselectValues) {
          var label = formatMomentObject.set({
            year
          }).format(this.yearFormat);
          var yearConfig = {
            value: year,
            label
          };
          this.yearselectConfig.push(yearConfig);
          this._yearsMap[year] = yearConfig;
          if (year === selectedYearValue) {
            hasSelectedYearValue = true;
          }
        }

        if (!hasSelectedYearValue) {
          // Since we do not include all years in the yearList, we need
          // to handle the case when the given datetimes year is not in the
          // list. We handle this by adding it to the end of the list.
          var label = formatMomentObject.set({
            year: selectedYearValue
          }).format(this.yearFormat);
          var yearConfig = {
            value: selectedYearValue,
            label
          };
          this.yearselectConfig.push(yearConfig);
          this._yearsMap[yearConfig.value] = yearConfig;

          // Sort the config to put the newly added config in
          // its natural place.
          return this._sortConfigObjectsByValue(this.yearselectConfig);
        }
      }

      _initMonthObjects() {
        let label;
        let monthObject;
        this.monthselectConfig = [];
        this._monthsMap = {};

        let formatMomentObject = this.calendarCoordinator.shownMomentObject.clone().set({
          month: 0,
          date: 0,
          hour: 0,
          minute: 0,
          second: 0
        });
        for (let monthnumber = 0; monthnumber < 12; monthnumber++) {
          label = formatMomentObject.set({
            month: monthnumber
          }).format(this.monthFormat);
          monthObject = {
            value: monthnumber,
            label
          };
          this.monthselectConfig.push(monthObject);
          this._monthsMap[monthnumber] = monthObject;
        }
      }

      _initHourObjects() {
        let selectedHourValue = this.calendarCoordinator.shownMomentObject.hour();
        let hasSelectedHourValue = false;
        let formatMomentObject = this.calendarCoordinator.shownMomentObject.clone().set({
          minute: 0,
          second: 0
        });
        this._hoursMap = {};
        this.hourselectConfig = [];
        for (let hour of this.hourselectValues) {
          var label = formatMomentObject.set({
            hour
          }).format(this.hourFormat);
          var hourConfig = {
            value: hour,
            label
          };
          this.hourselectConfig.push(hourConfig);
          this._hoursMap[hourConfig.value] = hourConfig;
          if (hourConfig.value === selectedHourValue) {
            hasSelectedHourValue = true;
          }
        }

        if (!hasSelectedHourValue) {
          // Since we do not include all hours in the hourList, we need
          // to handle the case when the given datetimes hour is not in the
          // list. We handle this by adding it to the end of the list.
          var label = formatMomentObject.set({
            hour: selectedHourValue
          }).format(this.hourFormat);
          var hourConfig = {
            value: selectedHourValue,
            label
          };
          this.hourselectConfig.push(hourConfig);
          this._hoursMap[hourConfig.value] = hourConfig;
          return this._sortConfigObjectsByValue(this.hourselectConfig);
        }
      }

      _initMinuteObjects() {
        let selectedMinuteValue = this.calendarCoordinator.shownMomentObject.minute();
        let hasSelectedMinuteValue = false;
        let formatMomentObject = this.calendarCoordinator.shownMomentObject.clone().set({
          second: 0
        });
        this._minutesMap = {};
        this.minuteselectConfig = [];
        for (let minute of this.minuteselectValues) {
          var label = formatMomentObject.set({
            minute
          }).format(this.minuteFormat);
          var minuteConfig = {
            value: minute,
            label
          };
          this.minuteselectConfig.push(minuteConfig);
          this._minutesMap[minuteConfig.value] = minuteConfig;
          if (minuteConfig.value === selectedMinuteValue) {
            hasSelectedMinuteValue = true;
          }
        }

        if (!hasSelectedMinuteValue) {
          // Since we do not include all minutes in the minuteList, we need
          // to handle the case when the given datetimes minute is not in the
          // list. We handle this by adding it to the end of the list.
          var label = formatMomentObject.set({
            minute: selectedMinuteValue
          }).format(this.minuteFormat);
          var minuteConfig = {
            value: selectedMinuteValue,
            label
          };
          this.minuteselectConfig.push(minuteConfig);
          this._minutesMap[minuteConfig.value] = minuteConfig;
          return this._sortConfigObjectsByValue(this.minuteselectConfig);
        }
      }

      _setCurrentYear() {
        let currentYearNumber = this.calendarMonth.month.firstDayOfMonth.year();
        this.currentYearObject = this._yearsMap[currentYearNumber];
        if (this.currentYearObject == null) {
          console.warn(`The given year, ${currentYearNumber} is not one of the available choices`);
        }
      }

      _setCurrentMonth() {
        let currentMonthNumber = this.calendarMonth.month.firstDayOfMonth.month();
        this.currentMonthObject = this._monthsMap[currentMonthNumber];
        if (this.currentMonthObject == null) {
          console.warn(`The given month number, ${currentMonthNumber} is not one of the available choices`);
        }
      }

      _setCurrentHour() {
        let currentHourNumber = this.calendarCoordinator.shownMomentObject.hour();
        this.currentHourObject = this._hoursMap[currentHourNumber];
        if (this.currentHourObject == null) {
          console.warn(`The given hour, ${currentHourNumber} is not one of the available choices`);
        }
      }

      _setCurrentMinute() {
        let currentMinuteNumber = this.calendarCoordinator.shownMomentObject.minute();
        this.currentMinuteObject = this._minutesMap[currentMinuteNumber];
        if (this.currentMinuteObject == null) {
          console.warn(`The given minute, ${currentMinuteNumber} is not one of the available choices`);
        }
      }

      _updateDayObjects() {
        let label;
        let dayNumberObject;
        let formatMomentObject = this.calendarCoordinator.shownMomentObject.clone().set({
          hour: 0,
          minute: 0,
          second: 0
        });
        this.dayobjects = [];
        for (let daynumber = 1; index <= this.calendarMonth.month.getDaysInMonth(); index++) {
          label = formatMomentObject.set({
            date: daynumber
          }).format(this.dayOfMonthSelectFormat);

          dayNumberObject = {
            value: daynumber,
            label: label
          };
          this.dayobjects.push(dayNumberObject);
        }
      }

      /*
      Change month to the month containing the given momentObject,
      and select the date.

      As long as you change ``@calendarCoordinator.shownMomentObject``, this
      will update everything to mirror the change (selected day, month, year, ...).
      */
      _changeSelectedDate() {
        this.calendarMonth = new CalendarMonth(this.calendarCoordinator, this.calendarCoordinator.shownMomentObject);
        this._setCurrentYear();
        this._setCurrentMonth();
        this._setCurrentHour();
        this._setCurrentMinute();
        this._updateDayObjects();
        return this.currentDayObject = this.dayobjects[this.calendarCoordinator.shownMomentObject.date()-1];
      }

      handleDayChange(momentObject) {
        this.calendarCoordinator.shownMomentObject = momentObject.clone().set({
          hour: this.currentHourObject.value,
          minute: this.currentMinuteObject.value
        });
        return this._changeSelectedDate();
      }

      handleCurrentDayObjectChange() {
        let momentObject = moment({
          year: this.currentYearObject.value,
          month: this.currentMonthObject.value,
          day: this.currentDayObject.value
        });
        return this.handleDayChange(momentObject);
      }

      handleCalendarDayChange(calendarDay) {
        return this.handleDayChange(calendarDay.momentObject);
      }

      handleCurrentMonthChange() {
        this.calendarCoordinator.shownMomentObject.set({
          month: this.currentMonthObject.value
        });
        return this._changeSelectedDate();
      }

      handleCurrentYearChange() {
        this.calendarCoordinator.shownMomentObject.set({
          year: this.currentYearObject.value
        });
        return this._changeSelectedDate();
      }

      handleCurrentHourChange() {
        this.calendarCoordinator.shownMomentObject.set({
          hour: this.currentHourObject.value
        });
        return this._changeSelectedDate();
      }

      handleCurrentMinuteChange() {
        this.calendarCoordinator.shownMomentObject.set({
          minute: this.currentMinuteObject.value
        });
        return this._changeSelectedDate();
      }

      handleFocusOnCalendarDay(calendarDay) {
        return this.lastFocusedMomentObject = calendarDay.momentObject;
      }

      getLastFocusedMomentObject() {
        if (this.lastFocusedMomentObject != null) {
          return this.lastFocusedMomentObject;
        } else {
          return this.calendarCoordinator.shownMomentObject;
        }
      }

      getDayOfMonthLabelForTableCell(calendarDay) {
        return calendarDay.momentObject.format(this.dayOfMonthTableCellFormat);
      }

      setToToday() {
        return this.handleDayChange(this.calendarCoordinator.nowMomentObject.clone());
      }
    }