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