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