Upgrade to ExtJS 3.0.3 - Released 10/11/2009
[extjs.git] / src / widgets / SplitBar.js
1 /*!
2  * Ext JS Library 3.0.3
3  * Copyright(c) 2006-2009 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     proxy.unselectable();\r
290     var cls = 'x-splitbar-proxy';\r
291     proxy.addClass(cls + ' ' + (dir == Ext.SplitBar.HORIZONTAL ? cls +'-h' : cls + '-v'));\r
292     document.body.appendChild(proxy.dom);\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