UNPKG

@qooxdoo/framework

Version:

The JS Framework for Coders

521 lines (444 loc) 14.2 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: * Fabian Jakobs (fjakobs) ************************************************************************ */ /** * This class blocks events and can be included into all widgets. * * The {@link #block} and {@link #unblock} methods provided by this class can be used * to block any event from the widget. When blocked, * the blocker widget overlays the widget to block, including the padding area. * * @ignore(qx.ui.root.Abstract) */ qx.Class.define("qx.ui.core.Blocker", { extend: qx.core.Object, events: { /** * Fires after {@link #block} executed. */ blocked: "qx.event.type.Event", /** * Fires after {@link #unblock} executed. */ unblocked: "qx.event.type.Event" }, /** * Creates a blocker for the passed widget. * * @param widget {qx.ui.core.Widget} Widget which should be added the blocker */ construct(widget) { super(); this._widget = widget; widget.addListener("resize", this.__onBoundsChange, this); widget.addListener("move", this.__onBoundsChange, this); widget.addListener("disappear", this.__onWidgetDisappear, this); if ( qx.Class.isDefined("qx.ui.root.Abstract") && widget instanceof qx.ui.root.Abstract ) { this._isRoot = true; this.setKeepBlockerActive(true); } // dynamic theme switch if (qx.core.Environment.get("qx.dyntheme")) { this.__changeThemeBlockerListenerId = qx.theme.manager.Meta.getInstance().addListener( "changeTheme", this._onChangeTheme, this ); } this.__activeElements = []; this.__focusElements = []; }, /* ***************************************************************************** PROPERTIES ***************************************************************************** */ properties: { /** * Color of the blocker */ color: { check: "Color", init: null, nullable: true, apply: "_applyColor", themeable: true }, /** * Opacity of the blocker */ opacity: { check: "Number", init: 1, apply: "_applyOpacity", themeable: true }, /** * If this property is enabled, the blocker created with {@link #block} * will always stay activated. This means that the blocker then gets all keyboard * events, this is useful to block keyboard input on other widgets. * Take care that only one blocker instance will be kept active, otherwise your * browser will freeze. * * Setting this property to true is ignored, if the blocker is attached to a * widget with a focus handler, as this would mean that the focus handler * tries to activate the widget behind the blocker. * * fixes: * https://github.com/qooxdoo/qooxdoo/issues/9449 * https://github.com/qooxdoo/qooxdoo/issues/8104 */ keepBlockerActive: { check: "Boolean", init: false } }, /* ***************************************************************************** MEMBERS ***************************************************************************** */ members: { __blocker: null, __blockerCount: 0, __activeElements: null, __focusElements: null, __timer: null, _widget: null, _isRoot: false, __appearListener: null, /** * Adjust html element size on layout resizes. * * @param e {qx.event.type.Data} event object */ __onBoundsChange(e) { var data = e.getData(); if (this.isBlocked()) { this._updateBlockerBounds(data); } }, /** * Widget re-appears: Update blocker size/position and attach to (new) parent */ __onWidgetAppear() { this._updateBlockerBounds(this._widget.getBounds()); if (this._widget.isRootWidget()) { this._widget.getContentElement().add(this.getBlockerElement()); } else { this._widget .getLayoutParent() .getContentElement() .add(this.getBlockerElement()); } }, /** * Remove the blocker if the widget disappears */ __onWidgetDisappear() { if (this.isBlocked()) { this.getBlockerElement().getParent().remove(this.getBlockerElement()); this._widget.addListenerOnce("appear", this.__onWidgetAppear, this); } }, /** * set the blocker's size and position * @param bounds {Map} Map with the new width, height, left and top values */ _updateBlockerBounds(bounds) { this.getBlockerElement().setStyles({ width: bounds.width + "px", height: bounds.height + "px", left: bounds.left + "px", top: bounds.top + "px" }); }, // property apply _applyColor(value, old) { var color = qx.theme.manager.Color.getInstance().resolve(value); this.__setBlockersStyle("backgroundColor", color); }, // property apply _applyOpacity(value, old) { this.__setBlockersStyle("opacity", value); }, /** * Handler for the theme change. * @signature function() */ _onChangeTheme: qx.core.Environment.select("qx.dyntheme", { true() { this._applyColor(this.getColor()); }, false: null }), /** * Set the style to all blockers (blocker and content blocker). * * @param key {String} The name of the style attribute. * @param value {String} The value. */ __setBlockersStyle(key, value) { var blockers = []; this.__blocker && blockers.push(this.__blocker); for (var i = 0; i < blockers.length; i++) { blockers[i].setStyle(key, value); } }, /** * Backup the current active and focused widget. */ _backupActiveWidget() { var focusHandler = qx.event.Registration.getManager(window).getHandler( qx.event.handler.Focus ); var activeWidget = qx.ui.core.Widget.getWidgetByElement( focusHandler.getActive() ); var focusedWidget = qx.ui.core.Widget.getWidgetByElement( focusHandler.getFocus() ); this.__activeElements.push(activeWidget); this.__focusElements.push(focusedWidget); if (activeWidget) { activeWidget.deactivate(); } if (focusedWidget && focusedWidget.isFocusable()) { focusedWidget.blur(); } }, /** * Restore the current active and focused widget. */ _restoreActiveWidget() { var widget; var focusElementsLength = this.__focusElements.length; if (focusElementsLength > 0) { widget = this.__focusElements.pop(); if (widget && !widget.isDisposed() && widget.isFocusable()) { widget.focus(); } } var activeElementsLength = this.__activeElements.length; if (activeElementsLength > 0) { widget = this.__activeElements.pop(); if (widget && !widget.isDisposed()) { widget.activate(); } } }, /** * Creates the blocker element. * * @return {qx.html.Element} The blocker element */ __createBlockerElement() { return new qx.html.Blocker(this.getColor(), this.getOpacity()); }, /** * Get/create the blocker element * * @param widget {qx.ui.core.Widget} The blocker will be added to this * widget's content element * @return {qx.html.Element} The blocker element */ getBlockerElement(widget) { if (!this.__blocker) { this.__blocker = this.__createBlockerElement(); this.__blocker.setStyle("zIndex", 15); if (!widget) { if (this._isRoot) { widget = this._widget; } else { widget = this._widget.getLayoutParent(); } } widget.getContentElement().add(this.__blocker); this.__blocker.exclude(); } return this.__blocker; }, /** * Block all events from this widget by placing a transparent overlay widget, * which receives all events, exactly over the widget. */ block() { this._block(); }, /** * Adds the blocker to the appropriate element and includes it. * * @param zIndex {Number} All child widgets with a zIndex below this value will be blocked * @param blockContent {Boolean} append the blocker to the widget's content if true */ _block(zIndex, blockContent) { if (!this._isRoot && !this._widget.getLayoutParent()) { if (!this.__appearListener) { this.__appearListener = this._widget.addListenerOnce( "appear", this._block.bind(this, zIndex) ); } return; } var parent; if (this._isRoot || blockContent) { parent = this._widget; } else { parent = this._widget.getLayoutParent(); } var blocker = this.getBlockerElement(parent); if (zIndex != null) { blocker.setStyle("zIndex", zIndex); } this.__blockerCount++; if (this.__blockerCount < 2) { this._backupActiveWidget(); var bounds = this._widget.getBounds(); // no bounds -> widget not yet rendered -> bounds will be set on resize if (bounds) { this._updateBlockerBounds(bounds); } blocker.include(); if (!blockContent) { blocker.activate(); } blocker.addListener("deactivate", this.__activateBlockerElement, this); blocker.addListener("keypress", this.__stopTabEvent, this); blocker.addListener("keydown", this.__stopTabEvent, this); blocker.addListener("keyup", this.__stopTabEvent, this); this.fireEvent("blocked", qx.event.type.Event); } }, /** * Returns whether the widget is blocked. * * @return {Boolean} Whether the widget is blocked. */ isBlocked() { return this.__blockerCount > 0; }, /** * Unblock the widget blocked by {@link #block}, but it takes care of * the amount of {@link #block} calls. The blocker is only removed if * the number of {@link #unblock} calls is identical to {@link #block} calls. */ unblock() { if (this.__appearListener) { this._widget.removeListenerById(this.__appearListener); this.__appearListener = null; } if (!this.isBlocked()) { return; } this.__blockerCount--; if (this.__blockerCount < 1) { this.__unblock(); this.__blockerCount = 0; } }, /** * Unblock the widget blocked by {@link #block}, but it doesn't take care of * the amount of {@link #block} calls. The blocker is directly removed. */ forceUnblock() { if (this.__appearListener) { this._widget.removeListenerById(this.__appearListener); this.__appearListener = null; } if (!this.isBlocked()) { return; } this.__blockerCount = 0; this.__unblock(); }, /** * Unblock the widget blocked by {@link #block}. */ __unblock() { this._restoreActiveWidget(); var blocker = this.getBlockerElement(); blocker.removeListener("deactivate", this.__activateBlockerElement, this); blocker.removeListener("keypress", this.__stopTabEvent, this); blocker.removeListener("keydown", this.__stopTabEvent, this); blocker.removeListener("keyup", this.__stopTabEvent, this); blocker.exclude(); this.fireEvent("unblocked", qx.event.type.Event); }, /** * Block direct child widgets with a zIndex below <code>zIndex</code> * * @param zIndex {Integer} All child widgets with a zIndex below this value * will be blocked */ blockContent(zIndex) { this._block(zIndex, true); }, /** * Stops the passed "Tab" event. * * @param e {qx.event.type.KeySequence} event to stop. */ __stopTabEvent(e) { if (e.getKeyIdentifier() == "Tab") { e.stop(); } }, /** * Sets the blocker element to active. */ __activateBlockerElement() { // // If this._widget is attached to the focus handler as a focus root, // activating the blocker after this widget was deactivated, // leads to the focus handler re-activate the widget behind // the blocker, loosing tab handling for this._widget which is // visually in front. Hence we prevent activating the // blocker in this situation. // // fixes: // https://github.com/qooxdoo/qooxdoo/issues/9449 // https://github.com/qooxdoo/qooxdoo/issues/8104 // if ( this.getKeepBlockerActive() && !qx.ui.core.FocusHandler.getInstance().isFocusRoot(this._widget) ) { this.getBlockerElement().activate(); } } }, /* ***************************************************************************** DESTRUCTOR ***************************************************************************** */ destruct() { // remove dynamic theme listener if (qx.core.Environment.get("qx.dyntheme") && this.__changeThemeBlockerListenerId) { qx.theme.manager.Meta.getInstance().removeListenerById( this.__changeThemeBlockerListenerId ); } this._widget.removeListener("resize", this.__onBoundsChange, this); this._widget.removeListener("move", this.__onBoundsChange, this); this._widget.removeListener("appear", this.__onWidgetAppear, this); this._widget.removeListener("disappear", this.__onWidgetDisappear, this); if (this.__appearListener) { this._widget.removeListenerById(this.__appearListener); } this._disposeObjects("__blocker", "__timer"); this.__activeElements = this.__focusElements = this._widget = null; } });