Upgrade to ExtJS 3.1.1 - Released 02/08/2010
[extjs.git] / src / widgets / SplitBar.js
1 /*!
2  * Ext JS Library 3.1.1
3  * Copyright(c) 2006-2010 Ext JS, LLC
4  * licensing@extjs.com
5  * http://www.extjs.com/license
6  */
7 /**\r
8  * @class Ext.SplitBar\r
9  * @extends Ext.util.Observable\r
10  * Creates draggable splitter bar functionality from two elements (element to be dragged and element to be resized).\r
11  * <br><br>\r
12  * Usage:\r
13  * <pre><code>\r
14 var split = new Ext.SplitBar("elementToDrag", "elementToSize",\r
15                    Ext.SplitBar.HORIZONTAL, Ext.SplitBar.LEFT);\r
16 split.setAdapter(new Ext.SplitBar.AbsoluteLayoutAdapter("container"));\r
17 split.minSize = 100;\r
18 split.maxSize = 600;\r
19 split.animate = true;\r
20 split.on('moved', splitterMoved);\r
21 </code></pre>\r
22  * @constructor\r
23  * Create a new SplitBar\r
24  * @param {Mixed} dragElement The element to be dragged and act as the SplitBar.\r
25  * @param {Mixed} resizingElement The element to be resized based on where the SplitBar element is dragged\r
26  * @param {Number} orientation (optional) Either Ext.SplitBar.HORIZONTAL or Ext.SplitBar.VERTICAL. (Defaults to HORIZONTAL)\r
27  * @param {Number} placement (optional) Either Ext.SplitBar.LEFT or Ext.SplitBar.RIGHT for horizontal or\r
28                         Ext.SplitBar.TOP or Ext.SplitBar.BOTTOM for vertical. (By default, this is determined automatically by the initial\r
29                         position of the SplitBar).\r
30  */\r
31 Ext.SplitBar = function(dragElement, resizingElement, orientation, placement, existingProxy){\r
32 \r
33     /** @private */\r
34     this.el = Ext.get(dragElement, true);\r
35     this.el.dom.unselectable = "on";\r
36     /** @private */\r
37     this.resizingEl = Ext.get(resizingElement, true);\r
38 \r
39     /**\r
40      * @private\r
41      * The orientation of the split. Either Ext.SplitBar.HORIZONTAL or Ext.SplitBar.VERTICAL. (Defaults to HORIZONTAL)\r
42      * Note: If this is changed after creating the SplitBar, the placement property must be manually updated\r
43      * @type Number\r
44      */\r
45     this.orientation = orientation || Ext.SplitBar.HORIZONTAL;\r
46 \r
47     /**\r
48      * The increment, in pixels by which to move this SplitBar. When <i>undefined</i>, the SplitBar moves smoothly.\r
49      * @type Number\r
50      * @property tickSize\r
51      */\r
52     /**\r
53      * The minimum size of the resizing element. (Defaults to 0)\r
54      * @type Number\r
55      */\r
56     this.minSize = 0;\r
57 \r
58     /**\r
59      * The maximum size of the resizing element. (Defaults to 2000)\r
60      * @type Number\r
61      */\r
62     this.maxSize = 2000;\r
63 \r
64     /**\r
65      * Whether to animate the transition to the new size\r
66      * @type Boolean\r
67      */\r
68     this.animate = false;\r
69 \r
70     /**\r
71      * Whether to create a transparent shim that overlays the page when dragging, enables dragging across iframes.\r
72      * @type Boolean\r
73      */\r
74     this.useShim = false;\r
75 \r
76     /** @private */\r
77     this.shim = null;\r
78 \r
79     if(!existingProxy){\r
80         /** @private */\r
81         this.proxy = Ext.SplitBar.createProxy(this.orientation);\r
82     }else{\r
83         this.proxy = Ext.get(existingProxy).dom;\r
84     }\r
85     /** @private */\r
86     this.dd = new Ext.dd.DDProxy(this.el.dom.id, "XSplitBars", {dragElId : this.proxy.id});\r
87 \r
88     /** @private */\r
89     this.dd.b4StartDrag = this.onStartProxyDrag.createDelegate(this);\r
90 \r
91     /** @private */\r
92     this.dd.endDrag = this.onEndProxyDrag.createDelegate(this);\r
93 \r
94     /** @private */\r
95     this.dragSpecs = {};\r
96 \r
97     /**\r
98      * @private The adapter to use to positon and resize elements\r
99      */\r
100     this.adapter = new Ext.SplitBar.BasicLayoutAdapter();\r
101     this.adapter.init(this);\r
102 \r
103     if(this.orientation == Ext.SplitBar.HORIZONTAL){\r
104         /** @private */\r
105         this.placement = placement || (this.el.getX() > this.resizingEl.getX() ? Ext.SplitBar.LEFT : Ext.SplitBar.RIGHT);\r
106         this.el.addClass("x-splitbar-h");\r
107     }else{\r
108         /** @private */\r
109         this.placement = placement || (this.el.getY() > this.resizingEl.getY() ? Ext.SplitBar.TOP : Ext.SplitBar.BOTTOM);\r
110         this.el.addClass("x-splitbar-v");\r
111     }\r
112 \r
113     this.addEvents(\r
114         /**\r
115          * @event resize\r
116          * Fires when the splitter is moved (alias for {@link #moved})\r
117          * @param {Ext.SplitBar} this\r
118          * @param {Number} newSize the new width or height\r
119          */\r
120         "resize",\r
121         /**\r
122          * @event moved\r
123          * Fires when the splitter is moved\r
124          * @param {Ext.SplitBar} this\r
125          * @param {Number} newSize the new width or height\r
126          */\r
127         "moved",\r
128         /**\r
129          * @event beforeresize\r
130          * Fires before the splitter is dragged\r
131          * @param {Ext.SplitBar} this\r
132          */\r
133         "beforeresize",\r
134 \r
135         "beforeapply"\r
136     );\r
137 \r
138     Ext.SplitBar.superclass.constructor.call(this);\r
139 };\r
140 \r
141 Ext.extend(Ext.SplitBar, Ext.util.Observable, {\r
142     onStartProxyDrag : function(x, y){\r
143         this.fireEvent("beforeresize", this);\r
144         this.overlay =  Ext.DomHelper.append(document.body,  {cls: "x-drag-overlay", html: "&#160;"}, true);\r
145         this.overlay.unselectable();\r
146         this.overlay.setSize(Ext.lib.Dom.getViewWidth(true), Ext.lib.Dom.getViewHeight(true));\r
147         this.overlay.show();\r
148         Ext.get(this.proxy).setDisplayed("block");\r
149         var size = this.adapter.getElementSize(this);\r
150         this.activeMinSize = this.getMinimumSize();\r
151         this.activeMaxSize = this.getMaximumSize();\r
152         var c1 = size - this.activeMinSize;\r
153         var c2 = Math.max(this.activeMaxSize - size, 0);\r
154         if(this.orientation == Ext.SplitBar.HORIZONTAL){\r
155             this.dd.resetConstraints();\r
156             this.dd.setXConstraint(\r
157                 this.placement == Ext.SplitBar.LEFT ? c1 : c2,\r
158                 this.placement == Ext.SplitBar.LEFT ? c2 : c1,\r
159                 this.tickSize\r
160             );\r
161             this.dd.setYConstraint(0, 0);\r
162         }else{\r
163             this.dd.resetConstraints();\r
164             this.dd.setXConstraint(0, 0);\r
165             this.dd.setYConstraint(\r
166                 this.placement == Ext.SplitBar.TOP ? c1 : c2,\r
167                 this.placement == Ext.SplitBar.TOP ? c2 : c1,\r
168                 this.tickSize\r
169             );\r
170          }\r
171         this.dragSpecs.startSize = size;\r
172         this.dragSpecs.startPoint = [x, y];\r
173         Ext.dd.DDProxy.prototype.b4StartDrag.call(this.dd, x, y);\r
174     },\r
175 \r
176     /**\r
177      * @private Called after the drag operation by the DDProxy\r
178      */\r
179     onEndProxyDrag : function(e){\r
180         Ext.get(this.proxy).setDisplayed(false);\r
181         var endPoint = Ext.lib.Event.getXY(e);\r
182         if(this.overlay){\r
183             Ext.destroy(this.overlay);\r
184             delete this.overlay;\r
185         }\r
186         var newSize;\r
187         if(this.orientation == Ext.SplitBar.HORIZONTAL){\r
188             newSize = this.dragSpecs.startSize +\r
189                 (this.placement == Ext.SplitBar.LEFT ?\r
190                     endPoint[0] - this.dragSpecs.startPoint[0] :\r
191                     this.dragSpecs.startPoint[0] - endPoint[0]\r
192                 );\r
193         }else{\r
194             newSize = this.dragSpecs.startSize +\r
195                 (this.placement == Ext.SplitBar.TOP ?\r
196                     endPoint[1] - this.dragSpecs.startPoint[1] :\r
197                     this.dragSpecs.startPoint[1] - endPoint[1]\r
198                 );\r
199         }\r
200         newSize = Math.min(Math.max(newSize, this.activeMinSize), this.activeMaxSize);\r
201         if(newSize != this.dragSpecs.startSize){\r
202             if(this.fireEvent('beforeapply', this, newSize) !== false){\r
203                 this.adapter.setElementSize(this, newSize);\r
204                 this.fireEvent("moved", this, newSize);\r
205                 this.fireEvent("resize", this, newSize);\r
206             }\r
207         }\r
208     },\r
209 \r
210     /**\r
211      * Get the adapter this SplitBar uses\r
212      * @return The adapter object\r
213      */\r
214     getAdapter : function(){\r
215         return this.adapter;\r
216     },\r
217 \r
218     /**\r
219      * Set the adapter this SplitBar uses\r
220      * @param {Object} adapter A SplitBar adapter object\r
221      */\r
222     setAdapter : function(adapter){\r
223         this.adapter = adapter;\r
224         this.adapter.init(this);\r
225     },\r
226 \r
227     /**\r
228      * Gets the minimum size for the resizing element\r
229      * @return {Number} The minimum size\r
230      */\r
231     getMinimumSize : function(){\r
232         return this.minSize;\r
233     },\r
234 \r
235     /**\r
236      * Sets the minimum size for the resizing element\r
237      * @param {Number} minSize The minimum size\r
238      */\r
239     setMinimumSize : function(minSize){\r
240         this.minSize = minSize;\r
241     },\r
242 \r
243     /**\r
244      * Gets the maximum size for the resizing element\r
245      * @return {Number} The maximum size\r
246      */\r
247     getMaximumSize : function(){\r
248         return this.maxSize;\r
249     },\r
250 \r
251     /**\r
252      * Sets the maximum size for the resizing element\r
253      * @param {Number} maxSize The maximum size\r
254      */\r
255     setMaximumSize : function(maxSize){\r
256         this.maxSize = maxSize;\r
257     },\r
258 \r
259     /**\r
260      * Sets the initialize size for the resizing element\r
261      * @param {Number} size The initial size\r
262      */\r
263     setCurrentSize : function(size){\r
264         var oldAnimate = this.animate;\r
265         this.animate = false;\r
266         this.adapter.setElementSize(this, size);\r
267         this.animate = oldAnimate;\r
268     },\r
269 \r
270     /**\r
271      * Destroy this splitbar.\r
272      * @param {Boolean} removeEl True to remove the element\r
273      */\r
274     destroy : function(removeEl){\r
275         Ext.destroy(this.shim, Ext.get(this.proxy));\r
276         this.dd.unreg();\r
277         if(removeEl){\r
278             this.el.remove();\r
279         }\r
280         this.purgeListeners();\r
281     }\r
282 });\r
283 \r
284 /**\r
285  * @private static Create our own proxy element element. So it will be the same same size on all browsers, we won't use borders. Instead we use a background color.\r
286  */\r
287 Ext.SplitBar.createProxy = function(dir){\r
288     var proxy = new Ext.Element(document.createElement("div"));\r
289     document.body.appendChild(proxy.dom);\r
290     proxy.unselectable();\r
291     var cls = 'x-splitbar-proxy';\r
292     proxy.addClass(cls + ' ' + (dir == Ext.SplitBar.HORIZONTAL ? cls +'-h' : cls + '-v'));\r
293     return proxy.dom;\r
294 };\r
295 \r
296 /**\r
297  * @class Ext.SplitBar.BasicLayoutAdapter\r
298  * Default Adapter. It assumes the splitter and resizing element are not positioned\r
299  * elements and only gets/sets the width of the element. Generally used for table based layouts.\r
300  */\r
301 Ext.SplitBar.BasicLayoutAdapter = function(){\r
302 };\r
303 \r
304 Ext.SplitBar.BasicLayoutAdapter.prototype = {\r
305     // do nothing for now\r
306     init : function(s){\r
307 \r
308     },\r
309     /**\r
310      * Called before drag operations to get the current size of the resizing element.\r
311      * @param {Ext.SplitBar} s The SplitBar using this adapter\r
312      */\r
313      getElementSize : function(s){\r
314         if(s.orientation == Ext.SplitBar.HORIZONTAL){\r
315             return s.resizingEl.getWidth();\r
316         }else{\r
317             return s.resizingEl.getHeight();\r
318         }\r
319     },\r
320 \r
321     /**\r
322      * Called after drag operations to set the size of the resizing element.\r
323      * @param {Ext.SplitBar} s The SplitBar using this adapter\r
324      * @param {Number} newSize The new size to set\r
325      * @param {Function} onComplete A function to be invoked when resizing is complete\r
326      */\r
327     setElementSize : function(s, newSize, onComplete){\r
328         if(s.orientation == Ext.SplitBar.HORIZONTAL){\r
329             if(!s.animate){\r
330                 s.resizingEl.setWidth(newSize);\r
331                 if(onComplete){\r
332                     onComplete(s, newSize);\r
333                 }\r
334             }else{\r
335                 s.resizingEl.setWidth(newSize, true, .1, onComplete, 'easeOut');\r
336             }\r
337         }else{\r
338 \r
339             if(!s.animate){\r
340                 s.resizingEl.setHeight(newSize);\r
341                 if(onComplete){\r
342                     onComplete(s, newSize);\r
343                 }\r
344             }else{\r
345                 s.resizingEl.setHeight(newSize, true, .1, onComplete, 'easeOut');\r
346             }\r
347         }\r
348     }\r
349 };\r
350 \r
351 /**\r
352  *@class Ext.SplitBar.AbsoluteLayoutAdapter\r
353  * @extends Ext.SplitBar.BasicLayoutAdapter\r
354  * Adapter that  moves the splitter element to align with the resized sizing element.\r
355  * Used with an absolute positioned SplitBar.\r
356  * @param {Mixed} container The container that wraps around the absolute positioned content. If it's\r
357  * document.body, make sure you assign an id to the body element.\r
358  */\r
359 Ext.SplitBar.AbsoluteLayoutAdapter = function(container){\r
360     this.basic = new Ext.SplitBar.BasicLayoutAdapter();\r
361     this.container = Ext.get(container);\r
362 };\r
363 \r
364 Ext.SplitBar.AbsoluteLayoutAdapter.prototype = {\r
365     init : function(s){\r
366         this.basic.init(s);\r
367     },\r
368 \r
369     getElementSize : function(s){\r
370         return this.basic.getElementSize(s);\r
371     },\r
372 \r
373     setElementSize : function(s, newSize, onComplete){\r
374         this.basic.setElementSize(s, newSize, this.moveSplitter.createDelegate(this, [s]));\r
375     },\r
376 \r
377     moveSplitter : function(s){\r
378         var yes = Ext.SplitBar;\r
379         switch(s.placement){\r
380             case yes.LEFT:\r
381                 s.el.setX(s.resizingEl.getRight());\r
382                 break;\r
383             case yes.RIGHT:\r
384                 s.el.setStyle("right", (this.container.getWidth() - s.resizingEl.getLeft()) + "px");\r
385                 break;\r
386             case yes.TOP:\r
387                 s.el.setY(s.resizingEl.getBottom());\r
388                 break;\r
389             case yes.BOTTOM:\r
390                 s.el.setY(s.resizingEl.getTop() - s.el.getHeight());\r
391                 break;\r
392         }\r
393     }\r
394 };\r
395 \r
396 /**\r
397  * Orientation constant - Create a vertical SplitBar\r
398  * @static\r
399  * @type Number\r
400  */\r
401 Ext.SplitBar.VERTICAL = 1;\r
402 \r
403 /**\r
404  * Orientation constant - Create a horizontal SplitBar\r
405  * @static\r
406  * @type Number\r
407  */\r
408 Ext.SplitBar.HORIZONTAL = 2;\r
409 \r
410 /**\r
411  * Placement constant - The resizing element is to the left of the splitter element\r
412  * @static\r
413  * @type Number\r
414  */\r
415 Ext.SplitBar.LEFT = 1;\r
416 \r
417 /**\r
418  * Placement constant - The resizing element is to the right of the splitter element\r
419  * @static\r
420  * @type Number\r
421  */\r
422 Ext.SplitBar.RIGHT = 2;\r
423 \r
424 /**\r
425  * Placement constant - The resizing element is positioned above the splitter element\r
426  * @static\r
427  * @type Number\r
428  */\r
429 Ext.SplitBar.TOP = 3;\r
430 \r
431 /**\r
432  * Placement constant - The resizing element is positioned under splitter element\r
433  * @static\r
434  * @type Number\r
435  */\r
436 Ext.SplitBar.BOTTOM = 4;\r