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