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