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