/** * A class that manages a group of {@link Ext.Component#floating} Components and provides z-order management, * and Component activation behavior, including masking below the active (topmost) Component. * * {@link Ext.Component#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#floating Floating} Components which are descendants of {@link Ext.Component#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', statics: { zBase : 9000 }, constructor: function(container) { var me = this; me.list = {}; me.zIndexStack = []; me.front = null; 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) { container.on('resize', me._onContainerResize, me); 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 { Ext.EventManager.onWindowResize(me._onContainerResize, me); 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 { Ext.EventManager.onWindowResize(me._onContainerResize, me); me.zseed = me.getNextZSeed(); Ext.onDocumentReady(function() { me.targetEl = Ext.getBody(); }); } }, getNextZSeed: function() { return (Ext.ZIndexManager.zBase += 10000); }, setBase: function(baseZIndex) { this.zseed = baseZIndex; var result = this.assignZIndices(); this._activateLast(); return result; }, // private assignZIndices: function() { var a = this.zIndexStack, len = a.length, i = 0, zIndex = this.zseed, comp; for (; i < len; i++) { comp = a[i]; if (comp && !comp.hidden) { // 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); } } // Activate new topmost this._activateLast(); return zIndex; }, // private _setActiveChild: function(comp, oldFront) { var front = this.front; if (comp !== front) { if (front && !front.destroying) { front.setActive(false, comp); } this.front = comp; if (comp && comp != oldFront) { comp.setActive(true); if (comp.modal) { this._showModalMask(comp); } } } }, onComponentHide: function(comp){ comp.setActive(false); this._activateLast(); }, // private _activateLast: function() { var me = this, stack = me.zIndexStack, i = stack.length - 1, oldFront = me.front, comp; // There may be no visible floater to activate me.front = undefined; // Go down through the z-index stack. // Activate the next visible one down. // If that was modal, then we're done for (; i >= 0 && stack[i].hidden; --i); if ((comp = stack[i])) { me._setActiveChild(comp, oldFront); if (comp.modal) { return; } } // If the new top one was not modal, keep going down to find the next visible // modal one to shift the modal mask down under for (; i >= 0; --i) { comp = stack[i]; // If we find a visible modal further down the zIndex stack, move the mask to just under it. if (comp.isVisible() && comp.modal) { me._showModalMask(comp); return; } } // No visible modal Component was found in the run down the stack. // So hide the modal mask me._hideModalMask(); }, _showModalMask: function(comp) { var me = this, zIndex = comp.el.getStyle('zIndex') - 4, maskTarget = comp.floatParent ? comp.floatParent.getTargetEl() : comp.container, viewSize = maskTarget.getBox(); if (maskTarget.dom === document.body) { viewSize.height = Math.max(document.body.scrollHeight, Ext.dom.Element.getDocumentHeight()); viewSize.width = Math.max(document.body.scrollWidth, viewSize.width); } if (!me.mask) { me.mask = Ext.getBody().createChild({ cls: Ext.baseCSSPrefix + 'mask' }); me.mask.setVisibilityMode(Ext.Element.DISPLAY); me.mask.on('click', me._onMaskClick, me); } me.mask.maskTarget = maskTarget; maskTarget.addCls(Ext.baseCSSPrefix + 'body-masked'); me.mask.setBox(viewSize); me.mask.setStyle('zIndex', zIndex); me.mask.show(); }, _hideModalMask: function() { var mask = this.mask; if (mask && mask.isVisible()) { mask.maskTarget.removeCls(Ext.baseCSSPrefix + 'body-masked'); mask.maskTarget = undefined; mask.hide(); } }, _onMaskClick: function() { if (this.front) { this.front.focus(); } }, _onContainerResize: function() { var mask = this.mask, maskTarget, viewSize; 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(); maskTarget = mask.maskTarget; if (maskTarget.dom === document.body) { viewSize = { height: Math.max(document.body.scrollHeight, Ext.dom.Element.getDocumentHeight()), width: Math.max(document.body.scrollWidth, document.documentElement.clientWidth) }; } else { viewSize = maskTarget.getViewSize(true); } mask.setSize(viewSize); mask.show(); } }, /** * 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; me.list[comp.id] = comp; me.zIndexStack.push(comp); comp.on('hide', me.onComponentHide, me); }, /** * 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, list = me.list; delete comp.zIndexManager; if (list && list[comp.id]) { delete list[comp.id]; comp.un('hide', me.onComponentHide); Ext.Array.remove(me.zIndexStack, comp); // Destruction requires that the topmost visible floater be activated. Same as hiding. me._activateLast(); } }, /** * 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.list[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 * @return {Boolean} True if the dialog was brought to the front, else false * if it was already in front */ bringToFront : function(comp) { var me = this, result = false, zIndexStack = me.zIndexStack; comp = me.get(comp); if (comp !== me.front) { Ext.Array.remove(zIndexStack, comp); if (comp.preventBringToFront) { // this takes care of cases where a load mask should be displayed under a floated component zIndexStack.unshift(comp); } else { // the default behavior is to push onto the stack zIndexStack.push(comp); } me.assignZIndices(); result = true; this.front = comp; } if (result && comp.modal) { me._showModalMask(comp); } return result; }, /** * 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) { var me = this; comp = me.get(comp); Ext.Array.remove(me.zIndexStack, comp); me.zIndexStack.unshift(comp); me.assignZIndices(); this._activateLast(); return comp; }, /** * Hides all Components managed by this ZIndexManager. */ hideAll : function() { var list = this.list, item, id; for (id in list) { if (list.hasOwnProperty(id)) { item = list[id]; if (item.isComponent && item.isVisible()) { item.hide(); } } } }, /** * @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 i = 0, stack = this.zIndexStack, len = stack.length, comp; this.tempHidden = []; for (; i < len; i++) { comp = stack[i]; if (comp.isVisible()) { this.tempHidden.push(comp); comp.el.hide(); } } }, /** * @private * Restores temporarily hidden managed Components to visibility. */ show: function() { var i = 0, tempHidden = this.tempHidden, len = tempHidden ? tempHidden.length : 0, comp; for (; i < len; i++) { comp = tempHidden[i]; comp.el.show(); comp.setPosition(comp.x, comp.y); } delete this.tempHidden; }, /** * Gets the currently-active Component in this ZIndexManager. * @return {Ext.Component} The active Component */ getActive : function() { return this.front; }, /** * 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 windows */ getBy : function(fn, scope) { var r = [], i = 0, stack = this.zIndexStack, len = stack.length, comp; for (; i < len; i++) { comp = stack[i]; if (fn.call(scope||comp, comp) !== false) { r.push(comp); } } return r; }, /** * 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) { var list = this.list, id, comp; for (id in list) { if (list.hasOwnProperty(id)) { comp = list[id]; 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 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, i = 0, len = stack.length, comp; for (; 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, i = stack.length, comp; for (; i-- > 0; ) { comp = stack[i]; if (comp.isComponent && fn.call(scope || comp, comp) === false) { return; } } }, destroy: function() { var me = this, list = me.list, comp, id; for (id in list) { if (list.hasOwnProperty(id)) { comp = list[id]; if (comp.isComponent) { comp.destroy(); } } } delete me.zIndexStack; delete me.list; delete me.container; delete me.targetEl; } }, 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(); });