UNPKG

@qooxdoo/framework

Version:

The JS Framework for Coders

587 lines (501 loc) 16.1 kB
/* ************************************************************************ qooxdoo - the new era of web development http://qooxdoo.org Copyright: 2004-2008 1&1 Internet AG, Germany, http://www.1und1.de License: MIT: https://opensource.org/licenses/MIT See the LICENSE file in the project's top-level directory for details. Authors: * Martin Wittemann (martinwittemann) ************************************************************************ */ /** * A *date field* is like a combo box with the date as popup. As button to * open the calendar a calendar icon is shown at the right to the textfield. * * To be conform with all form widgets, the {@link qx.ui.form.IForm} interface * is implemented. * * The following example creates a date field and sets the current * date as selected. * * <pre class='javascript'> * var dateField = new qx.ui.form.DateField(); * this.getRoot().add(dateField, {top: 20, left: 20}); * dateField.setValue(new Date()); * </pre> * * @childControl list {qx.ui.control.DateChooser} date chooser component * @childControl popup {qx.ui.popup.Popup} popup which shows the list control * @childControl textfield {qx.ui.form.TextField} text field for manual date entry * @childControl button {qx.ui.form.Button} button that opens the list control */ qx.Class.define("qx.ui.form.DateField", { extend: qx.ui.core.Widget, include: [qx.ui.core.MRemoteChildrenHandling, qx.ui.form.MForm], implement: [qx.ui.form.IForm, qx.ui.form.IDateForm], /* ***************************************************************************** CONSTRUCTOR ***************************************************************************** */ construct() { super(); // set the layout var layout = new qx.ui.layout.HBox(); this._setLayout(layout); layout.setAlignY("middle"); // text field var textField = this._createChildControl("textfield"); this._createChildControl("button"); // register listeners this.addListener("tap", this._onTap, this); this.addListener("blur", this._onBlur, this); // forward the focusin and focusout events to the textfield. The textfield // is not focusable so the events need to be forwarded manually. this.addListener("focusin", e => { textField.fireNonBubblingEvent("focusin", qx.event.type.Focus); }); this.addListener("focusout", e => { textField.fireNonBubblingEvent("focusout", qx.event.type.Focus); }); // initializes the DateField with the default format this._setDefaultDateFormat(); // adds a locale change listener this._addLocaleChangeListener(); }, /* ***************************************************************************** EVENTS ***************************************************************************** */ events: { /** Whenever the value is changed this event is fired * * Event data: The new text value of the field. */ changeValue: "qx.event.type.Data" }, /* ***************************************************************************** PROPERTIES ***************************************************************************** */ properties: { /** The formatter, which converts the selected date to a string. **/ dateFormat: { check: "qx.util.format.DateFormat", apply: "_applyDateFormat" }, /** * String value which will be shown as a hint if the field is all of: * unset, unfocused and enabled. Set to null to not show a placeholder * text. */ placeholder: { check: "String", nullable: true, apply: "_applyPlaceholder" }, // overridden appearance: { refine: true, init: "datefield" }, // overridden focusable: { refine: true, init: true }, // overridden width: { refine: true, init: 120 } }, /* ***************************************************************************** MEMBERS ***************************************************************************** */ statics: { __dateFormat: null, __formatter: null, /** * Get the shared default date formatter * * @return {qx.util.format.DateFormat} The shared date formatter */ getDefaultDateFormatter() { var format = qx.locale.Date.getDateFormat("medium").toString(); if (format == this.__dateFormat) { return this.__formatter; } if (this.__formatter) { this.__formatter.dispose(); } this.__formatter = new qx.util.format.DateFormat( format, qx.locale.Manager.getInstance().getLocale() ); this.__dateFormat = format; return this.__formatter; } }, /* ***************************************************************************** MEMBERS ***************************************************************************** */ /* eslint-disable @qooxdoo/qx/no-refs-in-members */ members: { __localeListenerId: null, /** * @lint ignoreReferenceField(_forwardStates) */ _forwardStates: { focused: true, invalid: true }, /* --------------------------------------------------------------------------- PROTECTED METHODS --------------------------------------------------------------------------- */ /** * Sets the default date format which is returned by * {@link #getDefaultDateFormatter}. You can override this method to * define your own default format. */ _setDefaultDateFormat() { this.setDateFormat(qx.ui.form.DateField.getDefaultDateFormatter()); }, /** * Checks for "qx.dynlocale" and adds a listener to the locale changes. * On every change, {@link #_setDefaultDateFormat} is called to reinitialize * the format. You can easily override that method to prevent that behavior. */ _addLocaleChangeListener() { // listen for locale changes if (qx.core.Environment.get("qx.dynlocale")) { this.__localeListenerId = qx.locale.Manager.getInstance().addListener( "changeLocale", () => { this._setDefaultDateFormat(); } ); } }, /* --------------------------------------------------------------------------- PUBLIC METHODS --------------------------------------------------------------------------- */ /** * This method sets the date, which will be formatted according to * #dateFormat to the date field. It will also select the date in the * calendar popup. * * @param value {Date} The date to set. */ setValue(value) { // set the date to the textfield var textField = this.getChildControl("textfield"); textField.setValue(this.getDateFormat().format(value)); // set the date in the datechooser var dateChooser = this.getChildControl("list"); dateChooser.setValue(value); }, /** * Returns the current set date, parsed from the input-field * corresponding to the {@link #dateFormat}. * If the given text could not be parsed, <code>null</code> will be returned. * * @return {Date} The currently set date. */ getValue() { // get the value of the textfield var textfieldValue = this.getChildControl("textfield").getValue(); // return the parsed date try { if (textfieldValue == null || textfieldValue.length == 0) { return null; } return this.getDateFormat().parse(textfieldValue); } catch (ex) { return null; } }, /** * Resets the DateField. The textfield will be empty and the datechooser * will also have no selection. */ resetValue() { // set the date to the textfield var textField = this.getChildControl("textfield"); textField.setValue(""); // set the date in the datechooser var dateChooser = this.getChildControl("list"); dateChooser.setValue(null); }, /* --------------------------------------------------------------------------- LIST STUFF --------------------------------------------------------------------------- */ /** * Shows the date chooser popup. */ open() { var popup = this.getChildControl("popup"); popup.placeToWidget(this, true); popup.show(); }, /** * Hides the date chooser popup. */ close() { var popup = this.getChildControl("popup", true); if (popup && popup.isVisible()) { popup.hide(); } }, /** * Toggles the date chooser popup visibility. */ toggle() { var isListOpen = this.getChildControl("popup").isVisible(); if (isListOpen) { this.close(); } else { this.open(); } }, /* --------------------------------------------------------------------------- PROPERTY APPLY METHODS --------------------------------------------------------------------------- */ // property apply routine _applyDateFormat(value, old) { // if old is undefined or null do nothing if (!old) { return; } // get the date with the old date format try { var textfield = this.getChildControl("textfield"); var dateStr = textfield.getValue(); if (dateStr != null) { var currentDate = old.parse(dateStr); textfield.setValue(value.format(currentDate)); } } catch (ex) { // do nothing if the former date could not be parsed } }, // property apply routine _applyPlaceholder(value, old) { this.getChildControl("textfield").setPlaceholder(value); }, /* --------------------------------------------------------------------------- WIDGET API --------------------------------------------------------------------------- */ // overridden _createChildControlImpl(id, hash) { var control; switch (id) { case "textfield": control = new qx.ui.form.TextField(); control.setFocusable(false); control.addState("inner"); control.addListener( "changeValue", this._onTextFieldChangeValue, this ); control.addListener("blur", this.close, this); this._add(control, { flex: 1 }); break; case "button": control = new qx.ui.form.Button(); control.setFocusable(false); control.setKeepActive(true); control.addState("inner"); control.addListener("execute", this.toggle, this); this._add(control); break; case "list": control = new qx.ui.control.DateChooser(); control.setFocusable(false); control.setKeepFocus(true); control.addListener("execute", this._onChangeDate, this); break; case "popup": control = new qx.ui.popup.Popup(new qx.ui.layout.VBox()); control.setAutoHide(false); control.add(this.getChildControl("list")); control.addListener("pointerup", this._onChangeDate, this); control.addListener( "changeVisibility", this._onPopupChangeVisibility, this ); break; } return control || super._createChildControlImpl(id); }, /* --------------------------------------------------------------------------- EVENT LISTENERS --------------------------------------------------------------------------- */ /** * Handler method which handles the tap on the calender popup. * * @param e {qx.event.type.Pointer} The pointer event. */ _onChangeDate(e) { var textField = this.getChildControl("textfield"); var selectedDate = this.getChildControl("list").getValue(); textField.setValue(this.getDateFormat().format(selectedDate)); this.close(); }, /** * Toggles the popup's visibility. * * @param e {qx.event.type.Pointer} Pointer tap event */ _onTap(e) { this.close(); }, /** * Handler for the blur event of the current widget. * * @param e {qx.event.type.Focus} The blur event. */ _onBlur(e) { this.close(); }, /** * Handler method which handles the key press. It forwards all key event * to the opened date chooser except the escape key event. Escape closes * the popup. * If the list is cloned, all key events will not be processed further. * * @param e {qx.event.type.KeySequence} Keypress event */ _onKeyPress(e) { // get the key identifier var iden = e.getKeyIdentifier(); if (iden == "Down" && e.isAltPressed()) { this.toggle(); e.stopPropagation(); return; } // if the popup is closed, ignore all var popup = this.getChildControl("popup"); if (popup.getVisibility() == "hidden") { return; } // hide the list always on escape if (iden == "Escape") { this.close(); e.stopPropagation(); return; } // Stop navigation keys when popup is open if ( iden === "Left" || iden === "Right" || iden === "Down" || iden === "Up" ) { e.preventDefault(); } // forward the rest of the events to the date chooser this.getChildControl("list").handleKeyPress(e); }, /** * Redirects changeVisibility event from the list to this widget. * * @param e {qx.event.type.Data} Property change event */ _onPopupChangeVisibility(e) { e.getData() == "visible" ? this.addState("popupOpen") : this.removeState("popupOpen"); // Synchronize the chooser with the current value on every // opening of the popup. This is needed when the value has been // modified and not saved yet (e.g. no blur) var popup = this.getChildControl("popup"); if (popup.isVisible()) { var chooser = this.getChildControl("list"); var date = this.getValue(); chooser.setValue(date); } }, /** * Reacts on value changes of the text field and syncs the * value to the combobox. * * @param e {qx.event.type.Data} Change event */ _onTextFieldChangeValue(e) { // Apply to popup var date = this.getValue(); if (date != null) { var list = this.getChildControl("list"); list.setValue(date); } // Fire event this.fireDataEvent("changeValue", this.getValue()); }, /** * Checks if the textfield of the DateField is empty. * * @return {Boolean} True, if the textfield of the DateField is empty. */ isEmpty() { var value = this.getChildControl("textfield").getValue(); return value == null || value == ""; }, // overridden focus() { super.focus(); this.getChildControl("textfield").getFocusElement().focus(); }, // overridden tabFocus() { var field = this.getChildControl("textfield"); field.getFocusElement().focus(); field.selectAllText(); }, // overridden setAriaLabel(label) { this.getChildControl("textfield").setAriaLabel(label); }, // overridden addAriaLabelledBy(...labelWidgets) { this.getChildControl("textfield").addAriaLabelledBy(...labelWidgets); }, // overridden addAriaDescribedBy(...describingWidgets) { this.getChildControl("textfield").addAriaDescribedBy( ...describingWidgets ); } }, destruct() { // listen for locale changes if (qx.core.Environment.get("qx.dynlocale")) { if (this.__localeListenerId) { qx.locale.Manager.getInstance().removeListenerById( this.__localeListenerId ); } } } });