UNPKG

@qooxdoo/framework

Version:

The JS Framework for Coders

794 lines (663 loc) 23.3 kB
/* ************************************************************************ qooxdoo - the new era of web development http://qooxdoo.org Copyright: 2006 STZ-IDA, Germany, http://www.stz-ida.de License: MIT: https://opensource.org/licenses/MIT See the LICENSE file in the project's top-level directory for details. Authors: * Til Schneider (til132) * Martin Wittemann (martinwittemann) ************************************************************************ */ /** * A *date chooser* is a small calendar including a navigation bar to switch the shown * month. It includes a column for the calendar week and shows one month. Selecting * a date is as easy as tapping on it. * * To be conform with all form widgets, the {@link qx.ui.form.IForm} interface * is implemented. * * The following example creates and adds a date chooser to the root element. * A listener alerts the user if a new date is selected. * * <pre class='javascript'> * var chooser = new qx.ui.control.DateChooser(); * this.getRoot().add(chooser, { left : 20, top: 20}); * * chooser.addListener("changeValue", function(e) { * alert(e.getData()); * }); * </pre> * * Additionally to a selection event an execute event is available which is * fired by doubletap or tapping the space / enter key. With this event you * can for example save the selection and close the date chooser. * * @childControl navigation-bar {qx.ui.container.Composite} container for the navigation bar controls * @childControl last-year-button-tooltip {qx.ui.tooltip.ToolTip} tooltip for the last year button * @childControl last-year-button {qx.ui.form.Button} button to jump to the last year * @childControl last-month-button-tooltip {qx.ui.tooltip.ToolTip} tooltip for the last month button * @childControl last-month-button {qx.ui.form.Button} button to jump to the last month * @childControl next-month-button-tooltip {qx.ui.tooltip.ToolTip} tooltip for the next month button * @childControl next-month-button {qx.ui.form.Button} button to jump to the next month * @childControl next-year-button-tooltip {qx.ui.tooltip.ToolTip} tooltip for the next year button * @childControl next-year-button {qx.ui.form.Button} button to jump to the next year * @childControl month-year-label {qx.ui.basic.Label} shows the current month and year * @childControl week {qx.ui.basic.Label} week label (used multiple times) * @childControl weekday {qx.ui.basic.Label} weekday label (used multiple times) * @childControl day {qx.ui.basic.Label} day label (used multiple times) * @childControl date-pane {qx.ui.container.Composite} the pane used to position the week, weekday and day labels * */ qx.Class.define("qx.ui.control.DateChooser", { extend: qx.ui.core.Widget, include: [qx.ui.core.MExecutable, qx.ui.form.MForm], implement: [qx.ui.form.IExecutable, qx.ui.form.IForm, qx.ui.form.IDateForm], /* ***************************************************************************** CONSTRUCTOR ***************************************************************************** */ /** * @param date {Date ? null} The initial date to show. If <code>null</code> * the current day (today) is shown. */ construct(date) { super(); // set the layout var layout = new qx.ui.layout.VBox(); this._setLayout(layout); // create the child controls this._createChildControl("navigation-bar"); this._createChildControl("date-pane"); // Support for key events this.addListener("keypress", this._onKeyPress); // initialize format - moved from statics{} to constructor due to [BUG #7149] var DateChooser = qx.ui.control.DateChooser; if (!DateChooser.MONTH_YEAR_FORMAT) { DateChooser.MONTH_YEAR_FORMAT = qx.locale.Date.getDateTimeFormat( "yyyyMMMM", "MMMM yyyy" ); } // Show the right date var shownDate = date != null ? date : new Date(); this.showMonth(shownDate.getMonth(), shownDate.getFullYear()); // listen for locale changes if (qx.core.Environment.get("qx.dynlocale")) { this.__changeLocaleDatePaneListenerId = qx.locale.Manager.getInstance().addListener( "changeLocale", this._updateDatePane, this ); } // register pointer up and down handler this.addListener("pointerdown", this._onPointerUpDown, this); this.addListener("pointerup", this._onPointerUpDown, this); }, /* ***************************************************************************** STATICS ***************************************************************************** */ statics: { /** * @type {string} The format for the date year label at the top center. */ MONTH_YEAR_FORMAT: null, /** * @type {string} The format for the weekday labels (the headers of the date table). */ WEEKDAY_FORMAT: "EE", /** * @type {string} The format for the week numbers (the labels of the left column). */ WEEK_FORMAT: "ww" }, /* ***************************************************************************** PROPERTIES ***************************************************************************** */ properties: { // overridden appearance: { refine: true, init: "datechooser" }, // overridden width: { refine: true, init: 200 }, // overridden height: { refine: true, init: 150 }, /** The currently shown month. 0 = january, 1 = february, and so on. */ shownMonth: { check: "Integer", init: null, nullable: true, event: "changeShownMonth" }, /** The currently shown year. */ shownYear: { check: "Integer", init: null, nullable: true, event: "changeShownYear" }, /** * The date value of the widget. * * CAUTION: Date is a reference type. If you call `setValue` with * a Date object, and then change that Date object's value, e.g., * with `date.setYear()`, and then call `setValue` with that same * Date object, ** the widget will not update **. Instead, you * should instantiate a new Date object with the old (modified) * date object as its initiatializer, and pass that to `setValue`, * e.g., * * ``` * date = new Date(); * dateChooser.setValue(date); * // ...some time later... * date = date.setDate(date.getDate() + 1); * // dateChooser.setValue(date); // This will NOT update the widget * dateChooser.setValue(new Date(date)); // This WILL update the widget * ``` */ value: { check: "Date", init: null, nullable: true, event: "changeValue", apply: "_applyValue" }, /** * The minimal date value of the widget. Dates which are less * than this property value will be shown as disabled (not clickable). */ minValue: { check: "Date", init: null, nullable: true, apply: "_applyMinValue" }, /** * The maximal date value of the widget. Dates which are greater * than this property value will be shown as disabled (not clickable). */ maxValue: { check: "Date", init: null, nullable: true, apply: "_applyMaxValue" } }, /* ***************************************************************************** MEMBERS ***************************************************************************** */ /* eslint-disable @qooxdoo/qx/no-refs-in-members */ members: { __weekdayLabelArr: null, __dayLabelArr: null, __weekLabelArr: null, // overridden /** * @lint ignoreReferenceField(_forwardStates) */ _forwardStates: { invalid: true }, /* --------------------------------------------------------------------------- WIDGET INTERNALS --------------------------------------------------------------------------- */ // overridden _createChildControlImpl(id, hash) { var control; switch (id) { // NAVIGATION BAR STUFF case "navigation-bar": control = new qx.ui.container.Composite(new qx.ui.layout.HBox()); // Add the navigation bar elements control.add(this.getChildControl("last-year-button")); control.add(this.getChildControl("last-month-button")); control.add(this.getChildControl("month-year-label"), { flex: 1 }); control.add(this.getChildControl("next-month-button")); control.add(this.getChildControl("next-year-button")); this._add(control); break; case "last-year-button-tooltip": control = new qx.ui.tooltip.ToolTip(this.tr("Last year")); break; case "last-year-button": control = new qx.ui.toolbar.Button(); control.addState("lastYear"); control.setFocusable(false); control.setToolTip(this.getChildControl("last-year-button-tooltip")); control.addListener("tap", this._onNavButtonTap, this); break; case "last-month-button-tooltip": control = new qx.ui.tooltip.ToolTip(this.tr("Last month")); break; case "last-month-button": control = new qx.ui.toolbar.Button(); control.addState("lastMonth"); control.setFocusable(false); control.setToolTip(this.getChildControl("last-month-button-tooltip")); control.addListener("tap", this._onNavButtonTap, this); break; case "next-month-button-tooltip": control = new qx.ui.tooltip.ToolTip(this.tr("Next month")); break; case "next-month-button": control = new qx.ui.toolbar.Button(); control.addState("nextMonth"); control.setFocusable(false); control.setToolTip(this.getChildControl("next-month-button-tooltip")); control.addListener("tap", this._onNavButtonTap, this); break; case "next-year-button-tooltip": control = new qx.ui.tooltip.ToolTip(this.tr("Next year")); break; case "next-year-button": control = new qx.ui.toolbar.Button(); control.addState("nextYear"); control.setFocusable(false); control.setToolTip(this.getChildControl("next-year-button-tooltip")); control.addListener("tap", this._onNavButtonTap, this); break; case "month-year-label": control = new qx.ui.basic.Label(); control.setAllowGrowX(true); control.setAnonymous(true); break; case "week": control = new qx.ui.basic.Label(); control.setAllowGrowX(true); control.setAllowGrowY(true); control.setSelectable(false); control.setAnonymous(true); control.setCursor("default"); break; case "weekday": control = new qx.ui.basic.Label(); control.setAllowGrowX(true); control.setAllowGrowY(true); control.setSelectable(false); control.setAnonymous(true); control.setCursor("default"); break; case "day": control = new qx.ui.basic.Label(); control.setAllowGrowX(true); control.setAllowGrowY(true); control.setCursor("default"); control.addListener("pointerdown", this._onDayTap, this); control.addListener("dbltap", this._onDayDblTap, this); break; case "date-pane": var controlLayout = new qx.ui.layout.Grid(); control = new qx.ui.container.Composite(controlLayout); for (var i = 0; i < 8; i++) { controlLayout.setColumnFlex(i, 1); } for (var i = 0; i < 7; i++) { controlLayout.setRowFlex(i, 1); } // Create the weekdays // Add an empty label as spacer for the week numbers var label = this.getChildControl("week#0"); label.addState("header"); control.add(label, { column: 0, row: 0 }); this.__weekdayLabelArr = []; for (var i = 0; i < 7; i++) { label = this.getChildControl("weekday#" + i); control.add(label, { column: i + 1, row: 0 }); this.__weekdayLabelArr.push(label); } // Add the days this.__dayLabelArr = []; this.__weekLabelArr = []; for (var y = 0; y < 6; y++) { // Add the week label var label = this.getChildControl("week#" + (y + 1)); control.add(label, { column: 0, row: y + 1 }); this.__weekLabelArr.push(label); // Add the day labels for (var x = 0; x < 7; x++) { var label = this.getChildControl("day#" + (y * 7 + x)); control.add(label, { column: x + 1, row: y + 1 }); this.__dayLabelArr.push(label); } } this._add(control); break; } return control || super._createChildControlImpl(id); }, // apply methods _applyValue(value, old) { if ( value != null && (this.getShownMonth() != value.getMonth() || this.getShownYear() != value.getFullYear()) ) { // The new date is in another month -> Show that month this.showMonth(value.getMonth(), value.getFullYear()); } else { // The new date is in the current month -> Just change the states var newDay = value == null ? -1 : value.getDate(); for (var i = 0; i < 6 * 7; i++) { var dayLabel = this.__dayLabelArr[i]; if (dayLabel.hasState("otherMonth")) { if (dayLabel.hasState("selected")) { dayLabel.removeState("selected"); } } else { var day = parseInt(dayLabel.getValue(), 10); if (day == newDay) { dayLabel.addState("selected"); } else if (dayLabel.hasState("selected")) { dayLabel.removeState("selected"); } } } } }, _applyMinValue(){ this._updateDatePane(); }, _applyMaxValue(){ this._updateDatePane(); }, /* --------------------------------------------------------------------------- EVENT HANDLER --------------------------------------------------------------------------- */ /** * Handler which stops the propagation of the tap event if * the navigation bar or calendar headers will be tapped. * * @param e {qx.event.type.Pointer} The pointer up / down event */ _onPointerUpDown(e) { var target = e.getTarget(); if ( target == this.getChildControl("navigation-bar") || target == this.getChildControl("date-pane") ) { e.stopPropagation(); return; } }, /** * Event handler. Called when a navigation button has been tapped. * * @param evt {qx.event.type.Data} The data event. */ _onNavButtonTap(evt) { var year = this.getShownYear(); var month = this.getShownMonth(); switch (evt.getCurrentTarget()) { case this.getChildControl("last-year-button"): year--; break; case this.getChildControl("last-month-button"): month--; if (month < 0) { month = 11; year--; } break; case this.getChildControl("next-month-button"): month++; if (month >= 12) { month = 0; year++; } break; case this.getChildControl("next-year-button"): year++; break; } this.showMonth(month, year); }, /** * Event handler. Called when a day has been tapped. * * @param evt {qx.event.type.Data} The event. */ _onDayTap(evt) { var time = evt.getCurrentTarget().dateTime; this.setValue(new Date(time)); }, /** * Event handler. Called when a day has been double-tapped. */ _onDayDblTap() { this.execute(); }, /** * Event handler. Called when a key was pressed. * * @param evt {qx.event.type.Data} The event. */ _onKeyPress(evt) { var dayIncrement = null; var monthIncrement = null; var yearIncrement = null; if (evt.getModifiers() == 0) { switch (evt.getKeyIdentifier()) { case "Left": dayIncrement = -1; break; case "Right": dayIncrement = 1; break; case "Up": dayIncrement = -7; break; case "Down": dayIncrement = 7; break; case "PageUp": monthIncrement = -1; break; case "PageDown": monthIncrement = 1; break; case "Escape": if (this.getValue() != null) { this.setValue(null); return; } break; case "Enter": case "Space": if (this.getValue() != null) { this.execute(); } return; } } else if (evt.isShiftPressed()) { switch (evt.getKeyIdentifier()) { case "PageUp": yearIncrement = -1; break; case "PageDown": yearIncrement = 1; break; } } if ( dayIncrement != null || monthIncrement != null || yearIncrement != null ) { var date = this.getValue(); if (date != null) { date = new Date(date.getTime()); } if (date == null) { date = new Date(); } else { if (dayIncrement != null) { date.setDate(date.getDate() + dayIncrement); } if (monthIncrement != null) { date.setMonth(date.getMonth() + monthIncrement); } if (yearIncrement != null) { date.setFullYear(date.getFullYear() + yearIncrement); } } this.setValue(date); } }, /** * Shows a certain month. * * @param month {Integer ? null} the month to show (0 = january). If not set * the month will remain the same. * @param year {Integer ? null} the year to show. If not set the year will * remain the same. */ showMonth(month, year) { if ( (month != null && month != this.getShownMonth()) || (year != null && year != this.getShownYear()) ) { if (month != null) { this.setShownMonth(month); } if (year != null) { this.setShownYear(year); } this._updateDatePane(); } }, /** * Event handler. Used to handle the key events. * * @param e {qx.event.type.Data} The event. */ handleKeyPress(e) { this._onKeyPress(e); }, /** * Updates the date pane. */ _updateDatePane() { var DateChooser = qx.ui.control.DateChooser; var today = new Date(); var todayYear = today.getFullYear(); var todayMonth = today.getMonth(); var todayDayOfMonth = today.getDate(); var selDate = this.getValue(); var selYear = selDate == null ? -1 : selDate.getFullYear(); var selMonth = selDate == null ? -1 : selDate.getMonth(); var selDayOfMonth = selDate == null ? -1 : selDate.getDate(); var shownMonth = this.getShownMonth(); var shownYear = this.getShownYear(); var startOfWeek = qx.locale.Date.getWeekStart(); // Create a help date that points to the first of the current month var helpDate = new Date(this.getShownYear(), this.getShownMonth(), 1); var monthYearFormat = new qx.util.format.DateFormat( DateChooser.MONTH_YEAR_FORMAT ); this.getChildControl("month-year-label").setValue( monthYearFormat.format(helpDate) ); // Show the day names var firstDayOfWeek = helpDate.getDay(); var firstSundayInMonth = 1 + ((7 - firstDayOfWeek) % 7); var weekDayFormat = new qx.util.format.DateFormat( DateChooser.WEEKDAY_FORMAT ); for (var i = 0; i < 7; i++) { var day = (i + startOfWeek) % 7; var dayLabel = this.__weekdayLabelArr[i]; helpDate.setDate(firstSundayInMonth + day); dayLabel.setValue(weekDayFormat.format(helpDate)); if (qx.locale.Date.isWeekend(day)) { dayLabel.addState("weekend"); } else { dayLabel.removeState("weekend"); } } // Show the days helpDate = new Date(shownYear, shownMonth, 1, 12, 0, 0); var nrDaysOfLastMonth = (7 + firstDayOfWeek - startOfWeek) % 7; helpDate.setDate(helpDate.getDate() - nrDaysOfLastMonth); var weekFormat = new qx.util.format.DateFormat(DateChooser.WEEK_FORMAT); for (var week = 0; week < 6; week++) { this.__weekLabelArr[week].setValue(weekFormat.format(helpDate)); for (var i = 0; i < 7; i++) { var dayLabel = this.__dayLabelArr[week * 7 + i]; var year = helpDate.getFullYear(); var month = helpDate.getMonth(); var dayOfMonth = helpDate.getDate(); var isSelectedDate = selYear == year && selMonth == month && selDayOfMonth == dayOfMonth; if (isSelectedDate) { dayLabel.addState("selected"); } else { dayLabel.removeState("selected"); } if (month != shownMonth) { dayLabel.addState("otherMonth"); } else { dayLabel.removeState("otherMonth"); } var isToday = year == todayYear && month == todayMonth && dayOfMonth == todayDayOfMonth; if (isToday) { dayLabel.addState("today"); } else { dayLabel.removeState("today"); } dayLabel.setValue("" + dayOfMonth); dayLabel.dateTime = helpDate.getTime(); dayLabel.setEnabled(!this.__exceedsLimits(helpDate)); // Go to the next day helpDate.setDate(helpDate.getDate() + 1); } } monthYearFormat.dispose(); weekDayFormat.dispose(); weekFormat.dispose(); }, __exceedsLimits(date){ const d = new Date(date); d.setHours(0,0,0,0); var exceedsMin = this.getMinValue() !== null && d < this.getMinValue(); var exceedsMax = this.getMaxValue() !== null && d > this.getMaxValue(); return exceedsMin || exceedsMax; } }, /* ***************************************************************************** DESTRUCTOR ***************************************************************************** */ destruct() { if (qx.core.Environment.get("qx.dynlocale") && this.__changeLocaleDatePaneListenerId) { qx.locale.Manager.getInstance().removeListenerById( this.__changeLocaleDatePaneListenerId ); } this.__weekdayLabelArr = this.__dayLabelArr = this.__weekLabelArr = null; } });