forked from mirrors/gecko-dev
		
	# ignore-this-changeset Differential Revision: https://phabricator.services.mozilla.com/D36053 --HG-- extra : source : 651d8f947a29f5d80b7e833f7e6b99e2afe8bf9d
		
			
				
	
	
		
			446 lines
		
	
	
	
		
			12 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			446 lines
		
	
	
	
		
			12 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
/* This Source Code Form is subject to the terms of the Mozilla Public
 | 
						|
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 | 
						|
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 | 
						|
 | 
						|
/* import-globals-from datekeeper.js */
 | 
						|
/* import-globals-from calendar.js */
 | 
						|
/* import-globals-from spinner.js */
 | 
						|
 | 
						|
"use strict";
 | 
						|
 | 
						|
function DatePicker(context) {
 | 
						|
  this.context = context;
 | 
						|
  this._attachEventListeners();
 | 
						|
}
 | 
						|
 | 
						|
{
 | 
						|
  const CAL_VIEW_SIZE = 42;
 | 
						|
 | 
						|
  DatePicker.prototype = {
 | 
						|
    /**
 | 
						|
     * Initializes the date picker. Set the default states and properties.
 | 
						|
     * @param  {Object} props
 | 
						|
     *         {
 | 
						|
     *           {Number} year [optional]
 | 
						|
     *           {Number} month [optional]
 | 
						|
     *           {Number} date [optional]
 | 
						|
     *           {Number} min
 | 
						|
     *           {Number} max
 | 
						|
     *           {Number} step
 | 
						|
     *           {Number} stepBase
 | 
						|
     *           {Number} firstDayOfWeek
 | 
						|
     *           {Array<Number>} weekends
 | 
						|
     *           {Array<String>} monthStrings
 | 
						|
     *           {Array<String>} weekdayStrings
 | 
						|
     *           {String} locale [optional]: User preferred locale
 | 
						|
     *         }
 | 
						|
     */
 | 
						|
    init(props = {}) {
 | 
						|
      this.props = props;
 | 
						|
      this._setDefaultState();
 | 
						|
      this._createComponents();
 | 
						|
      this._update();
 | 
						|
      document.dispatchEvent(new CustomEvent("PickerReady"));
 | 
						|
    },
 | 
						|
 | 
						|
    /*
 | 
						|
     * Set initial date picker states.
 | 
						|
     */
 | 
						|
    _setDefaultState() {
 | 
						|
      const {
 | 
						|
        year,
 | 
						|
        month,
 | 
						|
        day,
 | 
						|
        min,
 | 
						|
        max,
 | 
						|
        step,
 | 
						|
        stepBase,
 | 
						|
        firstDayOfWeek,
 | 
						|
        weekends,
 | 
						|
        monthStrings,
 | 
						|
        weekdayStrings,
 | 
						|
        locale,
 | 
						|
        dir,
 | 
						|
      } = this.props;
 | 
						|
      const dateKeeper = new DateKeeper({
 | 
						|
        year,
 | 
						|
        month,
 | 
						|
        day,
 | 
						|
        min,
 | 
						|
        max,
 | 
						|
        step,
 | 
						|
        stepBase,
 | 
						|
        firstDayOfWeek,
 | 
						|
        weekends,
 | 
						|
        calViewSize: CAL_VIEW_SIZE,
 | 
						|
      });
 | 
						|
 | 
						|
      document.dir = dir;
 | 
						|
 | 
						|
      this.state = {
 | 
						|
        dateKeeper,
 | 
						|
        locale,
 | 
						|
        isMonthPickerVisible: false,
 | 
						|
        datetimeOrders: new Intl.DateTimeFormat(locale)
 | 
						|
          .formatToParts(new Date(0))
 | 
						|
          .map(part => part.type),
 | 
						|
        getDayString: day =>
 | 
						|
          day ? new Intl.NumberFormat(locale).format(day) : "",
 | 
						|
        getWeekHeaderString: weekday => weekdayStrings[weekday],
 | 
						|
        getMonthString: month => monthStrings[month],
 | 
						|
        setSelection: date => {
 | 
						|
          dateKeeper.setSelection({
 | 
						|
            year: date.getUTCFullYear(),
 | 
						|
            month: date.getUTCMonth(),
 | 
						|
            day: date.getUTCDate(),
 | 
						|
          });
 | 
						|
          this._update();
 | 
						|
          this._dispatchState();
 | 
						|
          this._closePopup();
 | 
						|
        },
 | 
						|
        setYear: year => {
 | 
						|
          dateKeeper.setYear(year);
 | 
						|
          dateKeeper.setSelection({
 | 
						|
            year,
 | 
						|
            month: dateKeeper.selection.month,
 | 
						|
            day: dateKeeper.selection.day,
 | 
						|
          });
 | 
						|
          this._update();
 | 
						|
          this._dispatchState();
 | 
						|
        },
 | 
						|
        setMonth: month => {
 | 
						|
          dateKeeper.setMonth(month);
 | 
						|
          dateKeeper.setSelection({
 | 
						|
            year: dateKeeper.selection.year,
 | 
						|
            month,
 | 
						|
            day: dateKeeper.selection.day,
 | 
						|
          });
 | 
						|
          this._update();
 | 
						|
          this._dispatchState();
 | 
						|
        },
 | 
						|
        toggleMonthPicker: () => {
 | 
						|
          this.state.isMonthPickerVisible = !this.state.isMonthPickerVisible;
 | 
						|
          this._update();
 | 
						|
        },
 | 
						|
      };
 | 
						|
    },
 | 
						|
 | 
						|
    /**
 | 
						|
     * Initalize the date picker components.
 | 
						|
     */
 | 
						|
    _createComponents() {
 | 
						|
      this.components = {
 | 
						|
        calendar: new Calendar(
 | 
						|
          {
 | 
						|
            calViewSize: CAL_VIEW_SIZE,
 | 
						|
            locale: this.state.locale,
 | 
						|
            setSelection: this.state.setSelection,
 | 
						|
            getDayString: this.state.getDayString,
 | 
						|
            getWeekHeaderString: this.state.getWeekHeaderString,
 | 
						|
          },
 | 
						|
          {
 | 
						|
            weekHeader: this.context.weekHeader,
 | 
						|
            daysView: this.context.daysView,
 | 
						|
          }
 | 
						|
        ),
 | 
						|
        monthYear: new MonthYear(
 | 
						|
          {
 | 
						|
            setYear: this.state.setYear,
 | 
						|
            setMonth: this.state.setMonth,
 | 
						|
            getMonthString: this.state.getMonthString,
 | 
						|
            datetimeOrders: this.state.datetimeOrders,
 | 
						|
            locale: this.state.locale,
 | 
						|
          },
 | 
						|
          {
 | 
						|
            monthYear: this.context.monthYear,
 | 
						|
            monthYearView: this.context.monthYearView,
 | 
						|
          }
 | 
						|
        ),
 | 
						|
      };
 | 
						|
    },
 | 
						|
 | 
						|
    /**
 | 
						|
     * Update date picker and its components.
 | 
						|
     */
 | 
						|
    _update(options = {}) {
 | 
						|
      const { dateKeeper, isMonthPickerVisible } = this.state;
 | 
						|
 | 
						|
      if (isMonthPickerVisible) {
 | 
						|
        this.state.months = dateKeeper.getMonths();
 | 
						|
        this.state.years = dateKeeper.getYears();
 | 
						|
      } else {
 | 
						|
        this.state.days = dateKeeper.getDays();
 | 
						|
      }
 | 
						|
 | 
						|
      this.components.monthYear.setProps({
 | 
						|
        isVisible: isMonthPickerVisible,
 | 
						|
        dateObj: dateKeeper.state.dateObj,
 | 
						|
        months: this.state.months,
 | 
						|
        years: this.state.years,
 | 
						|
        toggleMonthPicker: this.state.toggleMonthPicker,
 | 
						|
        noSmoothScroll: options.noSmoothScroll,
 | 
						|
      });
 | 
						|
      this.components.calendar.setProps({
 | 
						|
        isVisible: !isMonthPickerVisible,
 | 
						|
        days: this.state.days,
 | 
						|
        weekHeaders: dateKeeper.state.weekHeaders,
 | 
						|
      });
 | 
						|
 | 
						|
      isMonthPickerVisible
 | 
						|
        ? this.context.monthYearView.classList.remove("hidden")
 | 
						|
        : this.context.monthYearView.classList.add("hidden");
 | 
						|
    },
 | 
						|
 | 
						|
    /**
 | 
						|
     * Use postMessage to close the picker.
 | 
						|
     */
 | 
						|
    _closePopup() {
 | 
						|
      window.postMessage(
 | 
						|
        {
 | 
						|
          name: "ClosePopup",
 | 
						|
        },
 | 
						|
        "*"
 | 
						|
      );
 | 
						|
    },
 | 
						|
 | 
						|
    /**
 | 
						|
     * Use postMessage to pass the state of picker to the panel.
 | 
						|
     */
 | 
						|
    _dispatchState() {
 | 
						|
      const { year, month, day } = this.state.dateKeeper.selection;
 | 
						|
      // The panel is listening to window for postMessage event, so we
 | 
						|
      // do postMessage to itself to send data to input boxes.
 | 
						|
      window.postMessage(
 | 
						|
        {
 | 
						|
          name: "PickerPopupChanged",
 | 
						|
          detail: {
 | 
						|
            year,
 | 
						|
            month,
 | 
						|
            day,
 | 
						|
          },
 | 
						|
        },
 | 
						|
        "*"
 | 
						|
      );
 | 
						|
    },
 | 
						|
 | 
						|
    /**
 | 
						|
     * Attach event listeners
 | 
						|
     */
 | 
						|
    _attachEventListeners() {
 | 
						|
      window.addEventListener("message", this);
 | 
						|
      document.addEventListener("mouseup", this, { passive: true });
 | 
						|
      document.addEventListener("mousedown", this);
 | 
						|
    },
 | 
						|
 | 
						|
    /**
 | 
						|
     * Handle events.
 | 
						|
     *
 | 
						|
     * @param  {Event} event
 | 
						|
     */
 | 
						|
    handleEvent(event) {
 | 
						|
      switch (event.type) {
 | 
						|
        case "message": {
 | 
						|
          this.handleMessage(event);
 | 
						|
          break;
 | 
						|
        }
 | 
						|
        case "mousedown": {
 | 
						|
          // Use preventDefault to keep focus on input boxes
 | 
						|
          event.preventDefault();
 | 
						|
          event.target.setCapture();
 | 
						|
 | 
						|
          if (event.target == this.context.buttonPrev) {
 | 
						|
            event.target.classList.add("active");
 | 
						|
            this.state.dateKeeper.setMonthByOffset(-1);
 | 
						|
            this._update();
 | 
						|
          } else if (event.target == this.context.buttonNext) {
 | 
						|
            event.target.classList.add("active");
 | 
						|
            this.state.dateKeeper.setMonthByOffset(1);
 | 
						|
            this._update();
 | 
						|
          }
 | 
						|
          break;
 | 
						|
        }
 | 
						|
        case "mouseup": {
 | 
						|
          if (
 | 
						|
            event.target == this.context.buttonPrev ||
 | 
						|
            event.target == this.context.buttonNext
 | 
						|
          ) {
 | 
						|
            event.target.classList.remove("active");
 | 
						|
          }
 | 
						|
        }
 | 
						|
      }
 | 
						|
    },
 | 
						|
 | 
						|
    /**
 | 
						|
     * Handle postMessage events.
 | 
						|
     *
 | 
						|
     * @param {Event} event
 | 
						|
     */
 | 
						|
    handleMessage(event) {
 | 
						|
      switch (event.data.name) {
 | 
						|
        case "PickerSetValue": {
 | 
						|
          this.set(event.data.detail);
 | 
						|
          break;
 | 
						|
        }
 | 
						|
        case "PickerInit": {
 | 
						|
          this.init(event.data.detail);
 | 
						|
          break;
 | 
						|
        }
 | 
						|
      }
 | 
						|
    },
 | 
						|
 | 
						|
    /**
 | 
						|
     * Set the date state and update the components with the new state.
 | 
						|
     *
 | 
						|
     * @param {Object} dateState
 | 
						|
     *        {
 | 
						|
     *          {Number} year [optional]
 | 
						|
     *          {Number} month [optional]
 | 
						|
     *          {Number} date [optional]
 | 
						|
     *        }
 | 
						|
     */
 | 
						|
    set({ year, month, day }) {
 | 
						|
      const { dateKeeper } = this.state;
 | 
						|
 | 
						|
      dateKeeper.setCalendarMonth({
 | 
						|
        year,
 | 
						|
        month,
 | 
						|
      });
 | 
						|
      dateKeeper.setSelection({
 | 
						|
        year,
 | 
						|
        month,
 | 
						|
        day,
 | 
						|
      });
 | 
						|
      this._update({ noSmoothScroll: true });
 | 
						|
    },
 | 
						|
  };
 | 
						|
 | 
						|
  /**
 | 
						|
   * MonthYear is a component that handles the month & year spinners
 | 
						|
   *
 | 
						|
   * @param {Object} options
 | 
						|
   *        {
 | 
						|
   *          {String} locale
 | 
						|
   *          {Function} setYear
 | 
						|
   *          {Function} setMonth
 | 
						|
   *          {Function} getMonthString
 | 
						|
   *          {Array<String>} datetimeOrders
 | 
						|
   *        }
 | 
						|
   * @param {DOMElement} context
 | 
						|
   */
 | 
						|
  function MonthYear(options, context) {
 | 
						|
    const spinnerSize = 5;
 | 
						|
    const yearFormat = new Intl.DateTimeFormat(options.locale, {
 | 
						|
      year: "numeric",
 | 
						|
      timeZone: "UTC",
 | 
						|
    }).format;
 | 
						|
    const dateFormat = new Intl.DateTimeFormat(options.locale, {
 | 
						|
      year: "numeric",
 | 
						|
      month: "long",
 | 
						|
      timeZone: "UTC",
 | 
						|
    }).format;
 | 
						|
    const spinnerOrder =
 | 
						|
      options.datetimeOrders.indexOf("month") <
 | 
						|
      options.datetimeOrders.indexOf("year")
 | 
						|
        ? "order-month-year"
 | 
						|
        : "order-year-month";
 | 
						|
 | 
						|
    context.monthYearView.classList.add(spinnerOrder);
 | 
						|
 | 
						|
    this.context = context;
 | 
						|
    this.state = { dateFormat };
 | 
						|
    this.props = {};
 | 
						|
    this.components = {
 | 
						|
      month: new Spinner(
 | 
						|
        {
 | 
						|
          id: "spinner-month",
 | 
						|
          setValue: month => {
 | 
						|
            this.state.isMonthSet = true;
 | 
						|
            options.setMonth(month);
 | 
						|
          },
 | 
						|
          getDisplayString: options.getMonthString,
 | 
						|
          viewportSize: spinnerSize,
 | 
						|
        },
 | 
						|
        context.monthYearView
 | 
						|
      ),
 | 
						|
      year: new Spinner(
 | 
						|
        {
 | 
						|
          id: "spinner-year",
 | 
						|
          setValue: year => {
 | 
						|
            this.state.isYearSet = true;
 | 
						|
            options.setYear(year);
 | 
						|
          },
 | 
						|
          getDisplayString: year =>
 | 
						|
            yearFormat(new Date(new Date(0).setUTCFullYear(year))),
 | 
						|
          viewportSize: spinnerSize,
 | 
						|
        },
 | 
						|
        context.monthYearView
 | 
						|
      ),
 | 
						|
    };
 | 
						|
 | 
						|
    this._attachEventListeners();
 | 
						|
  }
 | 
						|
 | 
						|
  MonthYear.prototype = {
 | 
						|
    /**
 | 
						|
     * Set new properties and pass them to components
 | 
						|
     *
 | 
						|
     * @param {Object} props
 | 
						|
     *        {
 | 
						|
     *          {Boolean} isVisible
 | 
						|
     *          {Date} dateObj
 | 
						|
     *          {Array<Object>} months
 | 
						|
     *          {Array<Object>} years
 | 
						|
     *          {Function} toggleMonthPicker
 | 
						|
     *         }
 | 
						|
     */
 | 
						|
    setProps(props) {
 | 
						|
      this.context.monthYear.textContent = this.state.dateFormat(props.dateObj);
 | 
						|
 | 
						|
      if (props.isVisible) {
 | 
						|
        this.context.monthYear.classList.add("active");
 | 
						|
        this.components.month.setState({
 | 
						|
          value: props.dateObj.getUTCMonth(),
 | 
						|
          items: props.months,
 | 
						|
          isInfiniteScroll: true,
 | 
						|
          isValueSet: this.state.isMonthSet,
 | 
						|
          smoothScroll: !(this.state.firstOpened || props.noSmoothScroll),
 | 
						|
        });
 | 
						|
        this.components.year.setState({
 | 
						|
          value: props.dateObj.getUTCFullYear(),
 | 
						|
          items: props.years,
 | 
						|
          isInfiniteScroll: false,
 | 
						|
          isValueSet: this.state.isYearSet,
 | 
						|
          smoothScroll: !(this.state.firstOpened || props.noSmoothScroll),
 | 
						|
        });
 | 
						|
        this.state.firstOpened = false;
 | 
						|
      } else {
 | 
						|
        this.context.monthYear.classList.remove("active");
 | 
						|
        this.state.isMonthSet = false;
 | 
						|
        this.state.isYearSet = false;
 | 
						|
        this.state.firstOpened = true;
 | 
						|
      }
 | 
						|
 | 
						|
      this.props = Object.assign(this.props, props);
 | 
						|
    },
 | 
						|
 | 
						|
    /**
 | 
						|
     * Handle events
 | 
						|
     * @param  {DOMEvent} event
 | 
						|
     */
 | 
						|
    handleEvent(event) {
 | 
						|
      switch (event.type) {
 | 
						|
        case "click": {
 | 
						|
          this.props.toggleMonthPicker();
 | 
						|
          break;
 | 
						|
        }
 | 
						|
      }
 | 
						|
    },
 | 
						|
 | 
						|
    /**
 | 
						|
     * Attach event listener to monthYear button
 | 
						|
     */
 | 
						|
    _attachEventListeners() {
 | 
						|
      this.context.monthYear.addEventListener("click", this);
 | 
						|
    },
 | 
						|
  };
 | 
						|
}
 |