Upgrade to ExtJS 4.0.7 - Released 10/19/2011
[extjs.git] / src / ZIndexManager.js
1 /*
2
3 This file is part of Ext JS 4
4
5 Copyright (c) 2011 Sencha Inc
6
7 Contact:  http://www.sencha.com/contact
8
9 GNU General Public License Usage
10 This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file.  Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
11
12 If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
13
14 */
15 /**
16  * @class Ext.ZIndexManager
17  * <p>A class that manages a group of {@link Ext.Component#floating} Components and provides z-order management,
18  * and Component activation behavior, including masking below the active (topmost) Component.</p>
19  * <p>{@link Ext.Component#floating Floating} Components which are rendered directly into the document (such as {@link Ext.window.Window Window}s) which are
20  * {@link Ext.Component#show show}n are managed by a {@link Ext.WindowManager global instance}.</p>
21  * <p>{@link Ext.Component#floating Floating} Components which are descendants of {@link Ext.Component#floating floating} <i>Containers</i>
22  * (for example a {@link Ext.view.BoundList BoundList} within an {@link Ext.window.Window Window}, or a {@link Ext.menu.Menu Menu}),
23  * are managed by a ZIndexManager owned by that floating Container. Therefore ComboBox dropdowns within Windows will have managed z-indices
24  * guaranteed to be correct, relative to the Window.</p>
25  */
26 Ext.define('Ext.ZIndexManager', {
27
28     alternateClassName: 'Ext.WindowGroup',
29
30     statics: {
31         zBase : 9000
32     },
33
34     constructor: function(container) {
35         var me = this;
36
37         me.list = {};
38         me.zIndexStack = [];
39         me.front = null;
40
41         if (container) {
42
43             // This is the ZIndexManager for an Ext.container.Container, base its zseed on the zIndex of the Container's element
44             if (container.isContainer) {
45                 container.on('resize', me._onContainerResize, me);
46                 me.zseed = Ext.Number.from(container.getEl().getStyle('zIndex'), me.getNextZSeed());
47                 // The containing element we will be dealing with (eg masking) is the content target
48                 me.targetEl = container.getTargetEl();
49                 me.container = container;
50             }
51             // This is the ZIndexManager for a DOM element
52             else {
53                 Ext.EventManager.onWindowResize(me._onContainerResize, me);
54                 me.zseed = me.getNextZSeed();
55                 me.targetEl = Ext.get(container);
56             }
57         }
58         // No container passed means we are the global WindowManager. Our target is the doc body.
59         // DOM must be ready to collect that ref.
60         else {
61             Ext.EventManager.onWindowResize(me._onContainerResize, me);
62             me.zseed = me.getNextZSeed();
63             Ext.onDocumentReady(function() {
64                 me.targetEl = Ext.getBody();
65             });
66         }
67     },
68
69     getNextZSeed: function() {
70         return (Ext.ZIndexManager.zBase += 10000);
71     },
72
73     setBase: function(baseZIndex) {
74         this.zseed = baseZIndex;
75         return this.assignZIndices();
76     },
77
78     // private
79     assignZIndices: function() {
80         var a = this.zIndexStack,
81             len = a.length,
82             i = 0,
83             zIndex = this.zseed,
84             comp;
85
86         for (; i < len; i++) {
87             comp = a[i];
88             if (comp && !comp.hidden) {
89
90                 // Setting the zIndex of a Component returns the topmost zIndex consumed by
91                 // that Component.
92                 // If it's just a plain floating Component such as a BoundList, then the
93                 // return value is the passed value plus 10, ready for the next item.
94                 // If a floating *Container* has its zIndex set, it re-orders its managed
95                 // floating children, starting from that new base, and returns a value 10000 above
96                 // the highest zIndex which it allocates.
97                 zIndex = comp.setZIndex(zIndex);
98             }
99         }
100         this._activateLast();
101         return zIndex;
102     },
103
104     // private
105     _setActiveChild: function(comp) {
106         if (comp !== this.front) {
107
108             if (this.front) {
109                 this.front.setActive(false, comp);
110             }
111             this.front = comp;
112             if (comp) {
113                 comp.setActive(true);
114                 if (comp.modal) {
115                     this._showModalMask(comp);
116                 }
117             }
118         }
119     },
120
121     // private
122     _activateLast: function(justHidden) {
123         var comp,
124             lastActivated = false,
125             i;
126
127         // Go down through the z-index stack.
128         // Activate the next visible one down.
129         // Keep going down to find the next visible modal one to shift the modal mask down under
130         for (i = this.zIndexStack.length-1; i >= 0; --i) {
131             comp = this.zIndexStack[i];
132             if (!comp.hidden) {
133                 if (!lastActivated) {
134                     this._setActiveChild(comp);
135                     lastActivated = true;
136                 }
137
138                 // Move any modal mask down to just under the next modal floater down the stack
139                 if (comp.modal) {
140                     this._showModalMask(comp);
141                     return;
142                 }
143             }
144         }
145
146         // none to activate, so there must be no modal mask.
147         // And clear the currently active property
148         this._hideModalMask();
149         if (!lastActivated) {
150             this._setActiveChild(null);
151         }
152     },
153
154     _showModalMask: function(comp) {
155         var zIndex = comp.el.getStyle('zIndex') - 4,
156             maskTarget = comp.floatParent ? comp.floatParent.getTargetEl() : Ext.get(comp.getEl().dom.parentNode),
157             parentBox;
158         
159         if (!maskTarget) {
160             //<debug>
161             Ext.global.console && Ext.global.console.warn && Ext.global.console.warn('mask target could not be found. Mask cannot be shown');
162             //</debug>
163             return;
164         }
165         
166         parentBox = maskTarget.getBox();
167
168         if (!this.mask) {
169             this.mask = Ext.getBody().createChild({
170                 cls: Ext.baseCSSPrefix + 'mask'
171             });
172             this.mask.setVisibilityMode(Ext.Element.DISPLAY);
173             this.mask.on('click', this._onMaskClick, this);
174         }
175         if (maskTarget.dom === document.body) {
176             parentBox.height = Ext.Element.getViewHeight();
177         }
178         maskTarget.addCls(Ext.baseCSSPrefix + 'body-masked');
179         this.mask.setBox(parentBox);
180         this.mask.setStyle('zIndex', zIndex);
181         this.mask.show();
182     },
183
184     _hideModalMask: function() {
185         if (this.mask && this.mask.dom.parentNode) {
186             Ext.get(this.mask.dom.parentNode).removeCls(Ext.baseCSSPrefix + 'body-masked');
187             this.mask.hide();
188         }
189     },
190
191     _onMaskClick: function() {
192         if (this.front) {
193             this.front.focus();
194         }
195     },
196
197     _onContainerResize: function() {
198         if (this.mask && this.mask.isVisible()) {
199             this.mask.setSize(Ext.get(this.mask.dom.parentNode).getViewSize(true));
200         }
201     },
202
203     /**
204      * <p>Registers a floating {@link Ext.Component} with this ZIndexManager. This should not
205      * need to be called under normal circumstances. Floating Components (such as Windows, BoundLists and Menus) are automatically registered
206      * with a {@link Ext.Component#zIndexManager zIndexManager} at render time.</p>
207      * <p>Where this may be useful is moving Windows between two ZIndexManagers. For example,
208      * to bring the Ext.MessageBox dialog under the same manager as the Desktop's
209      * ZIndexManager in the desktop sample app:</p><code><pre>
210 MyDesktop.getDesktop().getManager().register(Ext.MessageBox);
211 </pre></code>
212      * @param {Ext.Component} comp The Component to register.
213      */
214     register : function(comp) {
215         if (comp.zIndexManager) {
216             comp.zIndexManager.unregister(comp);
217         }
218         comp.zIndexManager = this;
219
220         this.list[comp.id] = comp;
221         this.zIndexStack.push(comp);
222         comp.on('hide', this._activateLast, this);
223     },
224
225     /**
226      * <p>Unregisters a {@link Ext.Component} from this ZIndexManager. This should not
227      * need to be called. Components are automatically unregistered upon destruction.
228      * See {@link #register}.</p>
229      * @param {Ext.Component} comp The Component to unregister.
230      */
231     unregister : function(comp) {
232         delete comp.zIndexManager;
233         if (this.list && this.list[comp.id]) {
234             delete this.list[comp.id];
235             comp.un('hide', this._activateLast);
236             Ext.Array.remove(this.zIndexStack, comp);
237
238             // Destruction requires that the topmost visible floater be activated. Same as hiding.
239             this._activateLast(comp);
240         }
241     },
242
243     /**
244      * Gets a registered Component by id.
245      * @param {String/Object} id The id of the Component or a {@link Ext.Component} instance
246      * @return {Ext.Component}
247      */
248     get : function(id) {
249         return typeof id == "object" ? id : this.list[id];
250     },
251
252    /**
253      * Brings the specified Component to the front of any other active Components in this ZIndexManager.
254      * @param {String/Object} comp The id of the Component or a {@link Ext.Component} instance
255      * @return {Boolean} True if the dialog was brought to the front, else false
256      * if it was already in front
257      */
258     bringToFront : function(comp) {
259         comp = this.get(comp);
260         if (comp !== this.front) {
261             Ext.Array.remove(this.zIndexStack, comp);
262             this.zIndexStack.push(comp);
263             this.assignZIndices();
264             return true;
265         }
266         if (comp.modal) {
267             this._showModalMask(comp);
268         }
269         return false;
270     },
271
272     /**
273      * Sends the specified Component to the back of other active Components in this ZIndexManager.
274      * @param {String/Object} comp The id of the Component or a {@link Ext.Component} instance
275      * @return {Ext.Component} The Component
276      */
277     sendToBack : function(comp) {
278         comp = this.get(comp);
279         Ext.Array.remove(this.zIndexStack, comp);
280         this.zIndexStack.unshift(comp);
281         this.assignZIndices();
282         return comp;
283     },
284
285     /**
286      * Hides all Components managed by this ZIndexManager.
287      */
288     hideAll : function() {
289         for (var id in this.list) {
290             if (this.list[id].isComponent && this.list[id].isVisible()) {
291                 this.list[id].hide();
292             }
293         }
294     },
295
296     /**
297      * @private
298      * Temporarily hides all currently visible managed Components. This is for when
299      * dragging a Window which may manage a set of floating descendants in its ZIndexManager;
300      * they should all be hidden just for the duration of the drag.
301      */
302     hide: function() {
303         var i = 0,
304             ln = this.zIndexStack.length,
305             comp;
306
307         this.tempHidden = [];
308         for (; i < ln; i++) {
309             comp = this.zIndexStack[i];
310             if (comp.isVisible()) {
311                 this.tempHidden.push(comp);
312                 comp.hide();
313             }
314         }
315     },
316
317     /**
318      * @private
319      * Restores temporarily hidden managed Components to visibility.
320      */
321     show: function() {
322         var i = 0,
323             ln = this.tempHidden.length,
324             comp,
325             x,
326             y;
327
328         for (; i < ln; i++) {
329             comp = this.tempHidden[i];
330             x = comp.x;
331             y = comp.y;
332             comp.show();
333             comp.setPosition(x, y);
334         }
335         delete this.tempHidden;
336     },
337
338     /**
339      * Gets the currently-active Component in this ZIndexManager.
340      * @return {Ext.Component} The active Component
341      */
342     getActive : function() {
343         return this.front;
344     },
345
346     /**
347      * Returns zero or more Components in this ZIndexManager using the custom search function passed to this method.
348      * The function should accept a single {@link Ext.Component} reference as its only argument and should
349      * return true if the Component matches the search criteria, otherwise it should return false.
350      * @param {Function} fn The search function
351      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to the Component being tested.
352      * that gets passed to the function if not specified)
353      * @return {Array} An array of zero or more matching windows
354      */
355     getBy : function(fn, scope) {
356         var r = [],
357             i = 0,
358             len = this.zIndexStack.length,
359             comp;
360
361         for (; i < len; i++) {
362             comp = this.zIndexStack[i];
363             if (fn.call(scope||comp, comp) !== false) {
364                 r.push(comp);
365             }
366         }
367         return r;
368     },
369
370     /**
371      * Executes the specified function once for every Component in this ZIndexManager, passing each
372      * Component as the only parameter. Returning false from the function will stop the iteration.
373      * @param {Function} fn The function to execute for each item
374      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to the current Component in the iteration.
375      */
376     each : function(fn, scope) {
377         var comp;
378         for (var id in this.list) {
379             comp = this.list[id];
380             if (comp.isComponent && fn.call(scope || comp, comp) === false) {
381                 return;
382             }
383         }
384     },
385
386     /**
387      * Executes the specified function once for every Component in this ZIndexManager, passing each
388      * Component as the only parameter. Returning false from the function will stop the iteration.
389      * The components are passed to the function starting at the bottom and proceeding to the top.
390      * @param {Function} fn The function to execute for each item
391      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function
392      * is executed. Defaults to the current Component in the iteration.
393      */
394     eachBottomUp: function (fn, scope) {
395         var comp,
396             stack = this.zIndexStack,
397             i, n;
398
399         for (i = 0, n = stack.length ; i < n; i++) {
400             comp = stack[i];
401             if (comp.isComponent && fn.call(scope || comp, comp) === false) {
402                 return;
403             }
404         }
405     },
406
407     /**
408      * Executes the specified function once for every Component in this ZIndexManager, passing each
409      * Component as the only parameter. Returning false from the function will stop the iteration.
410      * The components are passed to the function starting at the top and proceeding to the bottom.
411      * @param {Function} fn The function to execute for each item
412      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function
413      * is executed. Defaults to the current Component in the iteration.
414      */
415     eachTopDown: function (fn, scope) {
416         var comp,
417             stack = this.zIndexStack,
418             i;
419
420         for (i = stack.length ; i-- > 0; ) {
421             comp = stack[i];
422             if (comp.isComponent && fn.call(scope || comp, comp) === false) {
423                 return;
424             }
425         }
426     },
427
428     destroy: function() {
429         this.each(function(c) {
430             c.destroy();
431         });
432         delete this.zIndexStack;
433         delete this.list;
434         delete this.container;
435         delete this.targetEl;
436     }
437 }, function() {
438     /**
439      * @class Ext.WindowManager
440      * @extends Ext.ZIndexManager
441      * <p>The default global floating Component group that is available automatically.</p>
442      * <p>This manages instances of floating Components which were rendered programatically without
443      * being added to a {@link Ext.container.Container Container}, and for floating Components which were added into non-floating Containers.</p>
444      * <p><i>Floating</i> Containers create their own instance of ZIndexManager, and floating Components added at any depth below
445      * there are managed by that ZIndexManager.</p>
446      * @singleton
447      */
448     Ext.WindowManager = Ext.WindowMgr = new this();
449 });
450