|
@@ -0,0 +1,808 @@
|
|
|
+/**
|
|
|
+ * A class that manages a group of {@link Ext.Component#cfg-floating} Components and
|
|
|
+ * provides z-order management, and Component activation behavior, including masking
|
|
|
+ * below the active (topmost) Component.
|
|
|
+ *
|
|
|
+ * {@link Ext.Component#cfg-floating Floating} Components which are rendered directly
|
|
|
+ * into the document (such as {@link Ext.window.Window Window}s) which are
|
|
|
+ * {@link Ext.Component#method-show show}n are managed by a
|
|
|
+ * {@link Ext.WindowManager global instance}.
|
|
|
+ *
|
|
|
+ * {@link Ext.Component#cfg-floating Floating} Components which are descendants of
|
|
|
+ * {@link Ext.Component#cfg-floating floating} *Containers* (for example a
|
|
|
+ * {@link Ext.view.BoundList BoundList} within an {@link Ext.window.Window Window},
|
|
|
+ * or a {@link Ext.menu.Menu Menu}), are managed by a ZIndexManager owned by that floating
|
|
|
+ * Container. Therefore ComboBox dropdowns within Windows will have managed z-indices guaranteed
|
|
|
+ * to be correct, relative to the Window.
|
|
|
+ */
|
|
|
+Ext.define('Ext.ZIndexManager', {
|
|
|
+ alternateClassName: 'Ext.WindowGroup',
|
|
|
+
|
|
|
+ requires: [
|
|
|
+ 'Ext.util.SorterCollection',
|
|
|
+ 'Ext.util.FilterCollection',
|
|
|
+ 'Ext.GlobalEvents'
|
|
|
+ ],
|
|
|
+
|
|
|
+ statics: {
|
|
|
+ zBase: 9000,
|
|
|
+ activeCounter: 0
|
|
|
+ },
|
|
|
+
|
|
|
+ reflowSuspended: 0,
|
|
|
+
|
|
|
+ /**
|
|
|
+ * @private
|
|
|
+ */
|
|
|
+ constructor: function(container) {
|
|
|
+ var me = this;
|
|
|
+
|
|
|
+ me.id = Ext.id(null, 'zindex-mgr-');
|
|
|
+
|
|
|
+ // The stack is a collection sorted on the incrementing activeCounter ascending,
|
|
|
+ // so recently active components sort to the top.
|
|
|
+ // The component's alwaysOnTop flag takes priority in the sort order and
|
|
|
+ // cause the component to gravitate to the correct end of the stack.
|
|
|
+ me.zIndexStack = new Ext.util.Collection({
|
|
|
+ sorters: {
|
|
|
+ sorterFn: function(comp1, comp2) {
|
|
|
+ var ret = (comp1.alwaysOnTop || 0) - (comp2.alwaysOnTop || 0);
|
|
|
+
|
|
|
+ if (!ret) {
|
|
|
+ ret = comp1.getActiveCounter() - comp2.getActiveCounter();
|
|
|
+ }
|
|
|
+
|
|
|
+ return ret;
|
|
|
+ }
|
|
|
+ },
|
|
|
+ filters: {
|
|
|
+ filterFn: function(comp) {
|
|
|
+ return comp.isVisible();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ // zIndexStack will call into this class on key lifecycle events if methods exist here.
|
|
|
+ // Specifically, we implement onCollectionSort which is called by Component's updaters
|
|
|
+ // for activeCounter and alwaysOnTop.
|
|
|
+ me.zIndexStack.addObserver(me);
|
|
|
+ me.front = null;
|
|
|
+ me.sortCount = 0;
|
|
|
+
|
|
|
+ // Listen for global component hiding and showing.
|
|
|
+ // onComponentShowHide only reacts if we are managing the component.
|
|
|
+ me.globalListeners = Ext.GlobalEvents.on({
|
|
|
+ // The 'beforehide' global event is a non-vetoable event fired before the component
|
|
|
+ // is hidden. We use this to sort the remaining visible components, and unmask
|
|
|
+ // if the hiding component is the sole modal.
|
|
|
+ beforehide: me.onComponentShowHide,
|
|
|
+ show: me.onComponentShowHide,
|
|
|
+ scope: me,
|
|
|
+ destroyable: true
|
|
|
+ });
|
|
|
+
|
|
|
+ if (container) {
|
|
|
+
|
|
|
+ // This is the ZIndexManager for an Ext.container.Container, base its zseed
|
|
|
+ // on the zIndex of the Container's element
|
|
|
+ if (container.isContainer) {
|
|
|
+ me.resizeListeners = container.on({
|
|
|
+ resize: me.onContainerResize,
|
|
|
+ scope: me,
|
|
|
+ destroyable: true
|
|
|
+ });
|
|
|
+
|
|
|
+ me.zseed = Ext.Number.from(
|
|
|
+ me.rendered ? container.getEl().getStyle('zIndex') : undefined,
|
|
|
+ me.getNextZSeed()
|
|
|
+ );
|
|
|
+
|
|
|
+ // The containing element we will be dealing with (eg masking) is the content target
|
|
|
+ me.targetEl = container.getTargetEl();
|
|
|
+ me.container = container;
|
|
|
+ }
|
|
|
+ // This is the ZIndexManager for a DOM element
|
|
|
+ else {
|
|
|
+ me.resizeListeners = Ext.on({
|
|
|
+ resize: me.onContainerResize,
|
|
|
+ scope: me,
|
|
|
+ destroyable: true
|
|
|
+ });
|
|
|
+
|
|
|
+ me.zseed = me.getNextZSeed();
|
|
|
+ me.targetEl = Ext.get(container);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // No container passed means we are the global WindowManager. Our target is the doc body.
|
|
|
+ // DOM must be ready to collect that ref.
|
|
|
+ else {
|
|
|
+ me.zseed = me.getNextZSeed();
|
|
|
+
|
|
|
+ Ext.onInternalReady(function() {
|
|
|
+ // We need to use lowest possible priority here to give enough time
|
|
|
+ // for layouts to run and resize if we're masking a contained component
|
|
|
+ me.resizeListeners = Ext.on({
|
|
|
+ resize: me.scheduleContainerResize,
|
|
|
+ scope: me,
|
|
|
+ destroyable: true,
|
|
|
+ priority: -10000
|
|
|
+ });
|
|
|
+
|
|
|
+ me.targetEl = Ext.getBody();
|
|
|
+ });
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ // Required to be an Observer of a Collection
|
|
|
+ getId: function() {
|
|
|
+ return this.id;
|
|
|
+ },
|
|
|
+
|
|
|
+ getNextZSeed: function() {
|
|
|
+ return (Ext.ZIndexManager.zBase += 10000);
|
|
|
+ },
|
|
|
+
|
|
|
+ setBase: function(baseZIndex) {
|
|
|
+ this.zseed = baseZIndex;
|
|
|
+
|
|
|
+ return this.onCollectionSort();
|
|
|
+ },
|
|
|
+
|
|
|
+ onCollectionSort: function() {
|
|
|
+ var me = this,
|
|
|
+ oldFront = me.front,
|
|
|
+ zIndex = me.zseed,
|
|
|
+ a = me.zIndexStack.getRange(),
|
|
|
+ len = a.length,
|
|
|
+ i, comp, topModal, topFocusable, topMost,
|
|
|
+ doFocus = !oldFront || oldFront.isVisible();
|
|
|
+
|
|
|
+ me.sortCount++;
|
|
|
+
|
|
|
+ for (i = 0; i < len; i++) {
|
|
|
+ comp = a[i];
|
|
|
+
|
|
|
+ if (comp.destroying || comp.destroyed) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Setting the zIndex of a Component returns the topmost zIndex consumed by
|
|
|
+ // that Component.
|
|
|
+ // If it's just a plain floating Component such as a BoundList, then the
|
|
|
+ // return value is the passed value plus 10, ready for the next item.
|
|
|
+ // If a floating *Container* has its zIndex set, it re-orders its managed
|
|
|
+ // floating children, starting from that new base, and returns a value 10000 above
|
|
|
+ // the highest zIndex which it allocates.
|
|
|
+ zIndex = comp.setZIndex(zIndex);
|
|
|
+
|
|
|
+ // Only register a new topmost to activate if we find one that is visible
|
|
|
+ // Unfiltered panels with hidden:true can end up here during an animated hide process
|
|
|
+ // When the hidden flag is set, and the ghost show operation kicks the ZIndexManager's
|
|
|
+ // sort.
|
|
|
+ if (!comp.hidden) {
|
|
|
+ topMost = comp;
|
|
|
+
|
|
|
+ // Track topmost visible modal so we can place the modal mask just below it.
|
|
|
+ // Any prior focusable ones just became not focusable - they'll be below our mask.
|
|
|
+ if (comp.modal) {
|
|
|
+ topModal = comp;
|
|
|
+ topFocusable = null;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Track topmost focusable floater which is above all modals.
|
|
|
+ // Unfocusable things like tooltips and toasts may be above it
|
|
|
+ // but they do not matter, the topmost *focusable* must be focused.
|
|
|
+ if (doFocus && (comp.isFocusable(true) && (comp.modal || comp.focusOnToFront))) {
|
|
|
+ topFocusable = comp;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // Sort resulted in a different topmost focusable.
|
|
|
+ if (topFocusable && topFocusable !== oldFront && !topFocusable.preventFocusOnActivate) {
|
|
|
+ topFocusable.onFocusTopmost();
|
|
|
+ }
|
|
|
+
|
|
|
+ // If we encountered a modal in our reassigment, ensure our modal mask is just below it.
|
|
|
+ if (topModal) {
|
|
|
+ // If it's the same topmost, then just ensure the
|
|
|
+ // correct z-index and size of mask.
|
|
|
+ if (topModal === me.topModal) {
|
|
|
+ me.syncModalMask(topModal);
|
|
|
+ }
|
|
|
+ // If it's a new top, we must re-show the mask because of tabbability resets.
|
|
|
+ else {
|
|
|
+ me.showModalMask(topModal);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ me.hideModalMask();
|
|
|
+ }
|
|
|
+
|
|
|
+ // Inform components of change in to of stack.
|
|
|
+ if (topMost !== me.topMost) {
|
|
|
+ if (me.topMost) {
|
|
|
+ // This one has been bumped from top.
|
|
|
+ me.topMost.onZIndexChange(false);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (topMost) {
|
|
|
+ // This one is now at the top.
|
|
|
+ topMost.onZIndexChange(true);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // Cache the top of the stack
|
|
|
+ me.front = topFocusable;
|
|
|
+ me.topModal = topModal;
|
|
|
+ me.topMost = topMost;
|
|
|
+
|
|
|
+ // Ensure the top-most component is the front
|
|
|
+ if (!me.front && me.topMost) {
|
|
|
+ me.front = me.topMost;
|
|
|
+ }
|
|
|
+
|
|
|
+ return zIndex;
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * @private
|
|
|
+ * Called from {@link Ext.util.Floating} updater methods when a config which affects
|
|
|
+ * the stack order is updated in a Component.
|
|
|
+ *
|
|
|
+ * eg {@link Ext.Component#alwaysOnTop alwaysOnTop} or
|
|
|
+ * {@link Ext.Component#activeCounter activeCounter}
|
|
|
+ */
|
|
|
+ onComponentUpdate: function(comp) {
|
|
|
+ if (!this.reflowSuspended && this.zIndexStack.contains(comp)) {
|
|
|
+ this.zIndexStack.sort();
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ suspendReflow: function() {
|
|
|
+ this.reflowSuspended++;
|
|
|
+ },
|
|
|
+
|
|
|
+ resumeReflow: function(flush) {
|
|
|
+ if (this.reflowSuspended && ! --this.reflowSuspended) {
|
|
|
+ if (flush) {
|
|
|
+ this.zIndexStack.sort();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ onComponentReady: function(comp) {
|
|
|
+ if (!this.reflowSuspended && comp.toFrontOnShow && comp.isVisible()) {
|
|
|
+ this.zIndexStack.itemChanged(comp, 'hidden');
|
|
|
+ this.zIndexStack.sort();
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * @private
|
|
|
+ * Called when the global hide and show events are fired. If it is one of our components,
|
|
|
+ * we must re-sort.
|
|
|
+ */
|
|
|
+ onComponentShowHide: function(comp) {
|
|
|
+ var me = this,
|
|
|
+ zIndexStack = me.zIndexStack,
|
|
|
+ sortCount = me.sortCount;
|
|
|
+
|
|
|
+ // If component has hidden, it will be filtered out, so we have to look in Collection's
|
|
|
+ // source if it's there.
|
|
|
+ if (comp.isFloating() && !me.hidingAll &&
|
|
|
+ (zIndexStack.getSource() || zIndexStack).contains(comp)) {
|
|
|
+ if (me.tempHidden) {
|
|
|
+ Ext.Array.remove(me.tempHidden, comp);
|
|
|
+ }
|
|
|
+
|
|
|
+ zIndexStack.beginUpdate();
|
|
|
+
|
|
|
+ // Showing. If it should go to front on show; nudge the active
|
|
|
+ // counter which will cause a stack sort.
|
|
|
+ if (comp.isVisible()) {
|
|
|
+ if (comp.toFrontOnShow) {
|
|
|
+ zIndexStack.itemChanged(comp, 'hidden');
|
|
|
+ comp.setActiveCounter(++Ext.ZIndexManager.activeCounter);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // Hiding
|
|
|
+ else {
|
|
|
+ zIndexStack.itemChanged(comp, 'hidden');
|
|
|
+ }
|
|
|
+
|
|
|
+ // We must update the frontmost according to the new stack order
|
|
|
+ // even if there has been no sort (Collection will not autosort if only one member)
|
|
|
+ zIndexStack.endUpdate();
|
|
|
+
|
|
|
+ if (me.sortCount === sortCount && !me.reflowSuspended) {
|
|
|
+ me.onCollectionSort();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Registers a floating {@link Ext.Component} with this ZIndexManager. This should not
|
|
|
+ * need to be called under normal circumstances. Floating Components (such as Windows,
|
|
|
+ * BoundLists and Menus) are automatically registered with a
|
|
|
+ * {@link Ext.Component#zIndexManager zIndexManager} at render time.
|
|
|
+ *
|
|
|
+ * Where this may be useful is moving Windows between two ZIndexManagers. For example,
|
|
|
+ * to bring the Ext.MessageBox dialog under the same manager as the Desktop's
|
|
|
+ * ZIndexManager in the desktop sample app:
|
|
|
+ *
|
|
|
+ * MyDesktop.getDesktop().getManager().register(Ext.MessageBox);
|
|
|
+ *
|
|
|
+ * @param {Ext.Component} comp The Component to register.
|
|
|
+ */
|
|
|
+ register: function(comp) {
|
|
|
+ var me = this;
|
|
|
+
|
|
|
+ if (comp.zIndexManager) {
|
|
|
+ comp.zIndexManager.unregister(comp);
|
|
|
+ }
|
|
|
+
|
|
|
+ comp.zIndexManager = me;
|
|
|
+
|
|
|
+ if (!comp.rendered) {
|
|
|
+ // Checking for rendered as opposed to hide/show is important because
|
|
|
+ // it's still possible to render a floating component and have it be visible.
|
|
|
+ // Since rendered isn't a global event, we need to react individually on each
|
|
|
+ // component and update the state in the collection after render.
|
|
|
+ // It is important to use boxready for floated components, because reordering the
|
|
|
+ // z-index stack during render can cause reflows. For components with liquid layouts,
|
|
|
+ // we must use afterrender because they don't participate in layouts
|
|
|
+ comp.on(me.getReadyEvent(comp), me.onComponentReady, me, { single: true });
|
|
|
+ }
|
|
|
+
|
|
|
+ me.zIndexStack.add(comp);
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Unregisters a {@link Ext.Component} from this ZIndexManager. This should not
|
|
|
+ * need to be called. Components are automatically unregistered upon destruction.
|
|
|
+ * See {@link #register}.
|
|
|
+ * @param {Ext.Component} comp The Component to unregister.
|
|
|
+ */
|
|
|
+ unregister: function(comp) {
|
|
|
+ var me = this;
|
|
|
+
|
|
|
+ delete comp.zIndexManager;
|
|
|
+ comp.un(me.getReadyEvent(comp), me.onComponentReady, me);
|
|
|
+ me.zIndexStack.remove(comp);
|
|
|
+
|
|
|
+ me.onCollectionSort();
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Gets a registered Component by id.
|
|
|
+ * @param {String/Object} id The id of the Component or a {@link Ext.Component} instance
|
|
|
+ * @return {Ext.Component}
|
|
|
+ */
|
|
|
+ get: function(id) {
|
|
|
+ return id.isComponent ? id : this.zIndexStack.get(id);
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Brings the specified Component to the front of any other active Components in this
|
|
|
+ * ZIndexManager.
|
|
|
+ * @param {String/Object} comp The id of the Component or a {@link Ext.Component} instance.
|
|
|
+ * @param {Boolean} preventFocus Pass `true` to prevent the component being focused when moved
|
|
|
+ * to front.
|
|
|
+ * @return {Boolean} True if the component was brought to the front, else false
|
|
|
+ * if it was already in front, or another component remains at the front due to configuration
|
|
|
+ * (eg {@link Ext.util.Floating#alwaysOnTop}, or if the component was not found.
|
|
|
+ */
|
|
|
+ bringToFront: function(comp, preventFocus) {
|
|
|
+ var me = this,
|
|
|
+ zIndexStack = me.zIndexStack,
|
|
|
+ oldFront = zIndexStack.last(),
|
|
|
+ newFront, preventFocusSetting;
|
|
|
+
|
|
|
+ comp = me.get(comp);
|
|
|
+
|
|
|
+ // Refuse to perform this operation if we do not own the passed component.
|
|
|
+ if (!comp) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ preventFocusSetting = comp.preventFocusOnActivate;
|
|
|
+
|
|
|
+ // The onCollectionSorted reaction to the setting of activeCounter will focus by default.
|
|
|
+ // Prevent it if requested.
|
|
|
+ comp.preventFocusOnActivate = preventFocus;
|
|
|
+ comp.setActiveCounter(++Ext.ZIndexManager.activeCounter);
|
|
|
+ comp.preventFocusOnActivate = preventFocusSetting;
|
|
|
+ newFront = zIndexStack.last();
|
|
|
+
|
|
|
+ // Return true if the passed component was moved to the front
|
|
|
+ // and was not already at the front
|
|
|
+ return (newFront === comp && newFront !== oldFront);
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Sends the specified Component to the back of other active Components in this ZIndexManager.
|
|
|
+ * @param {String/Object} comp The id of the Component or a {@link Ext.Component} instance
|
|
|
+ * @return {Ext.Component} The Component
|
|
|
+ */
|
|
|
+ sendToBack: function(comp) {
|
|
|
+ comp = this.get(comp);
|
|
|
+
|
|
|
+ if (comp) {
|
|
|
+ comp.setActiveCounter(0);
|
|
|
+ }
|
|
|
+
|
|
|
+ return comp || null;
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Hides all Components managed by this ZIndexManager.
|
|
|
+ */
|
|
|
+ hideAll: function() {
|
|
|
+ var me = this,
|
|
|
+ all = me.zIndexStack.getRange(),
|
|
|
+ len = all.length,
|
|
|
+ i;
|
|
|
+
|
|
|
+ me.hidingAll = true;
|
|
|
+
|
|
|
+ for (i = 0; i < len; i++) {
|
|
|
+ all[i].hide();
|
|
|
+ }
|
|
|
+
|
|
|
+ me.hidingAll = false;
|
|
|
+ me.hideModalMask();
|
|
|
+ me.front = null;
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * @private
|
|
|
+ * Temporarily hides all currently visible managed Components. This is for when
|
|
|
+ * dragging a Window which may manage a set of floating descendants in its ZIndexManager;
|
|
|
+ * they should all be hidden just for the duration of the drag.
|
|
|
+ */
|
|
|
+ hide: function() {
|
|
|
+ var me = this,
|
|
|
+ activeElement = Ext.Element.getActiveElement(),
|
|
|
+ all = me.zIndexStack.getRange(),
|
|
|
+ len = all.length,
|
|
|
+ comp, i;
|
|
|
+
|
|
|
+ // If any of the components contained focus, we must restore it on show.
|
|
|
+ me.focusRestoreElement = null;
|
|
|
+ (me.tempHidden || (me.tempHidden = [])).length = 0;
|
|
|
+
|
|
|
+ for (i = 0; i < len; i++) {
|
|
|
+ comp = all[i];
|
|
|
+
|
|
|
+ // Only hide currently visible floaters
|
|
|
+ if (comp.isVisible()) {
|
|
|
+ if (comp.el.contains(activeElement)) {
|
|
|
+ me.focusRestoreElement = activeElement;
|
|
|
+ }
|
|
|
+
|
|
|
+ comp.el.hide();
|
|
|
+ comp.pendingShow = comp.hidden = true;
|
|
|
+ me.tempHidden.push(comp);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * @private
|
|
|
+ * Restores temporarily hidden managed Components to visibility.
|
|
|
+ */
|
|
|
+ show: function() {
|
|
|
+ var me = this,
|
|
|
+ tempHidden = me.tempHidden,
|
|
|
+ len = tempHidden ? tempHidden.length : 0,
|
|
|
+ comp, i;
|
|
|
+
|
|
|
+ for (i = 0; i < len; i++) {
|
|
|
+ comp = tempHidden[i];
|
|
|
+ comp.hidden = false;
|
|
|
+
|
|
|
+ if (comp.pendingShow) {
|
|
|
+ comp.el.show();
|
|
|
+ comp.pendingShow = false;
|
|
|
+ comp.setPosition(comp.x, comp.y);
|
|
|
+ comp.onFloatShow();
|
|
|
+ }
|
|
|
+ // An attempt at hiding while temp hidden
|
|
|
+ // has cleared the pendingShow flag. Hide it
|
|
|
+ // properly with full event flow now.
|
|
|
+ else {
|
|
|
+ comp.hide();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ me.tempHidden = null;
|
|
|
+
|
|
|
+ if (me.focusRestoreElement) {
|
|
|
+ me.focusRestoreElement.focus();
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Gets the currently-active Component in this ZIndexManager.
|
|
|
+ * @return {Ext.Component} The active Component
|
|
|
+ */
|
|
|
+ getActive: function() {
|
|
|
+ return this.zIndexStack.last();
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Returns zero or more Components in this ZIndexManager using the custom search function passed
|
|
|
+ * to this method. The function should accept a single {@link Ext.Component} reference
|
|
|
+ * as its only argument and should return true if the Component matches the search criteria,
|
|
|
+ * otherwise it should return false.
|
|
|
+ * @param {Function} fn The search function
|
|
|
+ * @param {Object} [scope] The scope (`this` reference) in which the function is executed.
|
|
|
+ * Defaults to the Component being tested. That gets passed to the function if not specified.
|
|
|
+ * @return {Array} An array of zero or more matching floating components.
|
|
|
+ */
|
|
|
+ getBy: function(fn, scope) {
|
|
|
+ return this.zIndexStack.filterBy(fn, scope).getRange();
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Executes the specified function once for every Component in this ZIndexManager, passing each
|
|
|
+ * Component as the only parameter. Returning false from the function will stop the iteration.
|
|
|
+ * @param {Function} fn The function to execute for each item
|
|
|
+ * @param {Object} [scope] The scope (this reference) in which the function
|
|
|
+ * is executed. Defaults to the current Component in the iteration.
|
|
|
+ */
|
|
|
+ each: function(fn, scope) {
|
|
|
+ this.zIndexStack.each(fn, scope);
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Executes the specified function once for every Component in this ZIndexManager, passing each
|
|
|
+ * Component as the only parameter. Returning false from the function will stop the iteration.
|
|
|
+ * The components are passed to the function starting at the bottom and proceeding to the top.
|
|
|
+ * @param {Function} fn The function to execute for each item
|
|
|
+ * @param {Object} scope (optional) The scope (this reference) in which the function
|
|
|
+ * is executed. Defaults to the current Component in the iteration.
|
|
|
+ */
|
|
|
+ eachBottomUp: function(fn, scope) {
|
|
|
+ var stack = this.zIndexStack.getRange(),
|
|
|
+ len = stack.length,
|
|
|
+ comp, i;
|
|
|
+
|
|
|
+ for (i = 0; i < len; i++) {
|
|
|
+ comp = stack[i];
|
|
|
+
|
|
|
+ if (comp.isComponent && fn.call(scope || comp, comp) === false) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Executes the specified function once for every Component in this ZIndexManager, passing each
|
|
|
+ * Component as the only parameter. Returning false from the function will stop the iteration.
|
|
|
+ * The components are passed to the function starting at the top and proceeding to the bottom.
|
|
|
+ * @param {Function} fn The function to execute for each item
|
|
|
+ * @param {Object} [scope] The scope (this reference) in which the function
|
|
|
+ * is executed. Defaults to the current Component in the iteration.
|
|
|
+ */
|
|
|
+ eachTopDown: function(fn, scope) {
|
|
|
+ var stack = this.zIndexStack.getRange(),
|
|
|
+ comp, i;
|
|
|
+
|
|
|
+ for (i = stack.length; i-- > 0;) {
|
|
|
+ comp = stack[i];
|
|
|
+
|
|
|
+ if (comp.isComponent && fn.call(scope || comp, comp) === false) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ destroy: function() {
|
|
|
+ var me = this,
|
|
|
+ stack = me.zIndexStack.getRange(),
|
|
|
+ len = stack.length,
|
|
|
+ i;
|
|
|
+
|
|
|
+ for (i = 0; i < len; i++) {
|
|
|
+ Ext.destroy(stack[i]);
|
|
|
+ }
|
|
|
+
|
|
|
+ Ext.destroy(me.mask, me.maskShim, me.zIndexStack, me.globalListeners, me.resizeListeners);
|
|
|
+
|
|
|
+ me.callParent();
|
|
|
+ },
|
|
|
+
|
|
|
+ privates: {
|
|
|
+ getMaskBox: function() {
|
|
|
+ var maskTarget = this.mask.maskTarget;
|
|
|
+
|
|
|
+ if (maskTarget.dom === document.body) {
|
|
|
+ // If we're masking the body, subtract the border/padding
|
|
|
+ // so we don't cause scrollbar.
|
|
|
+ return {
|
|
|
+ height: Math.max(
|
|
|
+ document.body.scrollHeight, Ext.dom.Element.getDocumentHeight()
|
|
|
+ ),
|
|
|
+ width: Math.max(document.body.scrollWidth, Ext.dom.Element.getDocumentWidth()),
|
|
|
+ x: 0,
|
|
|
+ y: 0
|
|
|
+ };
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ return maskTarget.getBox();
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ getReadyEvent: function(c) {
|
|
|
+ return c.liquidLayout ? 'afterrender' : 'boxready';
|
|
|
+ },
|
|
|
+
|
|
|
+ scheduleContainerResize: function() {
|
|
|
+ // The reason we're scheduling resize handler here is to allow Responsive mixin
|
|
|
+ // to fire events and run layouts that may affect the size of the modal mask.
|
|
|
+ // Responsive will request animation frame on browser window resize event,
|
|
|
+ // we do likewise here to minimize flicker.
|
|
|
+ if (!this.containerResizeTimer) {
|
|
|
+ this.containerResizeTimer = Ext.raf(this.onContainerResize, this);
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ onContainerResize: function() {
|
|
|
+ var me = this,
|
|
|
+ mask = me.mask,
|
|
|
+ maskShim = me.maskShim,
|
|
|
+ viewSize;
|
|
|
+
|
|
|
+ me.containerResizeTimer = null;
|
|
|
+
|
|
|
+ if (mask && mask.isVisible()) {
|
|
|
+ // At the new container size, the mask might be *causing* the scrollbar,
|
|
|
+ // so to find the valid client size to mask, we must temporarily unmask
|
|
|
+ // the parent node.
|
|
|
+ mask.hide();
|
|
|
+
|
|
|
+ if (maskShim) {
|
|
|
+ maskShim.hide();
|
|
|
+ }
|
|
|
+
|
|
|
+ viewSize = me.getMaskBox();
|
|
|
+
|
|
|
+ if (maskShim) {
|
|
|
+ maskShim.setSize(viewSize);
|
|
|
+ maskShim.show();
|
|
|
+ }
|
|
|
+
|
|
|
+ mask.setSize(viewSize);
|
|
|
+ mask.show();
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ onMaskMousedown: function(e) {
|
|
|
+ // Focus frontmost modal, do not allow mousedown to focus mask.
|
|
|
+ if (this.topModal) {
|
|
|
+ this.topModal.focus();
|
|
|
+ e.preventDefault();
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ onMaskClick: function() {
|
|
|
+ var front = this.topModal,
|
|
|
+ methodName;
|
|
|
+
|
|
|
+ if (front) {
|
|
|
+ // Fire a maskclick event. Allow the onward processing by the maskClickAction method
|
|
|
+ // to be vetoed by a false return value.
|
|
|
+ if (!front.hasListeners.maskclick ||
|
|
|
+ front.fireEvent('maskclick', front) !== false) {
|
|
|
+ // We call whatever method 'maskClickAction' points to.
|
|
|
+ // By default, Windows have 'focus'. If we encounter other
|
|
|
+ // classes without that property, default to 'focus'
|
|
|
+ methodName = front.maskClickAction || 'focus';
|
|
|
+ front[methodName]();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ showModalMask: function(comp) {
|
|
|
+ var me = this,
|
|
|
+ compEl = comp.el,
|
|
|
+ maskTarget = comp.floatParent ? comp.floatParent.getEl() : comp.container,
|
|
|
+ mask = me.mask;
|
|
|
+
|
|
|
+ if (!mask) {
|
|
|
+ // Create the mask at zero size so that it does not affect upcoming
|
|
|
+ // target measurements.
|
|
|
+ me.mask = mask = Ext.getBody().createChild({
|
|
|
+ //<debug>
|
|
|
+ // tell the spec runner to ignore this element when checking if the dom is clean
|
|
|
+ 'data-sticky': true,
|
|
|
+ //</debug>
|
|
|
+
|
|
|
+ role: 'presentation',
|
|
|
+ cls: Ext.baseCSSPrefix + 'mask ' + Ext.baseCSSPrefix + 'border-box',
|
|
|
+ style: 'height:0;width:0'
|
|
|
+ });
|
|
|
+
|
|
|
+ mask.setVisibilityMode(Ext.Element.DISPLAY);
|
|
|
+
|
|
|
+ mask.on({
|
|
|
+ mousedown: me.onMaskMousedown,
|
|
|
+ click: me.onMaskClick,
|
|
|
+ scope: me
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ // If the mask is already shown, hide it before showing again
|
|
|
+ // to ensure underlying elements' tabbability is restored
|
|
|
+ else {
|
|
|
+ me.hideModalMask();
|
|
|
+ }
|
|
|
+
|
|
|
+ mask.maskTarget = maskTarget;
|
|
|
+
|
|
|
+ // Since there is no fast and reliable way to find elements above or below
|
|
|
+ // a given z-index, we just cheat and prevent tabbable elements within the
|
|
|
+ // topmost component from being made untabbable.
|
|
|
+ maskTarget.saveTabbableState({
|
|
|
+ excludeRoot: compEl
|
|
|
+ });
|
|
|
+
|
|
|
+ // Size and zIndex stack the mask (and its shim)
|
|
|
+ me.syncModalMask(comp);
|
|
|
+ },
|
|
|
+
|
|
|
+ syncModalMask: function(comp) {
|
|
|
+ var me = this,
|
|
|
+ zIndex = comp.el.getZIndex() - 4,
|
|
|
+ mask = me.mask,
|
|
|
+ shim = me.maskShim,
|
|
|
+ viewSize = me.getMaskBox();
|
|
|
+
|
|
|
+ if (shim) {
|
|
|
+ shim.setZIndex(zIndex);
|
|
|
+ shim.show();
|
|
|
+ shim.setBox(viewSize);
|
|
|
+ }
|
|
|
+
|
|
|
+ mask.setZIndex(zIndex);
|
|
|
+ mask.show();
|
|
|
+ mask.setBox(viewSize);
|
|
|
+ },
|
|
|
+
|
|
|
+ hideModalMask: function() {
|
|
|
+ var mask = this.mask,
|
|
|
+ maskShim = this.maskShim;
|
|
|
+
|
|
|
+ if (mask && mask.isVisible()) {
|
|
|
+ mask.maskTarget.restoreTabbableState();
|
|
|
+
|
|
|
+ mask.maskTarget = undefined;
|
|
|
+ mask.hide();
|
|
|
+
|
|
|
+ if (maskShim) {
|
|
|
+ maskShim.hide();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+}, function() {
|
|
|
+ /**
|
|
|
+ * @class Ext.WindowManager
|
|
|
+ * @extends Ext.ZIndexManager
|
|
|
+ *
|
|
|
+ * The default global floating Component group that is available automatically.
|
|
|
+ *
|
|
|
+ * This manages instances of floating Components which were rendered programatically without
|
|
|
+ * being added to a {@link Ext.container.Container Container}, and for floating Components
|
|
|
+ * which were added into non-floating Containers.
|
|
|
+ *
|
|
|
+ * *Floating* Containers create their own instance of ZIndexManager, and floating Components
|
|
|
+ * added at any depth below there are managed by that ZIndexManager.
|
|
|
+ *
|
|
|
+ * @singleton
|
|
|
+ */
|
|
|
+ Ext.WindowManager = Ext.WindowMgr = new this();
|
|
|
+});
|