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