Upgrade to ExtJS 4.0.2 - Released 06/09/2011
[extjs.git] / src / Layer.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.Layer
17  * @extends Ext.core.Element
18  * An extended {@link Ext.core.Element} object that supports a shadow and shim, constrain to viewport and
19  * automatic maintaining of shadow/shim positions.
20  * @cfg {Boolean} shim False to disable the iframe shim in browsers which need one (defaults to true)
21  * @cfg {String/Boolean} shadow True to automatically create an {@link Ext.Shadow}, or a string indicating the
22  * shadow's display {@link Ext.Shadow#mode}. False to disable the shadow. (defaults to false)
23  * @cfg {Object} dh DomHelper object config to create element with (defaults to {tag: 'div', cls: 'x-layer'}).
24  * @cfg {Boolean} constrain False to disable constrain to viewport (defaults to true)
25  * @cfg {String} cls CSS class to add to the element
26  * @cfg {Number} zindex Starting z-index (defaults to 11000)
27  * @cfg {Number} shadowOffset Number of pixels to offset the shadow (defaults to 4)
28  * @cfg {Boolean} useDisplay
29  * Defaults to use css offsets to hide the Layer. Specify <tt>true</tt>
30  * to use css style <tt>'display:none;'</tt> to hide the Layer.
31  * @cfg {String} visibilityCls The CSS class name to add in order to hide this Layer if this layer
32  * is configured with <code>{@link #hideMode}: 'asclass'</code>
33  * @cfg {String} hideMode
34  * A String which specifies how this Layer will be hidden.
35  * Values may be<div class="mdetail-params"><ul>
36  * <li><code>'display'</code> : The Component will be hidden using the <code>display: none</code> style.</li>
37  * <li><code>'visibility'</code> : The Component will be hidden using the <code>visibility: hidden</code> style.</li>
38  * <li><code>'offsets'</code> : The Component will be hidden by absolutely positioning it out of the visible area of the document. This
39  * is useful when a hidden Component must maintain measurable dimensions. Hiding using <code>display</code> results
40  * in a Component having zero dimensions.</li></ul></div>
41  */
42 Ext.define('Ext.Layer', {
43     uses: ['Ext.Shadow'],
44
45     // shims are shared among layer to keep from having 100 iframes
46     statics: {
47         shims: []
48     },
49
50     extend: 'Ext.core.Element',
51
52     /**
53      * Creates new Layer.
54      * @param {Object} config (optional) An object with config options.
55      * @param {String/HTMLElement} existingEl (optional) Uses an existing DOM element.
56      * If the element is not found it creates it.
57      */
58     constructor: function(config, existingEl) {
59         config = config || {};
60         var me = this,
61             dh = Ext.core.DomHelper,
62             cp = config.parentEl,
63             pel = cp ? Ext.getDom(cp) : document.body,
64         hm = config.hideMode;
65
66         if (existingEl) {
67             me.dom = Ext.getDom(existingEl);
68         }
69         if (!me.dom) {
70             me.dom = dh.append(pel, config.dh || {
71                 tag: 'div',
72                 cls: Ext.baseCSSPrefix + 'layer'
73             });
74         } else {
75             me.addCls(Ext.baseCSSPrefix + 'layer');
76             if (!me.dom.parentNode) {
77                 pel.appendChild(me.dom);
78             }
79         }
80
81         if (config.cls) {
82             me.addCls(config.cls);
83         }
84         me.constrain = config.constrain !== false;
85
86         // Allow Components to pass their hide mode down to the Layer if they are floating.
87         // Otherwise, allow useDisplay to override the default hiding method which is visibility.
88         // TODO: Have ExtJS's Element implement visibilityMode by using classes as in Mobile.
89         if (hm) {
90             me.setVisibilityMode(Ext.core.Element[hm.toUpperCase()]);
91             if (me.visibilityMode == Ext.core.Element.ASCLASS) {
92                 me.visibilityCls = config.visibilityCls;
93             }
94         } else if (config.useDisplay) {
95             me.setVisibilityMode(Ext.core.Element.DISPLAY);
96         } else {
97             me.setVisibilityMode(Ext.core.Element.VISIBILITY);
98         }
99
100         if (config.id) {
101             me.id = me.dom.id = config.id;
102         } else {
103             me.id = Ext.id(me.dom);
104         }
105         me.position('absolute');
106         if (config.shadow) {
107             me.shadowOffset = config.shadowOffset || 4;
108             me.shadow = Ext.create('Ext.Shadow', {
109                 offset: me.shadowOffset,
110                 mode: config.shadow
111             });
112             me.disableShadow();
113         } else {
114             me.shadowOffset = 0;
115         }
116         me.useShim = config.shim !== false && Ext.useShims;
117         if (config.hidden === true) {
118             me.hide();
119         } else {
120             this.show();
121         }
122     },
123
124     getZIndex: function() {
125         return parseInt((this.getShim() || this).getStyle('z-index'), 10);
126     },
127
128     getShim: function() {
129         var me = this,
130             shim, pn;
131
132         if (!me.useShim) {
133             return null;
134         }
135         if (!me.shim) {
136             shim = me.self.shims.shift();
137             if (!shim) {
138                 shim = me.createShim();
139                 shim.enableDisplayMode('block');
140                 shim.hide();
141             }
142             pn = me.dom.parentNode;
143             if (shim.dom.parentNode != pn) {
144                 pn.insertBefore(shim.dom, me.dom);
145             }
146             me.shim = shim;
147         }
148         return me.shim;
149     },
150
151     hideShim: function() {
152         if (this.shim) {
153             this.shim.setDisplayed(false);
154             this.self.shims.push(this.shim);
155             delete this.shim;
156         }
157     },
158
159     disableShadow: function() {
160         if (this.shadow) {
161             this.shadowDisabled = true;
162             this.shadow.hide();
163             this.lastShadowOffset = this.shadowOffset;
164             this.shadowOffset = 0;
165         }
166     },
167
168     enableShadow: function(show) {
169         if (this.shadow) {
170             this.shadowDisabled = false;
171             this.shadowOffset = this.lastShadowOffset;
172             delete this.lastShadowOffset;
173             if (show) {
174                 this.sync(true);
175             }
176         }
177     },
178
179     /**
180      * @private
181      * <p>Synchronize this Layer's associated elements, the shadow, and possibly the shim.</p>
182      * <p>This code can execute repeatedly in milliseconds,
183      * eg: dragging a Component configured liveDrag: true, or which has no ghost method
184      * so code size was sacrificed for efficiency (e.g. no getBox/setBox, no XY calls)</p>
185      * @param {Boolean} doShow Pass true to ensure that the shadow is shown.
186      */
187     sync: function(doShow) {
188         var me = this,
189             shadow = me.shadow,
190             shadowPos, shimStyle, shadowSize;
191
192         if (!this.updating && this.isVisible() && (shadow || this.useShim)) {
193             var shim = this.getShim(),
194                 l = this.getLeft(true),
195                 t = this.getTop(true),
196                 w = this.getWidth(),
197                 h = this.getHeight(),
198                 shimIndex;
199
200             if (shadow && !this.shadowDisabled) {
201                 if (doShow && !shadow.isVisible()) {
202                     shadow.show(this);
203                 } else {
204                     shadow.realign(l, t, w, h);
205                 }
206                 if (shim) {
207                     // TODO: Determine how the shims zIndex is above the layer zIndex at this point
208                     shimIndex = shim.getStyle('z-index');
209                     if (shimIndex > me.zindex) {
210                         me.shim.setStyle('z-index', me.zindex - 2);
211                     }
212                     shim.show();
213                     // fit the shim behind the shadow, so it is shimmed too
214                     if (shadow.isVisible()) {
215                         shadowPos = shadow.el.getXY();
216                         shimStyle = shim.dom.style;
217                         shadowSize = shadow.el.getSize();
218                         shimStyle.left = (shadowPos[0]) + 'px';
219                         shimStyle.top = (shadowPos[1]) + 'px';
220                         shimStyle.width = (shadowSize.width) + 'px';
221                         shimStyle.height = (shadowSize.height) + 'px';
222                     } else {
223                         shim.setSize(w, h);
224                         shim.setLeftTop(l, t);
225                     }
226                 }
227             } else if (shim) {
228                 // TODO: Determine how the shims zIndex is above the layer zIndex at this point
229                 shimIndex = shim.getStyle('z-index');
230                 if (shimIndex > me.zindex) {
231                     me.shim.setStyle('z-index', me.zindex - 2);
232                 }
233                 shim.show();
234                 shim.setSize(w, h);
235                 shim.setLeftTop(l, t);
236             }
237         }
238         return this;
239     },
240
241     remove: function() {
242         this.hideUnders();
243         this.callParent();
244     },
245
246     // private
247     beginUpdate: function() {
248         this.updating = true;
249     },
250
251     // private
252     endUpdate: function() {
253         this.updating = false;
254         this.sync(true);
255     },
256
257     // private
258     hideUnders: function() {
259         if (this.shadow) {
260             this.shadow.hide();
261         }
262         this.hideShim();
263     },
264
265     // private
266     constrainXY: function() {
267         if (this.constrain) {
268             var vw = Ext.core.Element.getViewWidth(),
269                 vh = Ext.core.Element.getViewHeight(),
270                 s = Ext.getDoc().getScroll(),
271                 xy = this.getXY(),
272                 x = xy[0],
273                 y = xy[1],
274                 so = this.shadowOffset,
275                 w = this.dom.offsetWidth + so,
276                 h = this.dom.offsetHeight + so,
277                 moved = false; // only move it if it needs it
278             // first validate right/bottom
279             if ((x + w) > vw + s.left) {
280                 x = vw - w - so;
281                 moved = true;
282             }
283             if ((y + h) > vh + s.top) {
284                 y = vh - h - so;
285                 moved = true;
286             }
287             // then make sure top/left isn't negative
288             if (x < s.left) {
289                 x = s.left;
290                 moved = true;
291             }
292             if (y < s.top) {
293                 y = s.top;
294                 moved = true;
295             }
296             if (moved) {
297                 Ext.Layer.superclass.setXY.call(this, [x, y]);
298                 this.sync();
299             }
300         }
301         return this;
302     },
303
304     getConstrainOffset: function() {
305         return this.shadowOffset;
306     },
307
308     // overridden Element method
309     setVisible: function(visible, animate, duration, callback, easing) {
310         var me = this,
311             cb;
312
313         // post operation processing
314         cb = function() {
315             if (visible) {
316                 me.sync(true);
317             }
318             if (callback) {
319                 callback();
320             }
321         };
322
323         // Hide shadow and shim if hiding
324         if (!visible) {
325             this.hideUnders(true);
326         }
327         this.callParent([visible, animate, duration, callback, easing]);
328         if (!animate) {
329             cb();
330         }
331         return this;
332     },
333
334     // private
335     beforeFx: function() {
336         this.beforeAction();
337         return this.callParent(arguments);
338     },
339
340     // private
341     afterFx: function() {
342         this.callParent(arguments);
343         this.sync(this.isVisible());
344     },
345
346     // private
347     beforeAction: function() {
348         if (!this.updating && this.shadow) {
349             this.shadow.hide();
350         }
351     },
352
353     // overridden Element method
354     setLeft: function(left) {
355         this.callParent(arguments);
356         return this.sync();
357     },
358
359     setTop: function(top) {
360         this.callParent(arguments);
361         return this.sync();
362     },
363
364     setLeftTop: function(left, top) {
365         this.callParent(arguments);
366         return this.sync();
367     },
368
369     setXY: function(xy, animate, duration, callback, easing) {
370
371         // Callback will restore shadow state and call the passed callback
372         callback = this.createCB(callback);
373
374         this.fixDisplay();
375         this.beforeAction();
376         this.callParent([xy, animate, duration, callback, easing]);
377         if (!animate) {
378             callback();
379         }
380         return this;
381     },
382
383     // private
384     createCB: function(callback) {
385         var me = this,
386             showShadow = me.shadow && me.shadow.isVisible();
387
388         return function() {
389             me.constrainXY();
390             me.sync(showShadow);
391             if (callback) {
392                 callback();
393             }
394         };
395     },
396
397     // overridden Element method
398     setX: function(x, animate, duration, callback, easing) {
399         this.setXY([x, this.getY()], animate, duration, callback, easing);
400         return this;
401     },
402
403     // overridden Element method
404     setY: function(y, animate, duration, callback, easing) {
405         this.setXY([this.getX(), y], animate, duration, callback, easing);
406         return this;
407     },
408
409     // overridden Element method
410     setSize: function(w, h, animate, duration, callback, easing) {
411         // Callback will restore shadow state and call the passed callback
412         callback = this.createCB(callback);
413
414         this.beforeAction();
415         this.callParent([w, h, animate, duration, callback, easing]);
416         if (!animate) {
417             callback();
418         }
419         return this;
420     },
421
422     // overridden Element method
423     setWidth: function(w, animate, duration, callback, easing) {
424         // Callback will restore shadow state and call the passed callback
425         callback = this.createCB(callback);
426
427         this.beforeAction();
428         this.callParent([w, animate, duration, callback, easing]);
429         if (!animate) {
430             callback();
431         }
432         return this;
433     },
434
435     // overridden Element method
436     setHeight: function(h, animate, duration, callback, easing) {
437         // Callback will restore shadow state and call the passed callback
438         callback = this.createCB(callback);
439
440         this.beforeAction();
441         this.callParent([h, animate, duration, callback, easing]);
442         if (!animate) {
443             callback();
444         }
445         return this;
446     },
447
448     // overridden Element method
449     setBounds: function(x, y, width, height, animate, duration, callback, easing) {
450         // Callback will restore shadow state and call the passed callback
451         callback = this.createCB(callback);
452
453         this.beforeAction();
454         if (!animate) {
455             Ext.Layer.superclass.setXY.call(this, [x, y]);
456             Ext.Layer.superclass.setSize.call(this, width, height);
457             callback();
458         } else {
459             this.callParent([x, y, width, height, animate, duration, callback, easing]);
460         }
461         return this;
462     },
463
464     /**
465      * <p>Sets the z-index of this layer and adjusts any shadow and shim z-indexes. The layer z-index is automatically
466      * incremented depending upon the presence of a shim or a shadow in so that it always shows above those two associated elements.</p>
467      * <p>Any shim, will be assigned the passed z-index. A shadow will be assigned the next highet z-index, and the Layer's
468      * element will receive the highest  z-index.
469      * @param {Number} zindex The new z-index to set
470      * @return {this} The Layer
471      */
472     setZIndex: function(zindex) {
473         this.zindex = zindex;
474         if (this.getShim()) {
475             this.shim.setStyle('z-index', zindex++);
476         }
477         if (this.shadow) {
478             this.shadow.setZIndex(zindex++);
479         }
480         this.setStyle('z-index', zindex);
481         return this;
482     }
483 });
484