Upgrade to ExtJS 4.0.2 - Released 06/09/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 {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. So 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.el.getStyle('zIndex') - 4);
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.el.getStyle('zIndex') - 4);
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(zIndex) {
155         if (!this.mask) {
156             this.mask = this.targetEl.createChild({
157                 cls: Ext.baseCSSPrefix + 'mask'
158             });
159             this.mask.setVisibilityMode(Ext.core.Element.DISPLAY);
160             this.mask.on('click', this._onMaskClick, this);
161         }
162         Ext.getBody().addCls(Ext.baseCSSPrefix + 'body-masked');
163         this.mask.setSize(this.targetEl.getViewSize(true));
164         this.mask.setStyle('zIndex', zIndex);
165         this.mask.show();
166     },
167
168     _hideModalMask: function() {
169         if (this.mask) {
170             Ext.getBody().removeCls(Ext.baseCSSPrefix + 'body-masked');
171             this.mask.hide();
172         }
173     },
174
175     _onMaskClick: function() {
176         if (this.front) {
177             this.front.focus();
178         }
179     },
180
181     _onContainerResize: function() {
182         if (this.mask && this.mask.isVisible()) {
183             this.mask.setSize(this.targetEl.getViewSize(true));
184         }
185     },
186
187     /**
188      * <p>Registers a floating {@link Ext.Component} with this ZIndexManager. This should not
189      * need to be called under normal circumstances. Floating Components (such as Windows, BoundLists and Menus) are automatically registered
190      * with a {@link Ext.Component#zIndexManager zIndexManager} at render time.</p>
191      * <p>Where this may be useful is moving Windows between two ZIndexManagers. For example,
192      * to bring the Ext.MessageBox dialog under the same manager as the Desktop's
193      * ZIndexManager in the desktop sample app:</p><code><pre>
194 MyDesktop.getDesktop().getManager().register(Ext.MessageBox);
195 </pre></code>
196      * @param {Component} comp The Component to register.
197      */
198     register : function(comp) {
199         if (comp.zIndexManager) {
200             comp.zIndexManager.unregister(comp);
201         }
202         comp.zIndexManager = this;
203
204         this.list[comp.id] = comp;
205         this.zIndexStack.push(comp);
206         comp.on('hide', this._activateLast, this);
207     },
208
209     /**
210      * <p>Unregisters a {@link Ext.Component} from this ZIndexManager. This should not
211      * need to be called. Components are automatically unregistered upon destruction.
212      * See {@link #register}.</p>
213      * @param {Component} comp The Component to unregister.
214      */
215     unregister : function(comp) {
216         delete comp.zIndexManager;
217         if (this.list && this.list[comp.id]) {
218             delete this.list[comp.id];
219             comp.un('hide', this._activateLast);
220             Ext.Array.remove(this.zIndexStack, comp);
221
222             // Destruction requires that the topmost visible floater be activated. Same as hiding.
223             this._activateLast(comp);
224         }
225     },
226
227     /**
228      * Gets a registered Component by id.
229      * @param {String/Object} id The id of the Component or a {@link Ext.Component} instance
230      * @return {Ext.Component}
231      */
232     get : function(id) {
233         return typeof id == "object" ? id : this.list[id];
234     },
235
236    /**
237      * Brings the specified Component to the front of any other active Components in this ZIndexManager.
238      * @param {String/Object} comp The id of the Component or a {@link Ext.Component} instance
239      * @return {Boolean} True if the dialog was brought to the front, else false
240      * if it was already in front
241      */
242     bringToFront : function(comp) {
243         comp = this.get(comp);
244         if (comp != this.front) {
245             Ext.Array.remove(this.zIndexStack, comp);
246             this.zIndexStack.push(comp);
247             this.assignZIndices();
248             return true;
249         }
250         if (comp.modal) {
251             Ext.getBody().addCls(Ext.baseCSSPrefix + 'body-masked');
252             this.mask.setSize(Ext.core.Element.getViewWidth(true), Ext.core.Element.getViewHeight(true));
253             this.mask.show();
254         }
255         return false;
256     },
257
258     /**
259      * Sends the specified Component to the back of other active Components in this ZIndexManager.
260      * @param {String/Object} comp The id of the Component or a {@link Ext.Component} instance
261      * @return {Ext.Component} The Component
262      */
263     sendToBack : function(comp) {
264         comp = this.get(comp);
265         Ext.Array.remove(this.zIndexStack, comp);
266         this.zIndexStack.unshift(comp);
267         this.assignZIndices();
268         return comp;
269     },
270
271     /**
272      * Hides all Components managed by this ZIndexManager.
273      */
274     hideAll : function() {
275         for (var id in this.list) {
276             if (this.list[id].isComponent && this.list[id].isVisible()) {
277                 this.list[id].hide();
278             }
279         }
280     },
281
282     /**
283      * @private
284      * Temporarily hides all currently visible managed Components. This is for when
285      * dragging a Window which may manage a set of floating descendants in its ZIndexManager;
286      * they should all be hidden just for the duration of the drag.
287      */
288     hide: function() {
289         var i = 0,
290             ln = this.zIndexStack.length,
291             comp;
292
293         this.tempHidden = [];
294         for (; i < ln; i++) {
295             comp = this.zIndexStack[i];
296             if (comp.isVisible()) {
297                 this.tempHidden.push(comp);
298                 comp.hide();
299             }
300         }
301     },
302
303     /**
304      * @private
305      * Restores temporarily hidden managed Components to visibility.
306      */
307     show: function() {
308         var i = 0,
309             ln = this.tempHidden.length,
310             comp,
311             x,
312             y;
313
314         for (; i < ln; i++) {
315             comp = this.tempHidden[i];
316             x = comp.x;
317             y = comp.y;
318             comp.show();
319             comp.setPosition(x, y);
320         }
321         delete this.tempHidden;
322     },
323
324     /**
325      * Gets the currently-active Component in this ZIndexManager.
326      * @return {Ext.Component} The active Component
327      */
328     getActive : function() {
329         return this.front;
330     },
331
332     /**
333      * Returns zero or more Components in this ZIndexManager using the custom search function passed to this method.
334      * The function should accept a single {@link Ext.Component} reference as its only argument and should
335      * return true if the Component matches the search criteria, otherwise it should return false.
336      * @param {Function} fn The search function
337      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to the Component being tested.
338      * that gets passed to the function if not specified)
339      * @return {Array} An array of zero or more matching windows
340      */
341     getBy : function(fn, scope) {
342         var r = [],
343             i = 0,
344             len = this.zIndexStack.length,
345             comp;
346
347         for (; i < len; i++) {
348             comp = this.zIndexStack[i];
349             if (fn.call(scope||comp, comp) !== false) {
350                 r.push(comp);
351             }
352         }
353         return r;
354     },
355
356     /**
357      * Executes the specified function once for every Component in this ZIndexManager, passing each
358      * Component as the only parameter. Returning false from the function will stop the iteration.
359      * @param {Function} fn The function to execute for each item
360      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to the current Component in the iteration.
361      */
362     each : function(fn, scope) {
363         var comp;
364         for (var id in this.list) {
365             comp = this.list[id];
366             if (comp.isComponent && fn.call(scope || comp, comp) === false) {
367                 return;
368             }
369         }
370     },
371
372     /**
373      * Executes the specified function once for every Component in this ZIndexManager, passing each
374      * Component as the only parameter. Returning false from the function will stop the iteration.
375      * The components are passed to the function starting at the bottom and proceeding to the top.
376      * @param {Function} fn The function to execute for each item
377      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function
378      * is executed. Defaults to the current Component in the iteration.
379      */
380     eachBottomUp: function (fn, scope) {
381         var comp,
382             stack = this.zIndexStack,
383             i, n;
384
385         for (i = 0, n = stack.length ; i < n; i++) {
386             comp = stack[i];
387             if (comp.isComponent && fn.call(scope || comp, comp) === false) {
388                 return;
389             }
390         }
391     },
392
393     /**
394      * Executes the specified function once for every Component in this ZIndexManager, passing each
395      * Component as the only parameter. Returning false from the function will stop the iteration.
396      * The components are passed to the function starting at the top and proceeding to the bottom.
397      * @param {Function} fn The function to execute for each item
398      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function
399      * is executed. Defaults to the current Component in the iteration.
400      */
401     eachTopDown: function (fn, scope) {
402         var comp,
403             stack = this.zIndexStack,
404             i;
405
406         for (i = stack.length ; i-- > 0; ) {
407             comp = stack[i];
408             if (comp.isComponent && fn.call(scope || comp, comp) === false) {
409                 return;
410             }
411         }
412     },
413
414     destroy: function() {
415         delete this.zIndexStack;
416         delete this.list;
417         delete this.container;
418         delete this.targetEl;
419     }
420 }, function() {
421     /**
422      * @class Ext.WindowManager
423      * @extends Ext.ZIndexManager
424      * <p>The default global floating Component group that is available automatically.</p>
425      * <p>This manages instances of floating Components which were rendered programatically without
426      * being added to a {@link Ext.container.Container Container}, and for floating Components which were added into non-floating Containers.</p>
427      * <p><i>Floating</i> Containers create their own instance of ZIndexManager, and floating Components added at any depth below
428      * there are managed by that ZIndexManager.</p>
429      * @singleton
430      */
431     Ext.WindowManager = Ext.WindowMgr = new this();
432 });
433