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