UNPKG

@qooxdoo/framework

Version:

The JS Framework for Coders

436 lines (373 loc) 11.8 kB
/* ************************************************************************ qooxdoo - the new era of web development http://qooxdoo.org Copyright: 2014 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: * Alexander Steitz (aback) ************************************************************************ */ /** * This is a date picker widget used to combine an input element with a calendar widget * to select a date. The calendar itself is opened as popup to save visual space. * * <h2>Markup</h2> * Each Date Picker widget is connected to an existing input element. * * <h2>CSS Classes</h2> * <table> * <thead> * <tr> * <td>Class Name</td> * <td>Applied to</td> * <td>Description</td> * </tr> * </thead> * <tbody> * <tr> * <td><code>qx-datepicker</code></td> * <td>Input element</td> * <td>Identifies the date picker widget</td> * </tr> * <tr> * <td><code>qx-datepicker-icon</code></td> * <td>Icon element</td> * <td>Identifies the (if configured) image element to open the date picker</td> * </tr> * </tbody> * </table> * * @require(qx.module.Template) * * @group (Widget) */ qx.Bootstrap.define("qx.ui.website.DatePicker", { extend: qx.ui.website.Widget, statics: { /** List of valid positions to check against */ __validPositions: null, /** * *format* * * Function which is provided with a JavaScript Date object instance. You can provide * an own format function to manipulate the value which is set to the associated input element. * * Default value: * <pre>function(date) { return date.toLocaleDateString(); }</pre> * * *readonly* * * Boolean value to control if the connected input element is read-only. * * Default value: * <pre>true</pre> * * *icon* * * Path to an icon which will be placed next to the input element as additional opener. If configured * a necessary <code>img</code> element is created and equipped with the <code>qx-datepicker-icon</code> * CSS class to style it. * * Default value: * <pre>null</pre> * * *mode* * * Which control should trigger showing the date picker. * Possible values are <code>input</code>, <code>icon</code>, <code>both</code>. * * Default value: * <pre>input</pre> * * *position* * * Position of the calendar popup from the point of view of the <code>INPUT</code> element. * Possible values are * * * <code>top-left</code> * * <code>top-center</code> * * <code>top-right</code> * * <code>bottom-left</code> * * <code>bottom-center</code> * * <code>bottom-right</code> * * <code>left-top</code> * * <code>left-middle</code> * * <code>left-bottom</code> * * <code>right-top</code> * * <code>right-middle</code> * * <code>right-bottom</code> * * Default value: * <pre>bottom-left</pre> */ _config: { format(date) { return date.toLocaleDateString(); }, readonly: true, icon: null, mode: "input", position: "bottom-left" }, /** * Factory method which converts the current collection into a collection of * Date Picker widgets. Therefore, an initialization process needs to be done which * can be configured with some parameter. * * @param date {Date?null} The initial Date of the calendar. * @return {qx.ui.website.DatePicker} A new date picker collection. * @attach {qxWeb} */ datepicker(date) { var datepicker = new qx.ui.website.DatePicker(this); datepicker.init(date); return datepicker; } }, construct(selector, context) { super(selector, context); }, members: { _calendarId: null, _iconId: null, _uniqueId: null, /** * Get the associated calendar widget * @return {qx.ui.website.Calendar} calendar widget instance */ getCalendar() { var calendarCollection = qxWeb(); calendarCollection = calendarCollection.concat( qxWeb("div#" + this._calendarId) ); return calendarCollection; }, // overridden /** * Initializes the date picker widget * * @param date {Date} A JavaScript Date object to set the current date * @return {Boolean} <code>true</code> if the widget has been initialized */ init(date) { if (!super.init()) { return false; } var uniqueId = Math.round(Math.random() * 10000); this._uniqueId = uniqueId; this.__setReadOnly(this); this.__setIcon(this); this.__addInputListener(this); var calendarId = "datepicker-calendar-" + uniqueId; var calendar = qxWeb .create('<div id="' + calendarId + '"></div>') .calendar(); calendar.on("tap", this._onCalendarTap); calendar.appendTo(document.body).hide(); // create the connection between the date picker and the corresponding calendar widget this._calendarId = calendarId; // grab tap events at the body element to be able to hide the calender popup // if the user taps outside var bodyElement = qxWeb.getDocument(this).body; qxWeb(bodyElement).on("tap", this._onBodyTap, this); // react on date selection calendar.on("changeValue", this._calendarChangeValue, this); if (date !== undefined) { calendar.setValue(date); } return true; }, // overridden render() { this.getCalendar().render(); this.__setReadOnly(this); this.__setIcon(this); this.__addInputListener(this); this.setEnabled(this.getEnabled()); return this; }, // overridden setConfig(name, config) { if (name === "position") { var validPositions = qx.ui.website.DatePicker.__validPositions; if (validPositions.indexOf(config) === -1) { throw new Error( 'Wrong config value for "position"! ' + 'Only the values "' + validPositions.join('", "') + '" are supported!' ); } } super.setConfig(name, config); return this; }, /** * Listener which handles clicks/taps on the associated input element and * opens / hides the calendar. * * @param e {Event} tap event */ _onTap(e) { if (!this.getEnabled()) { return; } var calendar = this.getCalendar(); if (calendar.getStyle("display") == "none") { // set position to make sure the width of the DOM element is correct - otherwise the DOM // element would be as wide as the parent (e.g. the body element). This would mess up the // positioning with 'placeTo' calendar .setStyle("position", "absolute") .show() .placeTo(this, this.getConfig("position")); } else { calendar.hide(); } }, /** * Stop tap events from reaching the body so the calendar won't close * @param e {Event} Tap event */ _onCalendarTap(e) { e.stopPropagation(); }, /** * Listener to the body element to be able to hide the calendar if the user clicks * or taps outside the calendar. * * @param e {Event} tap event */ _onBodyTap(e) { var target = qxWeb(e.getTarget()); // fast check for tap on the connected input field if (this.length > 0 && target.length > 0 && this[0] == target[0]) { return; } // fast check for tap on the configured icon if (this.getConfig("icon") !== null) { var icon = qxWeb("#" + this._iconId); if (icon.length > 0 && target.length > 0 && icon[0] == target[0]) { return; } } // otherwise check if the target is a child of the (rendered) calendar if (this.getCalendar().isRendered()) { var tappedCol = qxWeb(e.getTarget()); if (tappedCol.isChildOf(this.getCalendar()) === false) { this.getCalendar().hide(); } } }, /** * Listens to value selection of the calendar, Whenever the user selected a day * we write it back to the input element and hide the calendar. * * The format of the date can be controlled with the 'format' config function * * @param e {Event} selected date value */ _calendarChangeValue(e) { var formattedValue = this.getConfig("format").call(this, e); this.setValue(formattedValue); this.getCalendar().hide(); }, /** * Helper method to set the readonly status on the input element * * @param collection {qxWeb} collection to work on */ __setReadOnly(collection) { if (collection.getConfig("readonly")) { collection.setAttribute("readonly", "readonly"); } else { collection.removeAttribute("readonly"); } }, /** * Helper method to add / remove an icon next to the input element * * @param collection {qxWeb} collection to work on */ __setIcon(collection) { var icon; if (collection.getConfig("icon") === null) { icon = collection.getNext("img#" + collection._iconId); if (icon.length === 1) { icon.off("tap", this._onTap, collection); icon.remove(); } } else { var iconId = "datepicker-icon-" + collection._uniqueId; // check if there is already an icon if (collection._iconId == undefined) { collection._iconId = iconId; icon = qxWeb.create("<img>"); icon.setAttributes({ id: iconId, src: collection.getConfig("icon") }); icon.addClass(this.getCssPrefix() + "-icon"); var openingMode = collection.getConfig("mode"); if (openingMode === "icon" || openingMode === "both") { if (!icon.hasListener("tap", this._onTap, collection)) { icon.on("tap", this._onTap, collection); } } icon.insertAfter(collection); } } }, /** * Helper method to add a listener to the connected input element * if the configured mode is set. * * @param collection {qxWeb} collection to work on */ __addInputListener(collection) { if (collection.getConfig("mode") === "icon") { collection.off("tap", collection._onTap); } else { if (!collection.hasListener("tap", collection._onTap)) { collection.on("tap", collection._onTap); } } }, // overridden dispose() { this.removeAttribute("readonly"); this.getNext("img#" + this._iconId).remove(); this.off("tap", this._onTap); var bodyElement = qxWeb.getDocument(this).body; qxWeb(bodyElement).off("tap", this._onBodyTap, this); this.getCalendar() .off("changeValue", this._calendarChangeValue, this) .off("tap", this._onCalendarTap); var calendar = qxWeb("div#" + this._calendarId); calendar.remove(); calendar.dispose(); return super.dispose(); } }, defer(statics) { qxWeb.$attach({ datepicker: statics.datepicker }); statics.__validPositions = [ "top-left", "top-center", "top-right", "bottom-left", "bottom-center", "bottom-right", "left-top", "left-middle", "left-bottom", "right-top", "right-middle", "right-bottom" ]; } });