Upgrade to ExtJS 3.3.1 - Released 11/30/2010
[extjs.git] / src / widgets / Layer.js
1 /*!
2  * Ext JS Library 3.3.1
3  * Copyright(c) 2006-2010 Sencha Inc.
4  * licensing@sencha.com
5  * http://www.sencha.com/license
6  */
7 /**
8  * @class Ext.Layer
9  * @extends Ext.Element
10  * An extended {@link Ext.Element} object that supports a shadow and shim, constrain to viewport and
11  * automatic maintaining of shadow/shim positions.
12  * @cfg {Boolean} shim False to disable the iframe shim in browsers which need one (defaults to true)
13  * @cfg {String/Boolean} shadow True to automatically create an {@link Ext.Shadow}, or a string indicating the
14  * shadow's display {@link Ext.Shadow#mode}. False to disable the shadow. (defaults to false)
15  * @cfg {Object} dh DomHelper object config to create element with (defaults to {tag: 'div', cls: 'x-layer'}).
16  * @cfg {Boolean} constrain False to disable constrain to viewport (defaults to true)
17  * @cfg {String} cls CSS class to add to the element
18  * @cfg {Number} zindex Starting z-index (defaults to 11000)
19  * @cfg {Number} shadowOffset Number of pixels to offset the shadow (defaults to 4)
20  * @cfg {Boolean} useDisplay
21  * Defaults to use css offsets to hide the Layer. Specify <tt>true</tt>
22  * to use css style <tt>'display:none;'</tt> to hide the Layer.
23  * @constructor
24  * @param {Object} config An object with config options.
25  * @param {String/HTMLElement} existingEl (optional) Uses an existing DOM element. If the element is not found it creates it.
26  */
27 (function(){
28 Ext.Layer = function(config, existingEl){
29     config = config || {};
30     var dh = Ext.DomHelper,
31         cp = config.parentEl, pel = cp ? Ext.getDom(cp) : document.body;
32         
33     if (existingEl) {
34         this.dom = Ext.getDom(existingEl);
35     }
36     if(!this.dom){
37         var o = config.dh || {tag: 'div', cls: 'x-layer'};
38         this.dom = dh.append(pel, o);
39     }
40     if(config.cls){
41         this.addClass(config.cls);
42     }
43     this.constrain = config.constrain !== false;
44     this.setVisibilityMode(Ext.Element.VISIBILITY);
45     if(config.id){
46         this.id = this.dom.id = config.id;
47     }else{
48         this.id = Ext.id(this.dom);
49     }
50     this.zindex = config.zindex || this.getZIndex();
51     this.position('absolute', this.zindex);
52     if(config.shadow){
53         this.shadowOffset = config.shadowOffset || 4;
54         this.shadow = new Ext.Shadow({
55             offset : this.shadowOffset,
56             mode : config.shadow
57         });
58     }else{
59         this.shadowOffset = 0;
60     }
61     this.useShim = config.shim !== false && Ext.useShims;
62     this.useDisplay = config.useDisplay;
63     this.hide();
64 };
65
66 var supr = Ext.Element.prototype;
67
68 // shims are shared among layer to keep from having 100 iframes
69 var shims = [];
70
71 Ext.extend(Ext.Layer, Ext.Element, {
72
73     getZIndex : function(){
74         return this.zindex || parseInt((this.getShim() || this).getStyle('z-index'), 10) || 11000;
75     },
76
77     getShim : function(){
78         if(!this.useShim){
79             return null;
80         }
81         if(this.shim){
82             return this.shim;
83         }
84         var shim = shims.shift();
85         if(!shim){
86             shim = this.createShim();
87             shim.enableDisplayMode('block');
88             shim.dom.style.display = 'none';
89             shim.dom.style.visibility = 'visible';
90         }
91         var pn = this.dom.parentNode;
92         if(shim.dom.parentNode != pn){
93             pn.insertBefore(shim.dom, this.dom);
94         }
95         shim.setStyle('z-index', this.getZIndex()-2);
96         this.shim = shim;
97         return shim;
98     },
99
100     hideShim : function(){
101         if(this.shim){
102             this.shim.setDisplayed(false);
103             shims.push(this.shim);
104             delete this.shim;
105         }
106     },
107
108     disableShadow : function(){
109         if(this.shadow){
110             this.shadowDisabled = true;
111             this.shadow.hide();
112             this.lastShadowOffset = this.shadowOffset;
113             this.shadowOffset = 0;
114         }
115     },
116
117     enableShadow : function(show){
118         if(this.shadow){
119             this.shadowDisabled = false;
120             this.shadowOffset = this.lastShadowOffset;
121             delete this.lastShadowOffset;
122             if(show){
123                 this.sync(true);
124             }
125         }
126     },
127
128     // private
129     // this code can execute repeatedly in milliseconds (i.e. during a drag) so
130     // code size was sacrificed for effeciency (e.g. no getBox/setBox, no XY calls)
131     sync : function(doShow){
132         var shadow = this.shadow;
133         if(!this.updating && this.isVisible() && (shadow || this.useShim)){
134             var shim = this.getShim(),
135                 w = this.getWidth(),
136                 h = this.getHeight(),
137                 l = this.getLeft(true),
138                 t = this.getTop(true);
139
140             if(shadow && !this.shadowDisabled){
141                 if(doShow && !shadow.isVisible()){
142                     shadow.show(this);
143                 }else{
144                     shadow.realign(l, t, w, h);
145                 }
146                 if(shim){
147                     if(doShow){
148                        shim.show();
149                     }
150                     // fit the shim behind the shadow, so it is shimmed too
151                     var shadowAdj = shadow.el.getXY(), shimStyle = shim.dom.style,
152                         shadowSize = shadow.el.getSize();
153                     shimStyle.left = (shadowAdj[0])+'px';
154                     shimStyle.top = (shadowAdj[1])+'px';
155                     shimStyle.width = (shadowSize.width)+'px';
156                     shimStyle.height = (shadowSize.height)+'px';
157                 }
158             }else if(shim){
159                 if(doShow){
160                    shim.show();
161                 }
162                 shim.setSize(w, h);
163                 shim.setLeftTop(l, t);
164             }
165         }
166     },
167
168     // private
169     destroy : function(){
170         this.hideShim();
171         if(this.shadow){
172             this.shadow.hide();
173         }
174         this.removeAllListeners();
175         Ext.removeNode(this.dom);
176         delete this.dom;
177     },
178
179     remove : function(){
180         this.destroy();
181     },
182
183     // private
184     beginUpdate : function(){
185         this.updating = true;
186     },
187
188     // private
189     endUpdate : function(){
190         this.updating = false;
191         this.sync(true);
192     },
193
194     // private
195     hideUnders : function(negOffset){
196         if(this.shadow){
197             this.shadow.hide();
198         }
199         this.hideShim();
200     },
201
202     // private
203     constrainXY : function(){
204         if(this.constrain){
205             var vw = Ext.lib.Dom.getViewWidth(),
206                 vh = Ext.lib.Dom.getViewHeight();
207             var s = Ext.getDoc().getScroll();
208
209             var xy = this.getXY();
210             var x = xy[0], y = xy[1];
211             var so = this.shadowOffset;
212             var w = this.dom.offsetWidth+so, h = this.dom.offsetHeight+so;
213             // only move it if it needs it
214             var moved = false;
215             // first validate right/bottom
216             if((x + w) > vw+s.left){
217                 x = vw - w - so;
218                 moved = true;
219             }
220             if((y + h) > vh+s.top){
221                 y = vh - h - so;
222                 moved = true;
223             }
224             // then make sure top/left isn't negative
225             if(x < s.left){
226                 x = s.left;
227                 moved = true;
228             }
229             if(y < s.top){
230                 y = s.top;
231                 moved = true;
232             }
233             if(moved){
234                 if(this.avoidY){
235                     var ay = this.avoidY;
236                     if(y <= ay && (y+h) >= ay){
237                         y = ay-h-5;
238                     }
239                 }
240                 xy = [x, y];
241                 this.storeXY(xy);
242                 supr.setXY.call(this, xy);
243                 this.sync();
244             }
245         }
246         return this;
247     },
248     
249     getConstrainOffset : function(){
250         return this.shadowOffset;    
251     },
252
253     isVisible : function(){
254         return this.visible;
255     },
256
257     // private
258     showAction : function(){
259         this.visible = true; // track visibility to prevent getStyle calls
260         if(this.useDisplay === true){
261             this.setDisplayed('');
262         }else if(this.lastXY){
263             supr.setXY.call(this, this.lastXY);
264         }else if(this.lastLT){
265             supr.setLeftTop.call(this, this.lastLT[0], this.lastLT[1]);
266         }
267     },
268
269     // private
270     hideAction : function(){
271         this.visible = false;
272         if(this.useDisplay === true){
273             this.setDisplayed(false);
274         }else{
275             this.setLeftTop(-10000,-10000);
276         }
277     },
278
279     // overridden Element method
280     setVisible : function(v, a, d, c, e){
281         if(v){
282             this.showAction();
283         }
284         if(a && v){
285             var cb = function(){
286                 this.sync(true);
287                 if(c){
288                     c();
289                 }
290             }.createDelegate(this);
291             supr.setVisible.call(this, true, true, d, cb, e);
292         }else{
293             if(!v){
294                 this.hideUnders(true);
295             }
296             var cb = c;
297             if(a){
298                 cb = function(){
299                     this.hideAction();
300                     if(c){
301                         c();
302                     }
303                 }.createDelegate(this);
304             }
305             supr.setVisible.call(this, v, a, d, cb, e);
306             if(v){
307                 this.sync(true);
308             }else if(!a){
309                 this.hideAction();
310             }
311         }
312         return this;
313     },
314
315     storeXY : function(xy){
316         delete this.lastLT;
317         this.lastXY = xy;
318     },
319
320     storeLeftTop : function(left, top){
321         delete this.lastXY;
322         this.lastLT = [left, top];
323     },
324
325     // private
326     beforeFx : function(){
327         this.beforeAction();
328         return Ext.Layer.superclass.beforeFx.apply(this, arguments);
329     },
330
331     // private
332     afterFx : function(){
333         Ext.Layer.superclass.afterFx.apply(this, arguments);
334         this.sync(this.isVisible());
335     },
336
337     // private
338     beforeAction : function(){
339         if(!this.updating && this.shadow){
340             this.shadow.hide();
341         }
342     },
343
344     // overridden Element method
345     setLeft : function(left){
346         this.storeLeftTop(left, this.getTop(true));
347         supr.setLeft.apply(this, arguments);
348         this.sync();
349         return this;
350     },
351
352     setTop : function(top){
353         this.storeLeftTop(this.getLeft(true), top);
354         supr.setTop.apply(this, arguments);
355         this.sync();
356         return this;
357     },
358
359     setLeftTop : function(left, top){
360         this.storeLeftTop(left, top);
361         supr.setLeftTop.apply(this, arguments);
362         this.sync();
363         return this;
364     },
365
366     setXY : function(xy, a, d, c, e){
367         this.fixDisplay();
368         this.beforeAction();
369         this.storeXY(xy);
370         var cb = this.createCB(c);
371         supr.setXY.call(this, xy, a, d, cb, e);
372         if(!a){
373             cb();
374         }
375         return this;
376     },
377
378     // private
379     createCB : function(c){
380         var el = this;
381         return function(){
382             el.constrainXY();
383             el.sync(true);
384             if(c){
385                 c();
386             }
387         };
388     },
389
390     // overridden Element method
391     setX : function(x, a, d, c, e){
392         this.setXY([x, this.getY()], a, d, c, e);
393         return this;
394     },
395
396     // overridden Element method
397     setY : function(y, a, d, c, e){
398         this.setXY([this.getX(), y], a, d, c, e);
399         return this;
400     },
401
402     // overridden Element method
403     setSize : function(w, h, a, d, c, e){
404         this.beforeAction();
405         var cb = this.createCB(c);
406         supr.setSize.call(this, w, h, a, d, cb, e);
407         if(!a){
408             cb();
409         }
410         return this;
411     },
412
413     // overridden Element method
414     setWidth : function(w, a, d, c, e){
415         this.beforeAction();
416         var cb = this.createCB(c);
417         supr.setWidth.call(this, w, a, d, cb, e);
418         if(!a){
419             cb();
420         }
421         return this;
422     },
423
424     // overridden Element method
425     setHeight : function(h, a, d, c, e){
426         this.beforeAction();
427         var cb = this.createCB(c);
428         supr.setHeight.call(this, h, a, d, cb, e);
429         if(!a){
430             cb();
431         }
432         return this;
433     },
434
435     // overridden Element method
436     setBounds : function(x, y, w, h, a, d, c, e){
437         this.beforeAction();
438         var cb = this.createCB(c);
439         if(!a){
440             this.storeXY([x, y]);
441             supr.setXY.call(this, [x, y]);
442             supr.setSize.call(this, w, h, a, d, cb, e);
443             cb();
444         }else{
445             supr.setBounds.call(this, x, y, w, h, a, d, cb, e);
446         }
447         return this;
448     },
449
450     /**
451      * Sets the z-index of this layer and adjusts any shadow and shim z-indexes. The layer z-index is automatically
452      * incremented by two more than the value passed in so that it always shows above any shadow or shim (the shadow
453      * element, if any, will be assigned z-index + 1, and the shim element, if any, will be assigned the unmodified z-index).
454      * @param {Number} zindex The new z-index to set
455      * @return {this} The Layer
456      */
457     setZIndex : function(zindex){
458         this.zindex = zindex;
459         this.setStyle('z-index', zindex + 2);
460         if(this.shadow){
461             this.shadow.setZIndex(zindex + 1);
462         }
463         if(this.shim){
464             this.shim.setStyle('z-index', zindex);
465         }
466         return this;
467     }
468 });
469 })();