Upgrade to ExtJS 4.0.7 - Released 10/19/2011
[extjs.git] / src / util / Floating.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  * A mixin to add floating capability to a Component.
17  */
18 Ext.define('Ext.util.Floating', {
19
20     uses: ['Ext.Layer', 'Ext.window.Window'],
21
22     /**
23      * @cfg {Boolean} focusOnToFront
24      * Specifies whether the floated component should be automatically {@link Ext.Component#focus focused} when
25      * it is {@link #toFront brought to the front}.
26      */
27     focusOnToFront: true,
28
29     /**
30      * @cfg {String/Boolean} shadow
31      * Specifies whether the floating component should be given a shadow. Set to true to automatically create an {@link
32      * Ext.Shadow}, or a string indicating the shadow's display {@link Ext.Shadow#mode}. Set to false to disable the
33      * shadow.
34      */
35     shadow: 'sides',
36
37     constructor: function(config) {
38         var me = this;
39         
40         me.floating = true;
41         me.el = Ext.create('Ext.Layer', Ext.apply({}, config, {
42             hideMode: me.hideMode,
43             hidden: me.hidden,
44             shadow: Ext.isDefined(me.shadow) ? me.shadow : 'sides',
45             shadowOffset: me.shadowOffset,
46             constrain: false,
47             shim: me.shim === false ? false : undefined
48         }), me.el);
49     },
50
51     onFloatRender: function() {
52         var me = this;
53         me.zIndexParent = me.getZIndexParent();
54         me.setFloatParent(me.ownerCt);
55         delete me.ownerCt;
56
57         if (me.zIndexParent) {
58             me.zIndexParent.registerFloatingItem(me);
59         } else {
60             Ext.WindowManager.register(me);
61         }
62     },
63
64     setFloatParent: function(floatParent) {
65         var me = this;
66
67         // Remove listeners from previous floatParent
68         if (me.floatParent) {
69             me.mun(me.floatParent, {
70                 hide: me.onFloatParentHide,
71                 show: me.onFloatParentShow,
72                 scope: me
73             });
74         }
75
76         me.floatParent = floatParent;
77
78         // Floating Components as children of Containers must hide when their parent hides.
79         if (floatParent) {
80             me.mon(me.floatParent, {
81                 hide: me.onFloatParentHide,
82                 show: me.onFloatParentShow,
83                 scope: me
84             });
85         }
86
87         // If a floating Component is configured to be constrained, but has no configured
88         // constrainTo setting, set its constrainTo to be it's ownerCt before rendering.
89         if ((me.constrain || me.constrainHeader) && !me.constrainTo) {
90             me.constrainTo = floatParent ? floatParent.getTargetEl() : me.container;
91         }
92     },
93
94     onFloatParentHide: function() {
95         var me = this;
96         
97         if (me.hideOnParentHide !== false) {
98             me.showOnParentShow = me.isVisible();
99             me.hide();
100         }
101     },
102
103     onFloatParentShow: function() {
104         if (this.showOnParentShow) {
105             delete this.showOnParentShow;
106             this.show();
107         }
108     },
109
110     /**
111      * @private
112      * Finds the ancestor Container responsible for allocating zIndexes for the passed Component.
113      *
114      * That will be the outermost floating Container (a Container which has no ownerCt and has floating:true).
115      *
116      * If we have no ancestors, or we walk all the way up to the document body, there's no zIndexParent,
117      * and the global Ext.WindowManager will be used.
118      */
119     getZIndexParent: function() {
120         var p = this.ownerCt,
121             c;
122
123         if (p) {
124             while (p) {
125                 c = p;
126                 p = p.ownerCt;
127             }
128             if (c.floating) {
129                 return c;
130             }
131         }
132     },
133
134     // private
135     // z-index is managed by the zIndexManager and may be overwritten at any time.
136     // Returns the next z-index to be used.
137     // If this is a Container, then it will have rebased any managed floating Components,
138     // and so the next available z-index will be approximately 10000 above that.
139     setZIndex: function(index) {
140         var me = this;
141         me.el.setZIndex(index);
142
143         // Next item goes 10 above;
144         index += 10;
145
146         // When a Container with floating items has its z-index set, it rebases any floating items it is managing.
147         // The returned value is a round number approximately 10000 above the last z-index used.
148         if (me.floatingItems) {
149             index = Math.floor(me.floatingItems.setBase(index) / 100) * 100 + 10000;
150         }
151         return index;
152     },
153
154     /**
155      * Moves this floating Component into a constrain region.
156      *
157      * By default, this Component is constrained to be within the container it was added to, or the element it was
158      * rendered to.
159      *
160      * An alternative constraint may be passed.
161      * @param {String/HTMLElement/Ext.Element/Ext.util.Region} constrainTo (Optional) The Element or {@link Ext.util.Region Region} into which this Component is
162      * to be constrained. Defaults to the element into which this floating Component was rendered.
163      */
164     doConstrain: function(constrainTo) {
165         var me = this,
166             vector = me.getConstrainVector(constrainTo || me.el.getScopeParent()),
167             xy;
168
169         if (vector) {
170             xy = me.getPosition();
171             xy[0] += vector[0];
172             xy[1] += vector[1];
173             me.setPosition(xy);
174         }
175     },
176
177
178     /**
179      * Gets the x/y offsets to constrain this float
180      * @private
181      * @param {String/HTMLElement/Ext.Element/Ext.util.Region} constrainTo (Optional) The Element or {@link Ext.util.Region Region} into which this Component is to be constrained.
182      * @return {Number[]} The x/y constraints
183      */
184     getConstrainVector: function(constrainTo){
185         var me = this,
186             el;
187
188         if (me.constrain || me.constrainHeader) {
189             el = me.constrainHeader ? me.header.el : me.el;
190             constrainTo = constrainTo || (me.floatParent && me.floatParent.getTargetEl()) || me.container;
191             return el.getConstrainVector(constrainTo);
192         }
193     },
194
195     /**
196      * Aligns this floating Component to the specified element
197      *
198      * @param {Ext.Component/Ext.Element/HTMLElement/String} element
199      * The element or {@link Ext.Component} to align to. If passing a component, it must be a
200      * omponent instance. If a string id is passed, it will be used as an element id.
201      * @param {String} [position="tl-bl?"] The position to align to (see {@link
202      * Ext.Element#alignTo} for more details).
203      * @param {Number[]} [offsets] Offset the positioning by [x, y]
204      * @return {Ext.Component} this
205      */
206     alignTo: function(element, position, offsets) {
207         if (element.isComponent) {
208             element = element.getEl();
209         }
210         var xy = this.el.getAlignToXY(element, position, offsets);
211         this.setPagePosition(xy);
212         return this;
213     },
214
215     /**
216      * Brings this floating Component to the front of any other visible, floating Components managed by the same {@link
217      * Ext.ZIndexManager ZIndexManager}
218      *
219      * If this Component is modal, inserts the modal mask just below this Component in the z-index stack.
220      *
221      * @param {Boolean} [preventFocus=false] Specify `true` to prevent the Component from being focused.
222      * @return {Ext.Component} this
223      */
224     toFront: function(preventFocus) {
225         var me = this;
226
227         // Find the floating Component which provides the base for this Component's zIndexing.
228         // That must move to front to then be able to rebase its zIndex stack and move this to the front
229         if (me.zIndexParent) {
230             me.zIndexParent.toFront(true);
231         }
232         if (me.zIndexManager.bringToFront(me)) {
233             if (!Ext.isDefined(preventFocus)) {
234                 preventFocus = !me.focusOnToFront;
235             }
236             if (!preventFocus) {
237                 // Kick off a delayed focus request.
238                 // If another floating Component is toFronted before the delay expires
239                 // this will not receive focus.
240                 me.focus(false, true);
241             }
242         }
243         return me;
244     },
245
246     /**
247      * This method is called internally by {@link Ext.ZIndexManager} to signal that a floating Component has either been
248      * moved to the top of its zIndex stack, or pushed from the top of its zIndex stack.
249      *
250      * If a _Window_ is superceded by another Window, deactivating it hides its shadow.
251      *
252      * This method also fires the {@link Ext.Component#activate activate} or
253      * {@link Ext.Component#deactivate deactivate} event depending on which action occurred.
254      *
255      * @param {Boolean} [active=false] True to activate the Component, false to deactivate it.
256      * @param {Ext.Component} [newActive] The newly active Component which is taking over topmost zIndex position.
257      */
258     setActive: function(active, newActive) {
259         var me = this;
260         
261         if (active) {
262             if (me.el.shadow && !me.maximized) {
263                 me.el.enableShadow(true);
264             }
265             me.fireEvent('activate', me);
266         } else {
267             // Only the *Windows* in a zIndex stack share a shadow. All other types of floaters
268             // can keep their shadows all the time
269             if ((me instanceof Ext.window.Window) && (newActive instanceof Ext.window.Window)) {
270                 me.el.disableShadow();
271             }
272             me.fireEvent('deactivate', me);
273         }
274     },
275
276     /**
277      * Sends this Component to the back of (lower z-index than) any other visible windows
278      * @return {Ext.Component} this
279      */
280     toBack: function() {
281         this.zIndexManager.sendToBack(this);
282         return this;
283     },
284
285     /**
286      * Center this Component in its container.
287      * @return {Ext.Component} this
288      */
289     center: function() {
290         var me = this,
291             xy = me.el.getAlignToXY(me.container, 'c-c');
292         me.setPagePosition(xy);
293         return me;
294     },
295
296     // private
297     syncShadow : function(){
298         if (this.floating) {
299             this.el.sync(true);
300         }
301     },
302
303     // private
304     fitContainer: function() {
305         var parent = this.floatParent,
306             container = parent ? parent.getTargetEl() : this.container,
307             size = container.getViewSize(false);
308
309         this.setSize(size);
310     }
311 });