123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211 |
- import {registerListeners, unregisterListeners} from './lib/event.js';
- import {formatDate} from './lib/date-format.js';
- import Datepicker from './Datepicker.js';
- // filter out the config options inapproprite to pass to Datepicker
- function filterOptions(options) {
- const newOpts = Object.assign({}, options);
- delete newOpts.inputs;
- delete newOpts.allowOneSidedRange;
- delete newOpts.maxNumberOfDates; // to ensure each datepicker handles a single date
- return newOpts;
- }
- function setupDatepicker(rangepicker, changeDateListener, el, options) {
- registerListeners(rangepicker, [
- [el, 'changeDate', changeDateListener],
- ]);
- new Datepicker(el, options, rangepicker);
- }
- function onChangeDate(rangepicker, ev) {
- // to prevent both datepickers trigger the other side's update each other
- if (rangepicker._updating) {
- return;
- }
- rangepicker._updating = true;
- const target = ev.target;
- if (target.datepicker === undefined) {
- return;
- }
- const datepickers = rangepicker.datepickers;
- const setDateOptions = {render: false};
- const changedSide = rangepicker.inputs.indexOf(target);
- const otherSide = changedSide === 0 ? 1 : 0;
- const changedDate = datepickers[changedSide].dates[0];
- const otherDate = datepickers[otherSide].dates[0];
- if (changedDate !== undefined && otherDate !== undefined) {
- // if the start of the range > the end, swap them
- if (changedSide === 0 && changedDate > otherDate) {
- datepickers[0].setDate(otherDate, setDateOptions);
- datepickers[1].setDate(changedDate, setDateOptions);
- } else if (changedSide === 1 && changedDate < otherDate) {
- datepickers[0].setDate(changedDate, setDateOptions);
- datepickers[1].setDate(otherDate, setDateOptions);
- }
- } else if (!rangepicker.allowOneSidedRange) {
- // to prevent the range from becoming one-sided, copy changed side's
- // selection (no matter if it's empty) to the other side
- if (changedDate !== undefined || otherDate !== undefined) {
- setDateOptions.clear = true;
- datepickers[otherSide].setDate(datepickers[changedSide].dates, setDateOptions);
- }
- }
- datepickers[0].picker.update().render();
- datepickers[1].picker.update().render();
- delete rangepicker._updating;
- }
- /**
- * Class representing a date range picker
- */
- export default class DateRangePicker {
- /**
- * Create a date range picker
- * @param {Element} element - element to bind a date range picker
- * @param {Object} [options] - config options
- */
- constructor(element, options = {}) {
- const inputs = Array.isArray(options.inputs)
- ? options.inputs
- : Array.from(element.querySelectorAll('input'));
- if (inputs.length < 2) {
- return;
- }
- element.rangepicker = this;
- this.element = element;
- this.inputs = inputs.slice(0, 2);
- this.allowOneSidedRange = !!options.allowOneSidedRange;
- const changeDateListener = onChangeDate.bind(null, this);
- const cleanOptions = filterOptions(options);
- // in order for initial date setup to work right when pcicLvel > 0,
- // let Datepicker constructor add the instance to the rangepicker
- const datepickers = [];
- Object.defineProperty(this, 'datepickers', {
- get() {
- return datepickers;
- },
- });
- setupDatepicker(this, changeDateListener, this.inputs[0], cleanOptions);
- setupDatepicker(this, changeDateListener, this.inputs[1], cleanOptions);
- Object.freeze(datepickers);
- // normalize the range if inital dates are given
- if (datepickers[0].dates.length > 0) {
- onChangeDate(this, {target: this.inputs[0]});
- } else if (datepickers[1].dates.length > 0) {
- onChangeDate(this, {target: this.inputs[1]});
- }
- }
- /**
- * @type {Array} - selected date of the linked date pickers
- */
- get dates() {
- return this.datepickers.length === 2
- ? [
- this.datepickers[0].dates[0],
- this.datepickers[1].dates[0],
- ]
- : undefined;
- }
- /**
- * Set new values to the config options
- * @param {Object} options - config options to update
- */
- setOptions(options) {
- this.allowOneSidedRange = !!options.allowOneSidedRange;
- const cleanOptions = filterOptions(options);
- this.datepickers[0].setOptions(cleanOptions);
- this.datepickers[1].setOptions(cleanOptions);
- }
- /**
- * Destroy the DateRangePicker instance
- * @return {DateRangePicker} - the instance destroyed
- */
- destroy() {
- this.datepickers[0].destroy();
- this.datepickers[1].destroy();
- unregisterListeners(this);
- delete this.element.rangepicker;
- }
- /**
- * Get the start and end dates of the date range
- *
- * The method returns Date objects by default. If format string is passed,
- * it returns date strings formatted in given format.
- * The result array always contains 2 items (start date/end date) and
- * undefined is used for unselected side. (e.g. If none is selected,
- * the result will be [undefined, undefined]. If only the end date is set
- * when allowOneSidedRange config option is true, [undefined, endDate] will
- * be returned.)
- *
- * @param {String} [format] - Format string to stringify the dates
- * @return {Array} - Start and end dates
- */
- getDates(format = undefined) {
- const callback = format
- ? date => formatDate(date, format, this.datepickers[0].config.locale)
- : date => new Date(date);
- return this.dates.map(date => date === undefined ? date : callback(date));
- }
- /**
- * Set the start and end dates of the date range
- *
- * The method calls datepicker.setDate() internally using each of the
- * arguments in start→end order.
- *
- * When a clear: true option object is passed instead of a date, the method
- * clears the date.
- *
- * If an invalid date, the same date as the current one or an option object
- * without clear: true is passed, the method considers that argument as an
- * "ineffective" argument because calling datepicker.setDate() with those
- * values makes no changes to the date selection.
- *
- * When the allowOneSidedRange config option is false, passing {clear: true}
- * to clear the range works only when it is done to the last effective
- * argument (in other words, passed to rangeEnd or to rangeStart along with
- * ineffective rangeEnd). This is because when the date range is changed,
- * it gets normalized based on the last change at the end of the changing
- * process.
- *
- * @param {Date|Number|String|Object} rangeStart - Start date of the range
- * or {clear: true} to clear the date
- * @param {Date|Number|String|Object} rangeEnd - End date of the range
- * or {clear: true} to clear the date
- */
- setDates(rangeStart, rangeEnd) {
- const [datepicker0, datepicker1] = this.datepickers;
- const origDates = this.dates;
- // If range normalization runs on every change, we can't set a new range
- // that starts after the end of the current range correctly because the
- // normalization process swaps start↔︎end right after setting the new start
- // date. To prevent this, the normalization process needs to run once after
- // both of the new dates are set.
- this._updating = true;
- datepicker0.setDate(rangeStart);
- datepicker1.setDate(rangeEnd);
- delete this._updating;
- if (datepicker1.dates[0] !== origDates[1]) {
- onChangeDate(this, {target: this.inputs[1]});
- } else if (datepicker0.dates[0] !== origDates[0]) {
- onChangeDate(this, {target: this.inputs[0]});
- }
- }
- }
|