Upgrade to ExtJS 3.0.3 - Released 10/11/2009
[extjs.git] / src / widgets / layout / BorderLayout.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 /**
8  * @class Ext.layout.BorderLayout
9  * @extends Ext.layout.ContainerLayout
10  * <p>This is a multi-pane, application-oriented UI layout style that supports multiple
11  * nested panels, automatic {@link Ext.layout.BorderLayout.Region#split split} bars between
12  * {@link Ext.layout.BorderLayout.Region#BorderLayout.Region regions} and built-in
13  * {@link Ext.layout.BorderLayout.Region#collapsible expanding and collapsing} of regions.</p>
14  * <p>This class is intended to be extended or created via the <tt>layout:'border'</tt>
15  * {@link Ext.Container#layout} config, and should generally not need to be created directly
16  * via the new keyword.</p>
17  * <p>BorderLayout does not have any direct config options (other than inherited ones).
18  * All configuration options available for customizing the BorderLayout are at the
19  * {@link Ext.layout.BorderLayout.Region} and {@link Ext.layout.BorderLayout.SplitRegion}
20  * levels.</p>
21  * <p>Example usage:</p>
22  * <pre><code>
23 var myBorderPanel = new Ext.Panel({
24     {@link Ext.Component#renderTo renderTo}: document.body,
25     {@link Ext.BoxComponent#width width}: 700,
26     {@link Ext.BoxComponent#height height}: 500,
27     {@link Ext.Panel#title title}: 'Border Layout',
28     {@link Ext.Container#layout layout}: 'border',
29     {@link Ext.Container#items items}: [{
30         {@link Ext.Panel#title title}: 'South Region is resizable',
31         {@link Ext.layout.BorderLayout.Region#BorderLayout.Region region}: 'south',     // position for region
32         {@link Ext.BoxComponent#height height}: 100,
33         {@link Ext.layout.BorderLayout.Region#split split}: true,         // enable resizing
34         {@link Ext.SplitBar#minSize minSize}: 75,         // defaults to {@link Ext.layout.BorderLayout.Region#minHeight 50} 
35         {@link Ext.SplitBar#maxSize maxSize}: 150,
36         {@link Ext.layout.BorderLayout.Region#margins margins}: '0 5 5 5'
37     },{
38         // xtype: 'panel' implied by default
39         {@link Ext.Panel#title title}: 'West Region is collapsible',
40         {@link Ext.layout.BorderLayout.Region#BorderLayout.Region region}:'west',
41         {@link Ext.layout.BorderLayout.Region#margins margins}: '5 0 0 5',
42         {@link Ext.BoxComponent#width width}: 200,
43         {@link Ext.layout.BorderLayout.Region#collapsible collapsible}: true,   // make collapsible
44         {@link Ext.layout.BorderLayout.Region#cmargins cmargins}: '5 5 0 5', // adjust top margin when collapsed
45         {@link Ext.Component#id id}: 'west-region-container',
46         {@link Ext.Container#layout layout}: 'fit',
47         {@link Ext.Panel#unstyled unstyled}: true
48     },{
49         {@link Ext.Panel#title title}: 'Center Region',
50         {@link Ext.layout.BorderLayout.Region#BorderLayout.Region region}: 'center',     // center region is required, no width/height specified
51         {@link Ext.Component#xtype xtype}: 'container',
52         {@link Ext.Container#layout layout}: 'fit',
53         {@link Ext.layout.BorderLayout.Region#margins margins}: '5 5 0 0'
54     }]
55 });
56 </code></pre>
57  * <p><b><u>Notes</u></b>:</p><div class="mdetail-params"><ul>
58  * <li>Any container using the BorderLayout <b>must</b> have a child item with <tt>region:'center'</tt>.
59  * The child item in the center region will always be resized to fill the remaining space not used by
60  * the other regions in the layout.</li>
61  * <li>Any child items with a region of <tt>west</tt> or <tt>east</tt> must have <tt>width</tt> defined
62  * (an integer representing the number of pixels that the region should take up).</li>
63  * <li>Any child items with a region of <tt>north</tt> or <tt>south</tt> must have <tt>height</tt> defined.</li>
64  * <li>The regions of a BorderLayout are <b>fixed at render time</b> and thereafter, its child Components may not be removed or added</b>.  To add/remove
65  * Components within a BorderLayout, have them wrapped by an additional Container which is directly
66  * managed by the BorderLayout.  If the region is to be collapsible, the Container used directly
67  * by the BorderLayout manager should be a Panel.  In the following example a Container (an Ext.Panel)
68  * is added to the west region:
69  * <div style="margin-left:16px"><pre><code>
70 wrc = {@link Ext#getCmp Ext.getCmp}('west-region-container');
71 wrc.{@link Ext.Panel#removeAll removeAll}();
72 wrc.{@link Ext.Container#add add}({
73     title: 'Added Panel',
74     html: 'Some content'
75 });
76 wrc.{@link Ext.Container#doLayout doLayout}();
77  * </code></pre></div>
78  * </li>
79  * <li> To reference a {@link Ext.layout.BorderLayout.Region Region}:
80  * <div style="margin-left:16px"><pre><code>
81 wr = myBorderPanel.layout.west;
82  * </code></pre></div>
83  * </li>
84  * </ul></div>
85  */
86 Ext.layout.BorderLayout = Ext.extend(Ext.layout.ContainerLayout, {
87     // private
88     monitorResize:true,
89     // private
90     rendered : false,
91
92     // private
93     onLayout : function(ct, target){
94         var collapsed;
95         if(!this.rendered){
96             target.addClass('x-border-layout-ct');
97             var items = ct.items.items;
98             collapsed = [];
99             for(var i = 0, len = items.length; i < len; i++) {
100                 var c = items[i];
101                 var pos = c.region;
102                 if(c.collapsed){
103                     collapsed.push(c);
104                 }
105                 c.collapsed = false;
106                 if(!c.rendered){
107                     c.render(target, i);
108                     c.getDomPositionEl().addClass('x-border-panel');
109                 }
110                 this[pos] = pos != 'center' && c.split ?
111                     new Ext.layout.BorderLayout.SplitRegion(this, c.initialConfig, pos) :
112                     new Ext.layout.BorderLayout.Region(this, c.initialConfig, pos);
113                 this[pos].render(target, c);
114             }
115             this.rendered = true;
116         }
117
118         var size = target.getViewSize();
119         if(size.width < 20 || size.height < 20){ // display none?
120             if(collapsed){
121                 this.restoreCollapsed = collapsed;
122             }
123             return;
124         }else if(this.restoreCollapsed){
125             collapsed = this.restoreCollapsed;
126             delete this.restoreCollapsed;
127         }
128
129         var w = size.width, h = size.height;
130         var centerW = w, centerH = h, centerY = 0, centerX = 0;
131
132         var n = this.north, s = this.south, west = this.west, e = this.east, c = this.center;
133         if(!c && Ext.layout.BorderLayout.WARN !== false){
134             throw 'No center region defined in BorderLayout ' + ct.id;
135         }
136
137         if(n && n.isVisible()){
138             var b = n.getSize();
139             var m = n.getMargins();
140             b.width = w - (m.left+m.right);
141             b.x = m.left;
142             b.y = m.top;
143             centerY = b.height + b.y + m.bottom;
144             centerH -= centerY;
145             n.applyLayout(b);
146         }
147         if(s && s.isVisible()){
148             var b = s.getSize();
149             var m = s.getMargins();
150             b.width = w - (m.left+m.right);
151             b.x = m.left;
152             var totalHeight = (b.height + m.top + m.bottom);
153             b.y = h - totalHeight + m.top;
154             centerH -= totalHeight;
155             s.applyLayout(b);
156         }
157         if(west && west.isVisible()){
158             var b = west.getSize();
159             var m = west.getMargins();
160             b.height = centerH - (m.top+m.bottom);
161             b.x = m.left;
162             b.y = centerY + m.top;
163             var totalWidth = (b.width + m.left + m.right);
164             centerX += totalWidth;
165             centerW -= totalWidth;
166             west.applyLayout(b);
167         }
168         if(e && e.isVisible()){
169             var b = e.getSize();
170             var m = e.getMargins();
171             b.height = centerH - (m.top+m.bottom);
172             var totalWidth = (b.width + m.left + m.right);
173             b.x = w - totalWidth + m.left;
174             b.y = centerY + m.top;
175             centerW -= totalWidth;
176             e.applyLayout(b);
177         }
178         if(c){
179             var m = c.getMargins();
180             var centerBox = {
181                 x: centerX + m.left,
182                 y: centerY + m.top,
183                 width: centerW - (m.left+m.right),
184                 height: centerH - (m.top+m.bottom)
185             };
186             c.applyLayout(centerBox);
187         }
188         if(collapsed){
189             for(var i = 0, len = collapsed.length; i < len; i++){
190                 collapsed[i].collapse(false);
191             }
192         }
193         if(Ext.isIE && Ext.isStrict){ // workaround IE strict repainting issue
194             target.repaint();
195         }
196     },
197
198     destroy: function() {
199         var r = ['north', 'south', 'east', 'west'];
200         for (var i = 0; i < r.length; i++) {
201             var region = this[r[i]];
202             if(region){
203                 if(region.destroy){
204                     region.destroy();
205                 }else if (region.split){
206                     region.split.destroy(true);
207                 }
208             }
209         }
210         Ext.layout.BorderLayout.superclass.destroy.call(this);
211     }
212
213     /**
214      * @property activeItem
215      * @hide
216      */
217 });
218
219 /**
220  * @class Ext.layout.BorderLayout.Region
221  * <p>This is a region of a {@link Ext.layout.BorderLayout BorderLayout} that acts as a subcontainer
222  * within the layout.  Each region has its own {@link Ext.layout.ContainerLayout layout} that is
223  * independent of other regions and the containing BorderLayout, and can be any of the
224  * {@link Ext.layout.ContainerLayout valid Ext layout types}.</p>
225  * <p>Region size is managed automatically and cannot be changed by the user -- for
226  * {@link #split resizable regions}, see {@link Ext.layout.BorderLayout.SplitRegion}.</p>
227  * @constructor
228  * Create a new Region.
229  * @param {Layout} layout The {@link Ext.layout.BorderLayout BorderLayout} instance that is managing this Region.
230  * @param {Object} config The configuration options
231  * @param {String} position The region position.  Valid values are: <tt>north</tt>, <tt>south</tt>,
232  * <tt>east</tt>, <tt>west</tt> and <tt>center</tt>.  Every {@link Ext.layout.BorderLayout BorderLayout}
233  * <b>must have a center region</b> for the primary content -- all other regions are optional.
234  */
235 Ext.layout.BorderLayout.Region = function(layout, config, pos){
236     Ext.apply(this, config);
237     this.layout = layout;
238     this.position = pos;
239     this.state = {};
240     if(typeof this.margins == 'string'){
241         this.margins = this.layout.parseMargins(this.margins);
242     }
243     this.margins = Ext.applyIf(this.margins || {}, this.defaultMargins);
244     if(this.collapsible){
245         if(typeof this.cmargins == 'string'){
246             this.cmargins = this.layout.parseMargins(this.cmargins);
247         }
248         if(this.collapseMode == 'mini' && !this.cmargins){
249             this.cmargins = {left:0,top:0,right:0,bottom:0};
250         }else{
251             this.cmargins = Ext.applyIf(this.cmargins || {},
252                 pos == 'north' || pos == 'south' ? this.defaultNSCMargins : this.defaultEWCMargins);
253         }
254     }
255 };
256
257 Ext.layout.BorderLayout.Region.prototype = {
258     /**
259      * @cfg {Boolean} animFloat
260      * When a collapsed region's bar is clicked, the region's panel will be displayed as a floated
261      * panel that will close again once the user mouses out of that panel (or clicks out if
262      * <tt>{@link #autoHide} = false</tt>).  Setting <tt>{@link #animFloat} = false</tt> will
263      * prevent the open and close of these floated panels from being animated (defaults to <tt>true</tt>).
264      */
265     /**
266      * @cfg {Boolean} autoHide
267      * When a collapsed region's bar is clicked, the region's panel will be displayed as a floated
268      * panel.  If <tt>autoHide = true</tt>, the panel will automatically hide after the user mouses
269      * out of the panel.  If <tt>autoHide = false</tt>, the panel will continue to display until the
270      * user clicks outside of the panel (defaults to <tt>true</tt>).
271      */
272     /**
273      * @cfg {String} collapseMode
274      * <tt>collapseMode</tt> supports two configuration values:<div class="mdetail-params"><ul>
275      * <li><b><tt>undefined</tt></b> (default)<div class="sub-desc">By default, {@link #collapsible}
276      * regions are collapsed by clicking the expand/collapse tool button that renders into the region's
277      * title bar.</div></li>
278      * <li><b><tt>'mini'</tt></b><div class="sub-desc">Optionally, when <tt>collapseMode</tt> is set to
279      * <tt>'mini'</tt> the region's split bar will also display a small collapse button in the center of
280      * the bar. In <tt>'mini'</tt> mode the region will collapse to a thinner bar than in normal mode.
281      * </div></li>
282      * </ul></div></p>
283      * <p><b>Note</b>: if a collapsible region does not have a title bar, then set <tt>collapseMode =
284      * 'mini'</tt> and <tt>{@link #split} = true</tt> in order for the region to be {@link #collapsible}
285      * by the user as the expand/collapse tool button (that would go in the title bar) will not be rendered.</p>
286      * <p>See also <tt>{@link #cmargins}</tt>.</p>
287      */
288     /**
289      * @cfg {Object} margins
290      * An object containing margins to apply to the region when in the expanded state in the
291      * format:<pre><code>
292 {
293     top: (top margin),
294     right: (right margin),
295     bottom: (bottom margin),
296     left: (left margin)
297 }</code></pre>
298      * <p>May also be a string containing space-separated, numeric margin values. The order of the
299      * sides associated with each value matches the way CSS processes margin values:</p>
300      * <p><div class="mdetail-params"><ul>
301      * <li>If there is only one value, it applies to all sides.</li>
302      * <li>If there are two values, the top and bottom borders are set to the first value and the
303      * right and left are set to the second.</li>
304      * <li>If there are three values, the top is set to the first value, the left and right are set
305      * to the second, and the bottom is set to the third.</li>
306      * <li>If there are four values, they apply to the top, right, bottom, and left, respectively.</li>
307      * </ul></div></p>
308      * <p>Defaults to:</p><pre><code>
309      * {top:0, right:0, bottom:0, left:0}
310      * </code></pre>
311      */
312     /**
313      * @cfg {Object} cmargins
314      * An object containing margins to apply to the region when in the collapsed state in the
315      * format:<pre><code>
316 {
317     top: (top margin),
318     right: (right margin),
319     bottom: (bottom margin),
320     left: (left margin)
321 }</code></pre>
322      * <p>May also be a string containing space-separated, numeric margin values. The order of the
323      * sides associated with each value matches the way CSS processes margin values.</p>
324      * <p><ul>
325      * <li>If there is only one value, it applies to all sides.</li>
326      * <li>If there are two values, the top and bottom borders are set to the first value and the
327      * right and left are set to the second.</li>
328      * <li>If there are three values, the top is set to the first value, the left and right are set
329      * to the second, and the bottom is set to the third.</li>
330      * <li>If there are four values, they apply to the top, right, bottom, and left, respectively.</li>
331      * </ul></p>
332      */
333     /**
334      * @cfg {Boolean} collapsible
335      * <p><tt>true</tt> to allow the user to collapse this region (defaults to <tt>false</tt>).  If
336      * <tt>true</tt>, an expand/collapse tool button will automatically be rendered into the title
337      * bar of the region, otherwise the button will not be shown.</p>
338      * <p><b>Note</b>: that a title bar is required to display the collapse/expand toggle button -- if
339      * no <tt>title</tt> is specified for the region's panel, the region will only be collapsible if
340      * <tt>{@link #collapseMode} = 'mini'</tt> and <tt>{@link #split} = true</tt>.
341      */
342     collapsible : false,
343     /**
344      * @cfg {Boolean} split
345      * <p><tt>true</tt> to create a {@link Ext.layout.BorderLayout.SplitRegion SplitRegion} and 
346      * display a 5px wide {@link Ext.SplitBar} between this region and its neighbor, allowing the user to
347      * resize the regions dynamically.  Defaults to <tt>false</tt> creating a
348      * {@link Ext.layout.BorderLayout.Region Region}.</p><br>
349      * <p><b>Notes</b>:</p><div class="mdetail-params"><ul>
350      * <li>this configuration option is ignored if <tt>region='center'</tt></li> 
351      * <li>when <tt>split == true</tt>, it is common to specify a
352      * <tt>{@link Ext.SplitBar#minSize minSize}</tt> and <tt>{@link Ext.SplitBar#maxSize maxSize}</tt>
353      * for the {@link Ext.BoxComponent BoxComponent} representing the region. These are not native
354      * configs of {@link Ext.BoxComponent BoxComponent}, and are used only by this class.</li>
355      * <li>if <tt>{@link #collapseMode} = 'mini'</tt> requires <tt>split = true</tt> to reserve space
356      * for the collapse tool</tt></li> 
357      * </ul></div> 
358      */
359     split:false,
360     /**
361      * @cfg {Boolean} floatable
362      * <tt>true</tt> to allow clicking a collapsed region's bar to display the region's panel floated
363      * above the layout, <tt>false</tt> to force the user to fully expand a collapsed region by
364      * clicking the expand button to see it again (defaults to <tt>true</tt>).
365      */
366     floatable: true,
367     /**
368      * @cfg {Number} minWidth
369      * <p>The minimum allowable width in pixels for this region (defaults to <tt>50</tt>).
370      * <tt>maxWidth</tt> may also be specified.</p><br>
371      * <p><b>Note</b>: setting the <tt>{@link Ext.SplitBar#minSize minSize}</tt> / 
372      * <tt>{@link Ext.SplitBar#maxSize maxSize}</tt> supersedes any specified 
373      * <tt>minWidth</tt> / <tt>maxWidth</tt>.</p>
374      */
375     minWidth:50,
376     /**
377      * @cfg {Number} minHeight
378      * The minimum allowable height in pixels for this region (defaults to <tt>50</tt>)
379      * <tt>maxHeight</tt> may also be specified.</p><br>
380      * <p><b>Note</b>: setting the <tt>{@link Ext.SplitBar#minSize minSize}</tt> / 
381      * <tt>{@link Ext.SplitBar#maxSize maxSize}</tt> supersedes any specified 
382      * <tt>minHeight</tt> / <tt>maxHeight</tt>.</p>
383      */
384     minHeight:50,
385
386     // private
387     defaultMargins : {left:0,top:0,right:0,bottom:0},
388     // private
389     defaultNSCMargins : {left:5,top:5,right:5,bottom:5},
390     // private
391     defaultEWCMargins : {left:5,top:0,right:5,bottom:0},
392     floatingZIndex: 100,
393
394     /**
395      * True if this region is collapsed. Read-only.
396      * @type Boolean
397      * @property
398      */
399     isCollapsed : false,
400
401     /**
402      * This region's panel.  Read-only.
403      * @type Ext.Panel
404      * @property panel
405      */
406     /**
407      * This region's layout.  Read-only.
408      * @type Layout
409      * @property layout
410      */
411     /**
412      * This region's layout position (north, south, east, west or center).  Read-only.
413      * @type String
414      * @property position
415      */
416
417     // private
418     render : function(ct, p){
419         this.panel = p;
420         p.el.enableDisplayMode();
421         this.targetEl = ct;
422         this.el = p.el;
423
424         var gs = p.getState, ps = this.position;
425         p.getState = function(){
426             return Ext.apply(gs.call(p) || {}, this.state);
427         }.createDelegate(this);
428
429         if(ps != 'center'){
430             p.allowQueuedExpand = false;
431             p.on({
432                 beforecollapse: this.beforeCollapse,
433                 collapse: this.onCollapse,
434                 beforeexpand: this.beforeExpand,
435                 expand: this.onExpand,
436                 hide: this.onHide,
437                 show: this.onShow,
438                 scope: this
439             });
440             if(this.collapsible || this.floatable){
441                 p.collapseEl = 'el';
442                 p.slideAnchor = this.getSlideAnchor();
443             }
444             if(p.tools && p.tools.toggle){
445                 p.tools.toggle.addClass('x-tool-collapse-'+ps);
446                 p.tools.toggle.addClassOnOver('x-tool-collapse-'+ps+'-over');
447             }
448         }
449     },
450
451     // private
452     getCollapsedEl : function(){
453         if(!this.collapsedEl){
454             if(!this.toolTemplate){
455                 var tt = new Ext.Template(
456                      '<div class="x-tool x-tool-{id}">&#160;</div>'
457                 );
458                 tt.disableFormats = true;
459                 tt.compile();
460                 Ext.layout.BorderLayout.Region.prototype.toolTemplate = tt;
461             }
462             this.collapsedEl = this.targetEl.createChild({
463                 cls: "x-layout-collapsed x-layout-collapsed-"+this.position,
464                 id: this.panel.id + '-xcollapsed'
465             });
466             this.collapsedEl.enableDisplayMode('block');
467
468             if(this.collapseMode == 'mini'){
469                 this.collapsedEl.addClass('x-layout-cmini-'+this.position);
470                 this.miniCollapsedEl = this.collapsedEl.createChild({
471                     cls: "x-layout-mini x-layout-mini-"+this.position, html: "&#160;"
472                 });
473                 this.miniCollapsedEl.addClassOnOver('x-layout-mini-over');
474                 this.collapsedEl.addClassOnOver("x-layout-collapsed-over");
475                 this.collapsedEl.on('click', this.onExpandClick, this, {stopEvent:true});
476             }else {
477                 if(this.collapsible !== false && !this.hideCollapseTool) {
478                     var t = this.toolTemplate.append(
479                             this.collapsedEl.dom,
480                             {id:'expand-'+this.position}, true);
481                     t.addClassOnOver('x-tool-expand-'+this.position+'-over');
482                     t.on('click', this.onExpandClick, this, {stopEvent:true});
483                 }
484                 if(this.floatable !== false || this.titleCollapse){
485                    this.collapsedEl.addClassOnOver("x-layout-collapsed-over");
486                    this.collapsedEl.on("click", this[this.floatable ? 'collapseClick' : 'onExpandClick'], this);
487                 }
488             }
489         }
490         return this.collapsedEl;
491     },
492
493     // private
494     onExpandClick : function(e){
495         if(this.isSlid){
496             this.afterSlideIn();
497             this.panel.expand(false);
498         }else{
499             this.panel.expand();
500         }
501     },
502
503     // private
504     onCollapseClick : function(e){
505         this.panel.collapse();
506     },
507
508     // private
509     beforeCollapse : function(p, animate){
510         this.lastAnim = animate;
511         if(this.splitEl){
512             this.splitEl.hide();
513         }
514         this.getCollapsedEl().show();
515         this.panel.el.setStyle('z-index', 100);
516         this.isCollapsed = true;
517         this.layout.layout();
518     },
519
520     // private
521     onCollapse : function(animate){
522         this.panel.el.setStyle('z-index', 1);
523         if(this.lastAnim === false || this.panel.animCollapse === false){
524             this.getCollapsedEl().dom.style.visibility = 'visible';
525         }else{
526             this.getCollapsedEl().slideIn(this.panel.slideAnchor, {duration:.2});
527         }
528         this.state.collapsed = true;
529         this.panel.saveState();
530     },
531
532     // private
533     beforeExpand : function(animate){
534         var c = this.getCollapsedEl();
535         this.el.show();
536         if(this.position == 'east' || this.position == 'west'){
537             this.panel.setSize(undefined, c.getHeight());
538         }else{
539             this.panel.setSize(c.getWidth(), undefined);
540         }
541         c.hide();
542         c.dom.style.visibility = 'hidden';
543         this.panel.el.setStyle('z-index', this.floatingZIndex);
544     },
545
546     // private
547     onExpand : function(){
548         this.isCollapsed = false;
549         if(this.splitEl){
550             this.splitEl.show();
551         }
552         this.layout.layout();
553         this.panel.el.setStyle('z-index', 1);
554         this.state.collapsed = false;
555         this.panel.saveState();
556     },
557
558     // private
559     collapseClick : function(e){
560         if(this.isSlid){
561            e.stopPropagation();
562            this.slideIn();
563         }else{
564            e.stopPropagation();
565            this.slideOut();
566         }
567     },
568
569     // private
570     onHide : function(){
571         if(this.isCollapsed){
572             this.getCollapsedEl().hide();
573         }else if(this.splitEl){
574             this.splitEl.hide();
575         }
576     },
577
578     // private
579     onShow : function(){
580         if(this.isCollapsed){
581             this.getCollapsedEl().show();
582         }else if(this.splitEl){
583             this.splitEl.show();
584         }
585     },
586
587     /**
588      * True if this region is currently visible, else false.
589      * @return {Boolean}
590      */
591     isVisible : function(){
592         return !this.panel.hidden;
593     },
594
595     /**
596      * Returns the current margins for this region.  If the region is collapsed, the
597      * {@link #cmargins} (collapsed margins) value will be returned, otherwise the
598      * {@link #margins} value will be returned.
599      * @return {Object} An object containing the element's margins: <tt>{left: (left
600      * margin), top: (top margin), right: (right margin), bottom: (bottom margin)}</tt>
601      */
602     getMargins : function(){
603         return this.isCollapsed && this.cmargins ? this.cmargins : this.margins;
604     },
605
606     /**
607      * Returns the current size of this region.  If the region is collapsed, the size of the
608      * collapsedEl will be returned, otherwise the size of the region's panel will be returned.
609      * @return {Object} An object containing the element's size: <tt>{width: (element width),
610      * height: (element height)}</tt>
611      */
612     getSize : function(){
613         return this.isCollapsed ? this.getCollapsedEl().getSize() : this.panel.getSize();
614     },
615
616     /**
617      * Sets the specified panel as the container element for this region.
618      * @param {Ext.Panel} panel The new panel
619      */
620     setPanel : function(panel){
621         this.panel = panel;
622     },
623
624     /**
625      * Returns the minimum allowable width for this region.
626      * @return {Number} The minimum width
627      */
628     getMinWidth: function(){
629         return this.minWidth;
630     },
631
632     /**
633      * Returns the minimum allowable height for this region.
634      * @return {Number} The minimum height
635      */
636     getMinHeight: function(){
637         return this.minHeight;
638     },
639
640     // private
641     applyLayoutCollapsed : function(box){
642         var ce = this.getCollapsedEl();
643         ce.setLeftTop(box.x, box.y);
644         ce.setSize(box.width, box.height);
645     },
646
647     // private
648     applyLayout : function(box){
649         if(this.isCollapsed){
650             this.applyLayoutCollapsed(box);
651         }else{
652             this.panel.setPosition(box.x, box.y);
653             this.panel.setSize(box.width, box.height);
654         }
655     },
656
657     // private
658     beforeSlide: function(){
659         this.panel.beforeEffect();
660     },
661
662     // private
663     afterSlide : function(){
664         this.panel.afterEffect();
665     },
666
667     // private
668     initAutoHide : function(){
669         if(this.autoHide !== false){
670             if(!this.autoHideHd){
671                 var st = new Ext.util.DelayedTask(this.slideIn, this);
672                 this.autoHideHd = {
673                     "mouseout": function(e){
674                         if(!e.within(this.el, true)){
675                             st.delay(500);
676                         }
677                     },
678                     "mouseover" : function(e){
679                         st.cancel();
680                     },
681                     scope : this
682                 };
683             }
684             this.el.on(this.autoHideHd);
685         }
686     },
687
688     // private
689     clearAutoHide : function(){
690         if(this.autoHide !== false){
691             this.el.un("mouseout", this.autoHideHd.mouseout);
692             this.el.un("mouseover", this.autoHideHd.mouseover);
693         }
694     },
695
696     // private
697     clearMonitor : function(){
698         Ext.getDoc().un("click", this.slideInIf, this);
699     },
700
701     /**
702      * If this Region is {@link #floatable}, this method slides this Region into full visibility <i>over the top
703      * of the center Region</i> where it floats until either {@link #slideIn} is called, or other regions of the layout
704      * are clicked, or the mouse exits the Region.
705      */
706     slideOut : function(){
707         if(this.isSlid || this.el.hasActiveFx()){
708             return;
709         }
710         this.isSlid = true;
711         var ts = this.panel.tools;
712         if(ts && ts.toggle){
713             ts.toggle.hide();
714         }
715         this.el.show();
716         if(this.position == 'east' || this.position == 'west'){
717             this.panel.setSize(undefined, this.collapsedEl.getHeight());
718         }else{
719             this.panel.setSize(this.collapsedEl.getWidth(), undefined);
720         }
721         this.restoreLT = [this.el.dom.style.left, this.el.dom.style.top];
722         this.el.alignTo(this.collapsedEl, this.getCollapseAnchor());
723         this.el.setStyle("z-index", this.floatingZIndex+2);
724         this.panel.el.replaceClass('x-panel-collapsed', 'x-panel-floating');
725         if(this.animFloat !== false){
726             this.beforeSlide();
727             this.el.slideIn(this.getSlideAnchor(), {
728                 callback: function(){
729                     this.afterSlide();
730                     this.initAutoHide();
731                     Ext.getDoc().on("click", this.slideInIf, this);
732                 },
733                 scope: this,
734                 block: true
735             });
736         }else{
737             this.initAutoHide();
738              Ext.getDoc().on("click", this.slideInIf, this);
739         }
740     },
741
742     // private
743     afterSlideIn : function(){
744         this.clearAutoHide();
745         this.isSlid = false;
746         this.clearMonitor();
747         this.el.setStyle("z-index", "");
748         this.panel.el.replaceClass('x-panel-floating', 'x-panel-collapsed');
749         this.el.dom.style.left = this.restoreLT[0];
750         this.el.dom.style.top = this.restoreLT[1];
751
752         var ts = this.panel.tools;
753         if(ts && ts.toggle){
754             ts.toggle.show();
755         }
756     },
757
758     /**
759      * If this Region is {@link #floatable}, and this Region has been slid into floating visibility, then this method slides
760      * this region back into its collapsed state.
761      */
762     slideIn : function(cb){
763         if(!this.isSlid || this.el.hasActiveFx()){
764             Ext.callback(cb);
765             return;
766         }
767         this.isSlid = false;
768         if(this.animFloat !== false){
769             this.beforeSlide();
770             this.el.slideOut(this.getSlideAnchor(), {
771                 callback: function(){
772                     this.el.hide();
773                     this.afterSlide();
774                     this.afterSlideIn();
775                     Ext.callback(cb);
776                 },
777                 scope: this,
778                 block: true
779             });
780         }else{
781             this.el.hide();
782             this.afterSlideIn();
783         }
784     },
785
786     // private
787     slideInIf : function(e){
788         if(!e.within(this.el)){
789             this.slideIn();
790         }
791     },
792
793     // private
794     anchors : {
795         "west" : "left",
796         "east" : "right",
797         "north" : "top",
798         "south" : "bottom"
799     },
800
801     // private
802     sanchors : {
803         "west" : "l",
804         "east" : "r",
805         "north" : "t",
806         "south" : "b"
807     },
808
809     // private
810     canchors : {
811         "west" : "tl-tr",
812         "east" : "tr-tl",
813         "north" : "tl-bl",
814         "south" : "bl-tl"
815     },
816
817     // private
818     getAnchor : function(){
819         return this.anchors[this.position];
820     },
821
822     // private
823     getCollapseAnchor : function(){
824         return this.canchors[this.position];
825     },
826
827     // private
828     getSlideAnchor : function(){
829         return this.sanchors[this.position];
830     },
831
832     // private
833     getAlignAdj : function(){
834         var cm = this.cmargins;
835         switch(this.position){
836             case "west":
837                 return [0, 0];
838             break;
839             case "east":
840                 return [0, 0];
841             break;
842             case "north":
843                 return [0, 0];
844             break;
845             case "south":
846                 return [0, 0];
847             break;
848         }
849     },
850
851     // private
852     getExpandAdj : function(){
853         var c = this.collapsedEl, cm = this.cmargins;
854         switch(this.position){
855             case "west":
856                 return [-(cm.right+c.getWidth()+cm.left), 0];
857             break;
858             case "east":
859                 return [cm.right+c.getWidth()+cm.left, 0];
860             break;
861             case "north":
862                 return [0, -(cm.top+cm.bottom+c.getHeight())];
863             break;
864             case "south":
865                 return [0, cm.top+cm.bottom+c.getHeight()];
866             break;
867         }
868     }
869 };
870
871 /**
872  * @class Ext.layout.BorderLayout.SplitRegion
873  * @extends Ext.layout.BorderLayout.Region
874  * <p>This is a specialized type of {@link Ext.layout.BorderLayout.Region BorderLayout region} that
875  * has a built-in {@link Ext.SplitBar} for user resizing of regions.  The movement of the split bar
876  * is configurable to move either {@link #tickSize smooth or incrementally}.</p>
877  * @constructor
878  * Create a new SplitRegion.
879  * @param {Layout} layout The {@link Ext.layout.BorderLayout BorderLayout} instance that is managing this Region.
880  * @param {Object} config The configuration options
881  * @param {String} position The region position.  Valid values are: north, south, east, west and center.  Every
882  * BorderLayout must have a center region for the primary content -- all other regions are optional.
883  */
884 Ext.layout.BorderLayout.SplitRegion = function(layout, config, pos){
885     Ext.layout.BorderLayout.SplitRegion.superclass.constructor.call(this, layout, config, pos);
886     // prevent switch
887     this.applyLayout = this.applyFns[pos];
888 };
889
890 Ext.extend(Ext.layout.BorderLayout.SplitRegion, Ext.layout.BorderLayout.Region, {
891     /**
892      * @cfg {Number} tickSize
893      * The increment, in pixels by which to move this Region's {@link Ext.SplitBar SplitBar}.
894      * By default, the {@link Ext.SplitBar SplitBar} moves smoothly.
895      */
896     /**
897      * @cfg {String} splitTip
898      * The tooltip to display when the user hovers over a
899      * {@link Ext.layout.BorderLayout.Region#collapsible non-collapsible} region's split bar
900      * (defaults to <tt>"Drag to resize."</tt>).  Only applies if
901      * <tt>{@link #useSplitTips} = true</tt>.
902      */
903     splitTip : "Drag to resize.",
904     /**
905      * @cfg {String} collapsibleSplitTip
906      * The tooltip to display when the user hovers over a
907      * {@link Ext.layout.BorderLayout.Region#collapsible collapsible} region's split bar
908      * (defaults to "Drag to resize. Double click to hide."). Only applies if
909      * <tt>{@link #useSplitTips} = true</tt>.
910      */
911     collapsibleSplitTip : "Drag to resize. Double click to hide.",
912     /**
913      * @cfg {Boolean} useSplitTips
914      * <tt>true</tt> to display a tooltip when the user hovers over a region's split bar
915      * (defaults to <tt>false</tt>).  The tooltip text will be the value of either
916      * <tt>{@link #splitTip}</tt> or <tt>{@link #collapsibleSplitTip}</tt> as appropriate.
917      */
918     useSplitTips : false,
919
920     // private
921     splitSettings : {
922         north : {
923             orientation: Ext.SplitBar.VERTICAL,
924             placement: Ext.SplitBar.TOP,
925             maxFn : 'getVMaxSize',
926             minProp: 'minHeight',
927             maxProp: 'maxHeight'
928         },
929         south : {
930             orientation: Ext.SplitBar.VERTICAL,
931             placement: Ext.SplitBar.BOTTOM,
932             maxFn : 'getVMaxSize',
933             minProp: 'minHeight',
934             maxProp: 'maxHeight'
935         },
936         east : {
937             orientation: Ext.SplitBar.HORIZONTAL,
938             placement: Ext.SplitBar.RIGHT,
939             maxFn : 'getHMaxSize',
940             minProp: 'minWidth',
941             maxProp: 'maxWidth'
942         },
943         west : {
944             orientation: Ext.SplitBar.HORIZONTAL,
945             placement: Ext.SplitBar.LEFT,
946             maxFn : 'getHMaxSize',
947             minProp: 'minWidth',
948             maxProp: 'maxWidth'
949         }
950     },
951
952     // private
953     applyFns : {
954         west : function(box){
955             if(this.isCollapsed){
956                 return this.applyLayoutCollapsed(box);
957             }
958             var sd = this.splitEl.dom, s = sd.style;
959             this.panel.setPosition(box.x, box.y);
960             var sw = sd.offsetWidth;
961             s.left = (box.x+box.width-sw)+'px';
962             s.top = (box.y)+'px';
963             s.height = Math.max(0, box.height)+'px';
964             this.panel.setSize(box.width-sw, box.height);
965         },
966         east : function(box){
967             if(this.isCollapsed){
968                 return this.applyLayoutCollapsed(box);
969             }
970             var sd = this.splitEl.dom, s = sd.style;
971             var sw = sd.offsetWidth;
972             this.panel.setPosition(box.x+sw, box.y);
973             s.left = (box.x)+'px';
974             s.top = (box.y)+'px';
975             s.height = Math.max(0, box.height)+'px';
976             this.panel.setSize(box.width-sw, box.height);
977         },
978         north : function(box){
979             if(this.isCollapsed){
980                 return this.applyLayoutCollapsed(box);
981             }
982             var sd = this.splitEl.dom, s = sd.style;
983             var sh = sd.offsetHeight;
984             this.panel.setPosition(box.x, box.y);
985             s.left = (box.x)+'px';
986             s.top = (box.y+box.height-sh)+'px';
987             s.width = Math.max(0, box.width)+'px';
988             this.panel.setSize(box.width, box.height-sh);
989         },
990         south : function(box){
991             if(this.isCollapsed){
992                 return this.applyLayoutCollapsed(box);
993             }
994             var sd = this.splitEl.dom, s = sd.style;
995             var sh = sd.offsetHeight;
996             this.panel.setPosition(box.x, box.y+sh);
997             s.left = (box.x)+'px';
998             s.top = (box.y)+'px';
999             s.width = Math.max(0, box.width)+'px';
1000             this.panel.setSize(box.width, box.height-sh);
1001         }
1002     },
1003
1004     // private
1005     render : function(ct, p){
1006         Ext.layout.BorderLayout.SplitRegion.superclass.render.call(this, ct, p);
1007
1008         var ps = this.position;
1009
1010         this.splitEl = ct.createChild({
1011             cls: "x-layout-split x-layout-split-"+ps, html: "&#160;",
1012             id: this.panel.id + '-xsplit'
1013         });
1014
1015         if(this.collapseMode == 'mini'){
1016             this.miniSplitEl = this.splitEl.createChild({
1017                 cls: "x-layout-mini x-layout-mini-"+ps, html: "&#160;"
1018             });
1019             this.miniSplitEl.addClassOnOver('x-layout-mini-over');
1020             this.miniSplitEl.on('click', this.onCollapseClick, this, {stopEvent:true});
1021         }
1022
1023         var s = this.splitSettings[ps];
1024
1025         this.split = new Ext.SplitBar(this.splitEl.dom, p.el, s.orientation);
1026         this.split.tickSize = this.tickSize;
1027         this.split.placement = s.placement;
1028         this.split.getMaximumSize = this[s.maxFn].createDelegate(this);
1029         this.split.minSize = this.minSize || this[s.minProp];
1030         this.split.on("beforeapply", this.onSplitMove, this);
1031         this.split.useShim = this.useShim === true;
1032         this.maxSize = this.maxSize || this[s.maxProp];
1033
1034         if(p.hidden){
1035             this.splitEl.hide();
1036         }
1037
1038         if(this.useSplitTips){
1039             this.splitEl.dom.title = this.collapsible ? this.collapsibleSplitTip : this.splitTip;
1040         }
1041         if(this.collapsible){
1042             this.splitEl.on("dblclick", this.onCollapseClick,  this);
1043         }
1044     },
1045
1046     //docs inherit from superclass
1047     getSize : function(){
1048         if(this.isCollapsed){
1049             return this.collapsedEl.getSize();
1050         }
1051         var s = this.panel.getSize();
1052         if(this.position == 'north' || this.position == 'south'){
1053             s.height += this.splitEl.dom.offsetHeight;
1054         }else{
1055             s.width += this.splitEl.dom.offsetWidth;
1056         }
1057         return s;
1058     },
1059
1060     // private
1061     getHMaxSize : function(){
1062          var cmax = this.maxSize || 10000;
1063          var center = this.layout.center;
1064          return Math.min(cmax, (this.el.getWidth()+center.el.getWidth())-center.getMinWidth());
1065     },
1066
1067     // private
1068     getVMaxSize : function(){
1069         var cmax = this.maxSize || 10000;
1070         var center = this.layout.center;
1071         return Math.min(cmax, (this.el.getHeight()+center.el.getHeight())-center.getMinHeight());
1072     },
1073
1074     // private
1075     onSplitMove : function(split, newSize){
1076         var s = this.panel.getSize();
1077         this.lastSplitSize = newSize;
1078         if(this.position == 'north' || this.position == 'south'){
1079             this.panel.setSize(s.width, newSize);
1080             this.state.height = newSize;
1081         }else{
1082             this.panel.setSize(newSize, s.height);
1083             this.state.width = newSize;
1084         }
1085         this.layout.layout();
1086         this.panel.saveState();
1087         return false;
1088     },
1089
1090     /**
1091      * Returns a reference to the split bar in use by this region.
1092      * @return {Ext.SplitBar} The split bar
1093      */
1094     getSplitBar : function(){
1095         return this.split;
1096     },
1097
1098     // inherit docs
1099     destroy : function() {
1100         Ext.destroy(
1101             this.miniSplitEl,
1102             this.split,
1103             this.splitEl
1104         );
1105     }
1106 });
1107
1108 Ext.Container.LAYOUTS['border'] = Ext.layout.BorderLayout;