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