@qooxdoo/framework
Version:
The JS Framework for Coders
587 lines (501 loc) • 16.1 kB
JavaScript
/* ************************************************************************
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
);
}
}
}
});