Upgrade to ExtJS 4.0.7 - Released 10/19/2011
[extjs.git] / docs / source / Border.html
1 <!DOCTYPE html>
2 <html>
3 <head>
4   <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
5   <title>The source code</title>
6   <link href="../resources/prettify/prettify.css" type="text/css" rel="stylesheet" />
7   <script type="text/javascript" src="../resources/prettify/prettify.js"></script>
8   <style type="text/css">
9     .highlight { display: block; background-color: #ddd; }
10   </style>
11   <script type="text/javascript">
12     function highlight() {
13       document.getElementById(location.hash.replace(/#/, "")).className = "highlight";
14     }
15   </script>
16 </head>
17 <body onload="prettyPrint(); highlight();">
18   <pre class="prettyprint lang-js"><span id='Ext-layout-container-Border'>/**
19 </span> * This is a multi-pane, application-oriented UI layout style that supports multiple nested panels, automatic bars
20  * between regions and built-in {@link Ext.panel.Panel#collapsible expanding and collapsing} of regions.
21  *
22  * This class is intended to be extended or created via the `layout:'border'` {@link Ext.container.Container#layout}
23  * config, and should generally not need to be created directly via the new keyword.
24  *
25  *     @example
26  *     Ext.create('Ext.panel.Panel', {
27  *         width: 500,
28  *         height: 400,
29  *         title: 'Border Layout',
30  *         layout: 'border',
31  *         items: [{
32  *             title: 'South Region is resizable',
33  *             region: 'south',     // position for region
34  *             xtype: 'panel',
35  *             height: 100,
36  *             split: true,         // enable resizing
37  *             margins: '0 5 5 5'
38  *         },{
39  *             // xtype: 'panel' implied by default
40  *             title: 'West Region is collapsible',
41  *             region:'west',
42  *             xtype: 'panel',
43  *             margins: '5 0 0 5',
44  *             width: 200,
45  *             collapsible: true,   // make collapsible
46  *             id: 'west-region-container',
47  *             layout: 'fit'
48  *         },{
49  *             title: 'Center Region',
50  *             region: 'center',     // center region is required, no width/height specified
51  *             xtype: 'panel',
52  *             layout: 'fit',
53  *             margins: '5 5 0 0'
54  *         }],
55  *         renderTo: Ext.getBody()
56  *     });
57  *
58  * # Notes
59  *
60  * - Any Container using the Border layout **must** have a child item with `region:'center'`.
61  *   The child item in the center region will always be resized to fill the remaining space
62  *   not used by the other regions in the layout.
63  *
64  * - Any child items with a region of `west` or `east` may be configured with either an initial
65  *   `width`, or a {@link Ext.layout.container.Box#flex} value, or an initial percentage width
66  *   **string** (Which is simply divided by 100 and used as a flex value).
67  *   The 'center' region has a flex value of `1`.
68  *
69  * - Any child items with a region of `north` or `south` may be configured with either an initial
70  *   `height`, or a {@link Ext.layout.container.Box#flex} value, or an initial percentage height
71  *   **string** (Which is simply divided by 100 and used as a flex value).
72  *   The 'center' region has a flex value of `1`.
73  *
74  * - The regions of a BorderLayout are **fixed at render time** and thereafter, its child
75  *   Components may not be removed or added**. To add/remove Components within a BorderLayout,
76  *   have them wrapped by an additional Container which is directly managed by the BorderLayout.
77  *   If the region is to be collapsible, the Container used directly by the BorderLayout manager
78  *   should be a Panel. In the following example a Container (an Ext.panel.Panel) is added to
79  *   the west region:
80  *
81  *       wrc = {@link Ext#getCmp Ext.getCmp}('west-region-container');
82  *       wrc.{@link Ext.container.Container#removeAll removeAll}();
83  *       wrc.{@link Ext.container.Container#add add}({
84  *           title: 'Added Panel',
85  *           html: 'Some content'
86  *       });
87  *
88  * - **There is no BorderLayout.Region class in ExtJS 4.0+**
89  */
90 Ext.define('Ext.layout.container.Border', {
91
92     alias: ['layout.border'],
93     extend: 'Ext.layout.container.Container',
94     requires: ['Ext.resizer.Splitter', 'Ext.container.Container', 'Ext.fx.Anim'],
95     alternateClassName: 'Ext.layout.BorderLayout',
96
97     targetCls: Ext.baseCSSPrefix + 'border-layout-ct',
98
99     itemCls: Ext.baseCSSPrefix + 'border-item',
100
101     bindToOwnerCtContainer: true,
102
103     percentageRe: /(\d+)%/,
104
105     slideDirection: {
106         north: 't',
107         south: 'b',
108         west: 'l',
109         east: 'r'
110     },
111
112     constructor: function(config) {
113         this.initialConfig = config;
114         this.callParent(arguments);
115     },
116
117     onLayout: function() {
118         var me = this;
119         if (!me.borderLayoutInitialized) {
120             me.initializeBorderLayout();
121         }
122
123         // Delegate this operation to the shadow &quot;V&quot; or &quot;H&quot; box layout, and then down to any embedded layout.
124         me.fixHeightConstraints();
125         me.shadowLayout.onLayout();
126         if (me.embeddedContainer) {
127             me.embeddedContainer.layout.onLayout();
128         }
129
130         // If the panel was originally configured with collapsed: true, it will have
131         // been initialized with a &quot;borderCollapse&quot; flag: Collapse it now before the first layout.
132         if (!me.initialCollapsedComplete) {
133             Ext.iterate(me.regions, function(name, region){
134                 if (region.borderCollapse) {
135                     me.onBeforeRegionCollapse(region, region.collapseDirection, false, 0);
136                 }
137             });
138             me.initialCollapsedComplete = true;
139         }
140     },
141
142     isValidParent : function(item, target, position) {
143         if (!this.borderLayoutInitialized) {
144             this.initializeBorderLayout();
145         }
146
147         // Delegate this operation to the shadow &quot;V&quot; or &quot;H&quot; box layout.
148         return this.shadowLayout.isValidParent(item, target, position);
149     },
150
151     beforeLayout: function() {
152         if (!this.borderLayoutInitialized) {
153             this.initializeBorderLayout();
154         }
155
156         // Delegate this operation to the shadow &quot;V&quot; or &quot;H&quot; box layout.
157         this.shadowLayout.beforeLayout();
158
159         // note: don't call base because that does a renderItems again
160     },
161
162     renderItems: function(items, target) {
163         //&lt;debug&gt;
164         Ext.Error.raise('This should not be called');
165         //&lt;/debug&gt;
166     },
167
168     renderItem: function(item) {
169         //&lt;debug&gt;
170         Ext.Error.raise('This should not be called');
171         //&lt;/debug&gt;
172     },
173
174     renderChildren: function() {
175         if (!this.borderLayoutInitialized) {
176             this.initializeBorderLayout();
177         }
178
179         this.shadowLayout.renderChildren();
180     },
181
182     /*
183      * Gathers items for a layout operation. Injected into child Box layouts through configuration.
184      * We must not include child items which are floated over the layout (are primed with a slide out animation)
185      */
186     getVisibleItems: function() {
187         return Ext.ComponentQuery.query(':not([slideOutAnim])', this.callParent(arguments));
188     },
189
190     initializeBorderLayout: function() {
191         var me = this,
192             i = 0,
193             items = me.getLayoutItems(),
194             ln = items.length,
195             regions = (me.regions = {}),
196             vBoxItems = [],
197             hBoxItems = [],
198             horizontalFlex = 0,
199             verticalFlex = 0,
200             comp, percentage;
201
202         // Map of Splitters for each region
203         me.splitters = {};
204
205         // Map of regions
206         for (; i &lt; ln; i++) {
207             comp = items[i];
208             regions[comp.region] = comp;
209
210             // Intercept collapsing to implement showing an alternate Component as a collapsed placeholder
211             if (comp.region != 'center' &amp;&amp; comp.collapsible &amp;&amp; comp.collapseMode != 'header') {
212
213                 // This layout intercepts any initial collapsed state. Panel must not do this itself.
214                 comp.borderCollapse = comp.collapsed;
215                 comp.collapsed = false;
216
217                 comp.on({
218                     beforecollapse: me.onBeforeRegionCollapse,
219                     beforeexpand: me.onBeforeRegionExpand,
220                     destroy: me.onRegionDestroy,
221                     scope: me
222                 });
223                 me.setupState(comp);
224             }
225         }
226         //&lt;debug&gt;
227         if (!regions.center) {
228             Ext.Error.raise(&quot;You must specify a center region when defining a BorderLayout.&quot;);
229         }
230         //&lt;/debug&gt;
231         comp = regions.center;
232         if (!comp.flex) {
233             comp.flex = 1;
234         }
235         delete comp.width;
236         comp.maintainFlex = true;
237
238         // Begin the VBox and HBox item list.
239         comp = regions.west;
240         if (comp) {
241             comp.collapseDirection = Ext.Component.DIRECTION_LEFT;
242             hBoxItems.push(comp);
243             if (comp.split) {
244                 hBoxItems.push(me.splitters.west = me.createSplitter(comp));
245             }
246             percentage = Ext.isString(comp.width) &amp;&amp; comp.width.match(me.percentageRe);
247             if (percentage) {
248                 horizontalFlex += (comp.flex = parseInt(percentage[1], 10) / 100);
249                 delete comp.width;
250             }
251         }
252         comp = regions.north;
253         if (comp) {
254             comp.collapseDirection = Ext.Component.DIRECTION_TOP;
255             vBoxItems.push(comp);
256             if (comp.split) {
257                 vBoxItems.push(me.splitters.north = me.createSplitter(comp));
258             }
259             percentage = Ext.isString(comp.height) &amp;&amp; comp.height.match(me.percentageRe);
260             if (percentage) {
261                 verticalFlex += (comp.flex = parseInt(percentage[1], 10) / 100);
262                 delete comp.height;
263             }
264         }
265
266         // Decide into which Collection the center region goes.
267         if (regions.north || regions.south) {
268             if (regions.east || regions.west) {
269
270                 // Create the embedded center. Mark it with the region: 'center' property so that it can be identified as the center.
271                 vBoxItems.push(me.embeddedContainer = Ext.create('Ext.container.Container', {
272                     xtype: 'container',
273                     region: 'center',
274                     id: me.owner.id + '-embedded-center',
275                     cls: Ext.baseCSSPrefix + 'border-item',
276                     flex: regions.center.flex,
277                     maintainFlex: true,
278                     layout: {
279                         type: 'hbox',
280                         align: 'stretch',
281                         getVisibleItems: me.getVisibleItems
282                     }
283                 }));
284                 hBoxItems.push(regions.center);
285             }
286             // No east or west: the original center goes straight into the vbox
287             else {
288                 vBoxItems.push(regions.center);
289             }
290         }
291         // If we have no north or south, then the center is part of the HBox items
292         else {
293             hBoxItems.push(regions.center);
294         }
295
296         // Finish off the VBox and HBox item list.
297         comp = regions.south;
298         if (comp) {
299             comp.collapseDirection = Ext.Component.DIRECTION_BOTTOM;
300             if (comp.split) {
301                 vBoxItems.push(me.splitters.south = me.createSplitter(comp));
302             }
303             percentage = Ext.isString(comp.height) &amp;&amp; comp.height.match(me.percentageRe);
304             if (percentage) {
305                 verticalFlex += (comp.flex = parseInt(percentage[1], 10) / 100);
306                 delete comp.height;
307             }
308             vBoxItems.push(comp);
309         }
310         comp = regions.east;
311         if (comp) {
312             comp.collapseDirection = Ext.Component.DIRECTION_RIGHT;
313             if (comp.split) {
314                 hBoxItems.push(me.splitters.east = me.createSplitter(comp));
315             }
316             percentage = Ext.isString(comp.width) &amp;&amp; comp.width.match(me.percentageRe);
317             if (percentage) {
318                 horizontalFlex += (comp.flex = parseInt(percentage[1], 10) / 100);
319                 delete comp.width;
320             }
321             hBoxItems.push(comp);
322         }
323
324         // Create the injected &quot;items&quot; collections for the Containers.
325         // If we have north or south, then the shadow Container will be a VBox.
326         // If there are also east or west regions, its center will be a shadow HBox.
327         // If there are *only* east or west regions, then the shadow layout will be an HBox (or Fit).
328         if (regions.north || regions.south) {
329
330             me.shadowContainer = Ext.create('Ext.container.Container', {
331                 ownerCt: me.owner,
332                 el: me.getTarget(),
333                 layout: Ext.applyIf({
334                     type: 'vbox',
335                     align: 'stretch',
336                     getVisibleItems: me.getVisibleItems
337                 }, me.initialConfig)
338             });
339             me.createItems(me.shadowContainer, vBoxItems);
340
341             // Allow the Splitters to orientate themselves
342             if (me.splitters.north) {
343                 me.splitters.north.ownerCt = me.shadowContainer;
344             }
345             if (me.splitters.south) {
346                 me.splitters.south.ownerCt = me.shadowContainer;
347             }
348
349             // Inject items into the HBox Container if there is one - if there was an east or west.
350             if (me.embeddedContainer) {
351                 me.embeddedContainer.ownerCt = me.shadowContainer;
352                 me.createItems(me.embeddedContainer, hBoxItems);
353
354                 // Allow the Splitters to orientate themselves
355                 if (me.splitters.east) {
356                     me.splitters.east.ownerCt = me.embeddedContainer;
357                 }
358                 if (me.splitters.west) {
359                     me.splitters.west.ownerCt = me.embeddedContainer;
360                 }
361
362                 // These spliiters need to be constrained by components one-level below
363                 // the component in their vobx. We update the min/maxHeight on the helper
364                 // (embeddedContainer) prior to starting the split/drag. This has to be
365                 // done on-the-fly to allow min/maxHeight of the E/C/W regions to be set
366                 // dynamically.
367                 Ext.each([me.splitters.north, me.splitters.south], function (splitter) {
368                     if (splitter) {
369                         splitter.on('beforedragstart', me.fixHeightConstraints, me);
370                     }
371                 });
372
373                 // The east or west region wanted a percentage
374                 if (horizontalFlex) {
375                     regions.center.flex -= horizontalFlex;
376                 }
377                 // The north or south region wanted a percentage
378                 if (verticalFlex) {
379                     me.embeddedContainer.flex -= verticalFlex;
380                 }
381             } else {
382                 // The north or south region wanted a percentage
383                 if (verticalFlex) {
384                     regions.center.flex -= verticalFlex;
385                 }
386             }
387         }
388         // If we have no north or south, then there's only one Container, and it's
389         // an HBox, or, if only a center region was specified, a Fit.
390         else {
391             me.shadowContainer = Ext.create('Ext.container.Container', {
392                 ownerCt: me.owner,
393                 el: me.getTarget(),
394                 layout: Ext.applyIf({
395                     type: (hBoxItems.length == 1) ? 'fit' : 'hbox',
396                     align: 'stretch'
397                 }, me.initialConfig)
398             });
399             me.createItems(me.shadowContainer, hBoxItems);
400
401             // Allow the Splitters to orientate themselves
402             if (me.splitters.east) {
403                 me.splitters.east.ownerCt = me.shadowContainer;
404             }
405             if (me.splitters.west) {
406                 me.splitters.west.ownerCt = me.shadowContainer;
407             }
408
409             // The east or west region wanted a percentage
410             if (horizontalFlex) {
411                 regions.center.flex -= verticalFlex;
412             }
413         }
414
415         // Create upward links from the region Components to their shadow ownerCts
416         for (i = 0, items = me.shadowContainer.items.items, ln = items.length; i &lt; ln; i++) {
417             items[i].shadowOwnerCt = me.shadowContainer;
418         }
419         if (me.embeddedContainer) {
420             for (i = 0, items = me.embeddedContainer.items.items, ln = items.length; i &lt; ln; i++) {
421                 items[i].shadowOwnerCt = me.embeddedContainer;
422             }
423         }
424
425         // This is the layout that we delegate all operations to
426         me.shadowLayout = me.shadowContainer.getLayout();
427
428         me.borderLayoutInitialized = true;
429     },
430
431     setupState: function(comp){
432         var getState = comp.getState;
433         comp.getState = function(){
434             // call the original getState
435             var state = getState.call(comp) || {},
436                 region = comp.region;
437
438             state.collapsed = !!comp.collapsed;
439             if (region == 'west' || region == 'east') {
440                 state.width = comp.getWidth();
441             } else {
442                 state.height = comp.getHeight();
443             }
444             return state;
445         };
446         comp.addStateEvents(['collapse', 'expand', 'resize']);
447     },
448
449 <span id='Ext-layout-container-Border-method-createItems'>    /**
450 </span>     * Create the items collection for our shadow/embedded containers
451      * @private
452      */
453     createItems: function(container, items){
454         // Have to inject an items Collection *after* construction.
455         // The child items of the shadow layout must retain their original, user-defined ownerCt
456         delete container.items;
457         container.initItems();
458         container.items.addAll(items);
459     },
460
461     // Private
462     // Create a splitter for a child of the layout.
463     createSplitter: function(comp) {
464         var me = this,
465             interceptCollapse = (comp.collapseMode != 'header'),
466             resizer;
467
468         resizer = Ext.create('Ext.resizer.Splitter', {
469             hidden: !!comp.hidden,
470             collapseTarget: comp,
471             performCollapse: !interceptCollapse,
472             listeners: interceptCollapse ? {
473                 click: {
474                     fn: Ext.Function.bind(me.onSplitterCollapseClick, me, [comp]),
475                     element: 'collapseEl'
476                 }
477             } : null
478         });
479
480         // Mini collapse means that the splitter is the placeholder Component
481         if (comp.collapseMode == 'mini') {
482             comp.placeholder = resizer;
483             resizer.collapsedCls = comp.collapsedCls;
484         }
485
486         // Arrange to hide/show a region's associated splitter when the region is hidden/shown
487         comp.on({
488             hide: me.onRegionVisibilityChange,
489             show: me.onRegionVisibilityChange,
490             scope: me
491         });
492         return resizer;
493     },
494
495     // Private
496     // Propagates the min/maxHeight values from the inner hbox items to its container.
497     fixHeightConstraints: function () {
498         var me = this,
499             ct = me.embeddedContainer,
500             maxHeight = 1e99, minHeight = -1;
501
502         if (!ct) {
503             return;
504         }
505
506         ct.items.each(function (item) {
507             if (Ext.isNumber(item.maxHeight)) {
508                 maxHeight = Math.max(maxHeight, item.maxHeight);
509             }
510             if (Ext.isNumber(item.minHeight)) {
511                 minHeight = Math.max(minHeight, item.minHeight);
512             }
513         });
514
515         ct.maxHeight = maxHeight;
516         ct.minHeight = minHeight;
517     },
518
519     // Hide/show a region's associated splitter when the region is hidden/shown
520     onRegionVisibilityChange: function(comp){
521         this.splitters[comp.region][comp.hidden ? 'hide' : 'show']();
522         this.layout();
523     },
524
525     // Called when a splitter mini-collapse tool is clicked on.
526     // The listener is only added if this layout is controlling collapsing,
527     // not if the component's collapseMode is 'mini' or 'header'.
528     onSplitterCollapseClick: function(comp) {
529         if (comp.collapsed) {
530             this.onPlaceHolderToolClick(null, null, null, {client: comp});
531         } else {
532             comp.collapse();
533         }
534     },
535
536 <span id='Ext-layout-container-Border-method-getPlaceholder'>    /**
537 </span>     * Return the {@link Ext.panel.Panel#placeholder placeholder} Component to which the passed child Panel of the
538      * layout will collapse. By default, this will be a {@link Ext.panel.Header Header} component (Docked to the
539      * appropriate border). See {@link Ext.panel.Panel#placeholder placeholder}. config to customize this.
540      *
541      * **Note that this will be a fully instantiated Component, but will only be _rendered_ when the Panel is first
542      * collapsed.**
543      * @param {Ext.panel.Panel} panel The child Panel of the layout for which to return the {@link
544      * Ext.panel.Panel#placeholder placeholder}.
545      * @return {Ext.Component} The Panel's {@link Ext.panel.Panel#placeholder placeholder} unless the {@link
546      * Ext.panel.Panel#collapseMode collapseMode} is `'header'`, in which case _undefined_ is returned.
547      */
548     getPlaceholder: function(comp) {
549         var me = this,
550             placeholder = comp.placeholder,
551             shadowContainer = comp.shadowOwnerCt,
552             shadowLayout = shadowContainer.layout,
553             oppositeDirection = Ext.panel.Panel.prototype.getOppositeDirection(comp.collapseDirection),
554             horiz = (comp.region == 'north' || comp.region == 'south');
555
556         // No placeholder if the collapse mode is not the Border layout default
557         if (comp.collapseMode == 'header') {
558             return;
559         }
560
561         // Provide a replacement Container with an expand tool
562         if (!placeholder) {
563             if (comp.collapseMode == 'mini') {
564                 placeholder = Ext.create('Ext.resizer.Splitter', {
565                     id: 'collapse-placeholder-' + comp.id,
566                     collapseTarget: comp,
567                     performCollapse: false,
568                     listeners: {
569                         click: {
570                             fn: Ext.Function.bind(me.onSplitterCollapseClick, me, [comp]),
571                             element: 'collapseEl'
572                         }
573                     }
574                 });
575                 placeholder.addCls(placeholder.collapsedCls);
576             } else {
577                 placeholder = {
578                     id: 'collapse-placeholder-' + comp.id,
579                     margins: comp.initialConfig.margins || Ext.getClass(comp).prototype.margins,
580                     xtype: 'header',
581                     orientation: horiz ? 'horizontal' : 'vertical',
582                     title: comp.title,
583                     textCls: comp.headerTextCls,
584                     iconCls: comp.iconCls,
585                     baseCls: comp.baseCls + '-header',
586                     ui: comp.ui,
587                     indicateDrag: comp.draggable,
588                     cls: Ext.baseCSSPrefix + 'region-collapsed-placeholder ' + Ext.baseCSSPrefix + 'region-collapsed-' + comp.collapseDirection + '-placeholder ' + comp.collapsedCls,
589                     listeners: comp.floatable ? {
590                         click: {
591                             fn: function(e) {
592                                 me.floatCollapsedPanel(e, comp);
593                             },
594                             element: 'el'
595                         }
596                     } : null
597                 };
598                 // Hack for IE6/7/IEQuirks's inability to display an inline-block
599                 if ((Ext.isIE6 || Ext.isIE7 || (Ext.isIEQuirks)) &amp;&amp; !horiz) {
600                     placeholder.width = 25;
601                 }
602                 if (!comp.hideCollapseTool) {
603                     placeholder[horiz ? 'tools' : 'items'] = [{
604                         xtype: 'tool',
605                         client: comp,
606                         type: 'expand-' + oppositeDirection,
607                         handler: me.onPlaceHolderToolClick,
608                         scope: me
609                     }];
610                 }
611             }
612             placeholder = me.owner.createComponent(placeholder);
613             if (comp.isXType('panel')) {
614                 comp.on({
615                     titlechange: me.onRegionTitleChange,
616                     iconchange: me.onRegionIconChange,
617                     scope: me
618                 });
619             }
620         }
621
622         // The collapsed Component holds a reference to its placeholder and vice versa
623         comp.placeholder = placeholder;
624         placeholder.comp = comp;
625
626         return placeholder;
627     },
628
629 <span id='Ext-layout-container-Border-method-onRegionTitleChange'>    /**
630 </span>     * @private
631      * Update the placeholder title when panel title has been set or changed.
632      */
633     onRegionTitleChange: function(comp, newTitle) {
634         comp.placeholder.setTitle(newTitle);
635     },
636
637 <span id='Ext-layout-container-Border-method-onRegionIconChange'>    /**
638 </span>     * @private
639      * Update the placeholder iconCls when panel iconCls has been set or changed.
640      */
641     onRegionIconChange: function(comp, newIconCls) {
642         comp.placeholder.setIconCls(newIconCls);
643     },
644
645 <span id='Ext-layout-container-Border-method-calculateChildBox'>    /**
646 </span>     * @private
647      * Calculates the size and positioning of the passed child item. Must be present because Panel's expand,
648      * when configured with a flex, calls this method on its ownerCt's layout.
649      * @param {Ext.Component} child The child Component to calculate the box for
650      * @return {Object} Object containing box measurements for the child. Properties are left,top,width,height.
651      */
652     calculateChildBox: function(comp) {
653         var me = this;
654         if (me.shadowContainer.items.contains(comp)) {
655             return me.shadowContainer.layout.calculateChildBox(comp);
656         }
657         else if (me.embeddedContainer &amp;&amp; me.embeddedContainer.items.contains(comp)) {
658             return me.embeddedContainer.layout.calculateChildBox(comp);
659         }
660     },
661
662 <span id='Ext-layout-container-Border-method-onBeforeRegionCollapse'>    /**
663 </span>     * @private
664      * Intercepts the Panel's own collapse event and perform's substitution of the Panel
665      * with a placeholder Header orientated in the appropriate dimension.
666      * @param comp The Panel being collapsed.
667      * @param direction
668      * @param animate
669      * @returns {Boolean} false to inhibit the Panel from performing its own collapse.
670      */
671     onBeforeRegionCollapse: function(comp, direction, animate) {
672         if (comp.collapsedChangingLayout) {
673             //&lt;debug warn&gt;
674             if (Ext.global.console &amp;&amp; Ext.global.console.warn) {
675                 Ext.global.console.warn(Ext.getDisplayName(arguments.callee), 'aborted because the collapsed state is in the middle of changing');
676             }
677             //&lt;/debug&gt;
678             return false;
679         }
680         comp.collapsedChangingLayout = true;
681         var me = this,
682             compEl = comp.el,
683             width,
684             miniCollapse = comp.collapseMode == 'mini',
685             shadowContainer = comp.shadowOwnerCt,
686             shadowLayout = shadowContainer.layout,
687             placeholder = comp.placeholder,
688             sl = me.owner.suspendLayout,
689             scsl = shadowContainer.suspendLayout,
690             isNorthOrWest = (comp.region == 'north' || comp.region == 'west'); // Flag to keep the placeholder non-adjacent to any Splitter
691
692         // Do not trigger a layout during transition to collapsed Component
693         me.owner.suspendLayout = true;
694         shadowContainer.suspendLayout = true;
695
696         // Prevent upward notifications from downstream layouts
697         shadowLayout.layoutBusy = true;
698         if (shadowContainer.componentLayout) {
699             shadowContainer.componentLayout.layoutBusy = true;
700         }
701         me.shadowContainer.layout.layoutBusy = true;
702         me.layoutBusy = true;
703         me.owner.componentLayout.layoutBusy = true;
704
705         // Provide a replacement Container with an expand tool
706         if (!placeholder) {
707             placeholder = me.getPlaceholder(comp);
708         }
709
710         // placeholder already in place; show it.
711         if (placeholder.shadowOwnerCt === shadowContainer) {
712             placeholder.show();
713         }
714         // Insert the collapsed placeholder Component into the appropriate Box layout shadow Container
715         // It must go next to its client Component, but non-adjacent to the splitter so splitter can find its collapse client.
716         // Inject an ownerCt value pointing to the owner, border layout Container as the user will expect.
717         else {
718             shadowContainer.insert(shadowContainer.items.indexOf(comp) + (isNorthOrWest ? 0 : 1), placeholder);
719             placeholder.shadowOwnerCt = shadowContainer;
720             placeholder.ownerCt = me.owner;
721         }
722
723         // Flag the collapsing Component as hidden and show the placeholder.
724         // This causes the shadow Box layout's calculateChildBoxes to calculate the correct new arrangement.
725         // We hide or slideOut the Component's element
726         comp.hidden = true;
727
728         if (!placeholder.rendered) {
729             shadowLayout.renderItem(placeholder, shadowLayout.innerCt);
730
731             // The inserted placeholder does not have the proper size, so copy the width
732             // for N/S or the height for E/W from the component. This fixes EXTJSIV-1562
733             // without recursive layouts. This is only an issue initially. After this time,
734             // placeholder will have the correct width/height set by the layout (which has
735             // already happened when we get here initially).
736             if (comp.region == 'north' || comp.region == 'south') {
737                 placeholder.setCalculatedSize(comp.getWidth());
738             } else {
739                 placeholder.setCalculatedSize(undefined, comp.getHeight());
740             }
741         }
742
743         // Jobs to be done after the collapse has been done
744         function afterCollapse() {
745             // Reinstate automatic laying out.
746             me.owner.suspendLayout = sl;
747             shadowContainer.suspendLayout = scsl;
748             delete shadowLayout.layoutBusy;
749             if (shadowContainer.componentLayout) {
750                 delete shadowContainer.componentLayout.layoutBusy;
751             }
752             delete me.shadowContainer.layout.layoutBusy;
753             delete me.layoutBusy;
754             delete me.owner.componentLayout.layoutBusy;
755             delete comp.collapsedChangingLayout;
756
757             // Fire the collapse event: The Panel has in fact been collapsed, but by substitution of an alternative Component
758             comp.collapsed = true;
759             comp.fireEvent('collapse', comp);
760         }
761
762         /*
763          * Set everything to the new positions. Note that we
764          * only want to animate the collapse if it wasn't configured
765          * initially with collapsed: true
766          */
767         if (comp.animCollapse &amp;&amp; me.initialCollapsedComplete) {
768             shadowLayout.layout();
769             compEl.dom.style.zIndex = 100;
770
771             // If we're mini-collapsing, the placholder is a Splitter. We don't want it to &quot;bounce in&quot;
772             if (!miniCollapse) {
773                 placeholder.el.hide();
774             }
775             compEl.slideOut(me.slideDirection[comp.region], {
776                 duration: Ext.Number.from(comp.animCollapse, Ext.fx.Anim.prototype.duration),
777                 listeners: {
778                     afteranimate: function() {
779                         compEl.show().setLeftTop(-10000, -10000);
780                         compEl.dom.style.zIndex = '';
781
782                         // If we're mini-collapsing, the placholder is a Splitter. We don't want it to &quot;bounce in&quot;
783                        if (!miniCollapse) {
784                             placeholder.el.slideIn(me.slideDirection[comp.region], {
785                                 easing: 'linear',
786                                 duration: 100
787                             });
788                         }
789                         afterCollapse();
790                     }
791                 }
792             });
793         } else {
794             compEl.setLeftTop(-10000, -10000);
795             shadowLayout.layout();
796             afterCollapse();
797         }
798
799         return false;
800     },
801
802     // Hijack the expand operation to remove the placeholder and slide the region back in.
803     onBeforeRegionExpand: function(comp, animate) {
804         // We don't check for comp.collapsedChangingLayout here because onPlaceHolderToolClick does it
805         this.onPlaceHolderToolClick(null, null, null, {client: comp, shouldFireBeforeexpand: false});
806         return false;
807     },
808
809     // Called when the collapsed placeholder is clicked to reinstate a &quot;collapsed&quot; (in reality hidden) Panel.
810     onPlaceHolderToolClick: function(e, target, owner, tool) {
811         var me = this,
812             comp = tool.client,
813
814             // Hide the placeholder unless it was the Component's preexisting splitter
815             hidePlaceholder = (comp.collapseMode != 'mini') || !comp.split,
816             compEl = comp.el,
817             toCompBox,
818             placeholder = comp.placeholder,
819             placeholderEl = placeholder.el,
820             shadowContainer = comp.shadowOwnerCt,
821             shadowLayout = shadowContainer.layout,
822             curSize,
823             sl = me.owner.suspendLayout,
824             scsl = shadowContainer.suspendLayout,
825             isFloating;
826
827         if (comp.collapsedChangingLayout) {
828             //&lt;debug warn&gt;
829             if (Ext.global.console &amp;&amp; Ext.global.console.warn) {
830                 Ext.global.console.warn(Ext.getDisplayName(arguments.callee), 'aborted because the collapsed state is in the middle of changing');
831             }
832             //&lt;/debug&gt;
833             return false;
834         }
835         if (tool.shouldFireBeforeexpand !== false &amp;&amp; comp.fireEvent('beforeexpand', comp, true) === false) {
836             return false;
837         }
838         comp.collapsedChangingLayout = true;
839         // If the slide in is still going, stop it.
840         // This will either leave the Component in its fully floated state (which is processed below)
841         // or in its collapsed state. Either way, we expand it..
842         if (comp.getActiveAnimation()) {
843             comp.stopAnimation();
844         }
845
846         // If the Component is fully floated when they click the placeholder Tool,
847         // it will be primed with a slide out animation object... so delete that
848         // and remove the mouseout listeners
849         if (comp.slideOutAnim) {
850             // Remove mouse leave monitors
851             compEl.un(comp.panelMouseMon);
852             placeholderEl.un(comp.placeholderMouseMon);
853
854             delete comp.slideOutAnim;
855             delete comp.panelMouseMon;
856             delete comp.placeholderMouseMon;
857
858             // If the Panel was floated and primed with a slideOut animation, we don't want to animate its layout operation.
859             isFloating = true;
860         }
861
862         // Do not trigger a layout during transition to expanded Component
863         me.owner.suspendLayout = true;
864         shadowContainer.suspendLayout = true;
865
866         // Prevent upward notifications from downstream layouts
867         shadowLayout.layoutBusy = true;
868         if (shadowContainer.componentLayout) {
869             shadowContainer.componentLayout.layoutBusy = true;
870         }
871         me.shadowContainer.layout.layoutBusy = true;
872         me.layoutBusy = true;
873         me.owner.componentLayout.layoutBusy = true;
874
875         // Unset the hidden and collapsed flags set in onBeforeRegionCollapse. The shadowLayout will now take it into account
876         // Find where the shadow Box layout plans to put the expanding Component.
877         comp.hidden = false;
878         comp.collapsed = false;
879         if (hidePlaceholder) {
880             placeholder.hidden = true;
881         }
882         toCompBox = shadowLayout.calculateChildBox(comp);
883
884         // Show the collapse tool in case it was hidden by the slide-in
885         if (comp.collapseTool) {
886             comp.collapseTool.show();
887         }
888
889         // If we're going to animate, we need to hide the component before moving it back into position
890         if (comp.animCollapse &amp;&amp; !isFloating) {
891             compEl.setStyle('visibility', 'hidden');
892         }
893         compEl.setLeftTop(toCompBox.left, toCompBox.top);
894
895         // Equalize the size of the expanding Component prior to animation
896         // in case the layout area has changed size during the time it was collapsed.
897         curSize = comp.getSize();
898         if (curSize.height != toCompBox.height || curSize.width != toCompBox.width) {
899             me.setItemSize(comp, toCompBox.width, toCompBox.height);
900         }
901
902         // Jobs to be done after the expand has been done
903         function afterExpand() {
904             // Reinstate automatic laying out.
905             me.owner.suspendLayout = sl;
906             shadowContainer.suspendLayout = scsl;
907             delete shadowLayout.layoutBusy;
908             if (shadowContainer.componentLayout) {
909                 delete shadowContainer.componentLayout.layoutBusy;
910             }
911             delete me.shadowContainer.layout.layoutBusy;
912             delete me.layoutBusy;
913             delete me.owner.componentLayout.layoutBusy;
914             delete comp.collapsedChangingLayout;
915
916             // In case it was floated out and they clicked the re-expand tool
917             comp.removeCls(Ext.baseCSSPrefix + 'border-region-slide-in');
918
919             // Fire the expand event: The Panel has in fact been expanded, but by removal of an alternative Component
920             comp.fireEvent('expand', comp);
921         }
922
923         // Hide the placeholder
924         if (hidePlaceholder) {
925             placeholder.el.hide();
926         }
927
928         // Slide the expanding Component to its new position.
929         // When that is done, layout the layout.
930         if (comp.animCollapse &amp;&amp; !isFloating) {
931             compEl.dom.style.zIndex = 100;
932             compEl.slideIn(me.slideDirection[comp.region], {
933                 duration: Ext.Number.from(comp.animCollapse, Ext.fx.Anim.prototype.duration),
934                 listeners: {
935                     afteranimate: function() {
936                         compEl.dom.style.zIndex = '';
937                         comp.hidden = false;
938                         shadowLayout.onLayout();
939                         afterExpand();
940                     }
941                 }
942             });
943         } else {
944             shadowLayout.onLayout();
945             afterExpand();
946         }
947     },
948
949     floatCollapsedPanel: function(e, comp) {
950
951         if (comp.floatable === false) {
952             return;
953         }
954
955         var me = this,
956             compEl = comp.el,
957             placeholder = comp.placeholder,
958             placeholderEl = placeholder.el,
959             shadowContainer = comp.shadowOwnerCt,
960             shadowLayout = shadowContainer.layout,
961             placeholderBox = shadowLayout.getChildBox(placeholder),
962             scsl = shadowContainer.suspendLayout,
963             curSize, toCompBox, compAnim;
964
965         // Ignore clicks on tools.
966         if (e.getTarget('.' + Ext.baseCSSPrefix + 'tool')) {
967             return;
968         }
969
970         // It's *being* animated, ignore the click.
971         // Possible future enhancement: Stop and *reverse* the current active Fx.
972         if (compEl.getActiveAnimation()) {
973             return;
974         }
975
976         // If the Component is already fully floated when they click the placeholder,
977         // it will be primed with a slide out animation object... so slide it out.
978         if (comp.slideOutAnim) {
979             me.slideOutFloatedComponent(comp);
980             return;
981         }
982
983         // Function to be called when the mouse leaves the floated Panel
984         // Slide out when the mouse leaves the region bounded by the slid Component and its placeholder.
985         function onMouseLeaveFloated(e) {
986             var slideRegion = compEl.getRegion().union(placeholderEl.getRegion()).adjust(1, -1, -1, 1);
987
988             // If mouse is not within slide Region, slide it out
989             if (!slideRegion.contains(e.getPoint())) {
990                 me.slideOutFloatedComponent(comp);
991             }
992         }
993
994         // Monitor for mouseouting of the placeholder. Hide it if they exit for half a second or more
995         comp.placeholderMouseMon = placeholderEl.monitorMouseLeave(500, onMouseLeaveFloated);
996
997         // Do not trigger a layout during slide out of the Component
998         shadowContainer.suspendLayout = true;
999
1000         // Prevent upward notifications from downstream layouts
1001         me.layoutBusy = true;
1002         me.owner.componentLayout.layoutBusy = true;
1003
1004         // The collapse tool is hidden while slid.
1005         // It is re-shown on expand.
1006         if (comp.collapseTool) {
1007             comp.collapseTool.hide();
1008         }
1009
1010         // Set flags so that the layout will calculate the boxes for what we want
1011         comp.hidden = false;
1012         comp.collapsed = false;
1013         placeholder.hidden = true;
1014
1015         // Recalculate new arrangement of the Component being floated.
1016         toCompBox = shadowLayout.calculateChildBox(comp);
1017         placeholder.hidden = false;
1018
1019         // Component to appear just after the placeholder, whatever &quot;after&quot; means in the context of the shadow Box layout.
1020         if (comp.region == 'north' || comp.region == 'west') {
1021             toCompBox[shadowLayout.parallelBefore] += placeholderBox[shadowLayout.parallelPrefix] - 1;
1022         } else {
1023             toCompBox[shadowLayout.parallelBefore] -= (placeholderBox[shadowLayout.parallelPrefix] - 1);
1024         }
1025         compEl.setStyle('visibility', 'hidden');
1026         compEl.setLeftTop(toCompBox.left, toCompBox.top);
1027
1028         // Equalize the size of the expanding Component prior to animation
1029         // in case the layout area has changed size during the time it was collapsed.
1030         curSize = comp.getSize();
1031         if (curSize.height != toCompBox.height || curSize.width != toCompBox.width) {
1032             me.setItemSize(comp, toCompBox.width, toCompBox.height);
1033         }
1034
1035         // This animation slides the collapsed Component's el out to just beyond its placeholder
1036         compAnim = {
1037             listeners: {
1038                 afteranimate: function() {
1039                     shadowContainer.suspendLayout = scsl;
1040                     delete me.layoutBusy;
1041                     delete me.owner.componentLayout.layoutBusy;
1042
1043                     // Prime the Component with an Anim config object to slide it back out
1044                     compAnim.listeners = {
1045                         afterAnimate: function() {
1046                             compEl.show().removeCls(Ext.baseCSSPrefix + 'border-region-slide-in').setLeftTop(-10000, -10000);
1047
1048                             // Reinstate the correct, current state after slide out animation finishes
1049                             comp.hidden = true;
1050                             comp.collapsed = true;
1051                             delete comp.slideOutAnim;
1052                             delete comp.panelMouseMon;
1053                             delete comp.placeholderMouseMon;
1054                         }
1055                     };
1056                     comp.slideOutAnim = compAnim;
1057                 }
1058             },
1059             duration: 500
1060         };
1061
1062         // Give the element the correct class which places it at a high z-index
1063         compEl.addCls(Ext.baseCSSPrefix + 'border-region-slide-in');
1064
1065         // Begin the slide in
1066         compEl.slideIn(me.slideDirection[comp.region], compAnim);
1067
1068         // Monitor for mouseouting of the slid area. Hide it if they exit for half a second or more
1069         comp.panelMouseMon = compEl.monitorMouseLeave(500, onMouseLeaveFloated);
1070
1071     },
1072
1073     slideOutFloatedComponent: function(comp) {
1074         var compEl = comp.el,
1075             slideOutAnim;
1076
1077         // Remove mouse leave monitors
1078         compEl.un(comp.panelMouseMon);
1079         comp.placeholder.el.un(comp.placeholderMouseMon);
1080
1081         // Slide the Component out
1082         compEl.slideOut(this.slideDirection[comp.region], comp.slideOutAnim);
1083
1084         delete comp.slideOutAnim;
1085         delete comp.panelMouseMon;
1086         delete comp.placeholderMouseMon;
1087     },
1088
1089     /*
1090      * @private
1091      * Ensure any collapsed placeholder Component is destroyed along with its region.
1092      * Can't do this in onDestroy because they may remove a Component and use it elsewhere.
1093      */
1094     onRegionDestroy: function(comp) {
1095         var placeholder = comp.placeholder;
1096         if (placeholder) {
1097             delete placeholder.ownerCt;
1098             placeholder.destroy();
1099         }
1100     },
1101
1102     /*
1103      * @private
1104      * Ensure any shadow Containers are destroyed.
1105      * Ensure we don't keep references to Components.
1106      */
1107     onDestroy: function() {
1108         var me = this,
1109             shadowContainer = me.shadowContainer,
1110             embeddedContainer = me.embeddedContainer;
1111
1112         if (shadowContainer) {
1113             delete shadowContainer.ownerCt;
1114             Ext.destroy(shadowContainer);
1115         }
1116
1117         if (embeddedContainer) {
1118             delete embeddedContainer.ownerCt;
1119             Ext.destroy(embeddedContainer);
1120         }
1121         delete me.regions;
1122         delete me.splitters;
1123         delete me.shadowContainer;
1124         delete me.embeddedContainer;
1125         me.callParent(arguments);
1126     }
1127 });
1128 </pre>
1129 </body>
1130 </html>