Upgrade to ExtJS 4.0.2 - Released 06/09/2011
[extjs.git] / src / layout / container / Box.js
1 /*
2
3 This file is part of Ext JS 4
4
5 Copyright (c) 2011 Sencha Inc
6
7 Contact:  http://www.sencha.com/contact
8
9 GNU General Public License Usage
10 This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file.  Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
11
12 If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
13
14 */
15 /**
16  * @class Ext.layout.container.Box
17  * @extends Ext.layout.container.Container
18  * <p>Base Class for HBoxLayout and VBoxLayout Classes. Generally it should not need to be used directly.</p>
19  */
20
21 Ext.define('Ext.layout.container.Box', {
22
23     /* Begin Definitions */
24
25     alias: ['layout.box'],
26     extend: 'Ext.layout.container.Container',
27     alternateClassName: 'Ext.layout.BoxLayout',
28     
29     requires: [
30         'Ext.layout.container.boxOverflow.None',
31         'Ext.layout.container.boxOverflow.Menu',
32         'Ext.layout.container.boxOverflow.Scroller',
33         'Ext.util.Format',
34         'Ext.dd.DragDropManager'
35     ],
36
37     /* End Definitions */
38
39     /**
40      * @cfg {Mixed} animate
41      * <p>If truthy, child Component are <i>animated</i> into position whenever the Container
42      * is layed out. If this option is numeric, it is used as the animation duration in milliseconds.</p>
43      * <p>May be set as a property at any time.</p>
44      */
45
46     /**
47      * @cfg {Object} defaultMargins
48      * <p>If the individual contained items do not have a <tt>margins</tt>
49      * property specified or margin specified via CSS, the default margins from this property will be
50      * applied to each item.</p>
51      * <br><p>This property may be specified as an object containing margins
52      * to apply in the format:</p><pre><code>
53 {
54     top: (top margin),
55     right: (right margin),
56     bottom: (bottom margin),
57     left: (left margin)
58 }</code></pre>
59      * <p>This property may also be specified as a string containing
60      * space-separated, numeric margin values. The order of the sides associated
61      * with each value matches the way CSS processes margin values:</p>
62      * <div class="mdetail-params"><ul>
63      * <li>If there is only one value, it applies to all sides.</li>
64      * <li>If there are two values, the top and bottom borders are set to the
65      * first value and the right and left are set to the second.</li>
66      * <li>If there are three values, the top is set to the first value, the left
67      * and right are set to the second, and the bottom is set to the third.</li>
68      * <li>If there are four values, they apply to the top, right, bottom, and
69      * left, respectively.</li>
70      * </ul></div>
71      * <p>Defaults to:</p><pre><code>
72      * {top:0, right:0, bottom:0, left:0}
73      * </code></pre>
74      */
75     defaultMargins: {
76         top: 0,
77         right: 0,
78         bottom: 0,
79         left: 0
80     },
81
82     /**
83      * @cfg {String} padding
84      * <p>Sets the padding to be applied to all child items managed by this layout.</p>
85      * <p>This property must be specified as a string containing
86      * space-separated, numeric padding values. The order of the sides associated
87      * with each value matches the way CSS processes padding values:</p>
88      * <div class="mdetail-params"><ul>
89      * <li>If there is only one value, it applies to all sides.</li>
90      * <li>If there are two values, the top and bottom borders are set to the
91      * first value and the right and left are set to the second.</li>
92      * <li>If there are three values, the top is set to the first value, the left
93      * and right are set to the second, and the bottom is set to the third.</li>
94      * <li>If there are four values, they apply to the top, right, bottom, and
95      * left, respectively.</li>
96      * </ul></div>
97      * <p>Defaults to: <code>"0"</code></p>
98      */
99     padding: '0',
100     // documented in subclasses
101     pack: 'start',
102
103     /**
104      * @cfg {String} pack
105      * Controls how the child items of the container are packed together. Acceptable configuration values
106      * for this property are:
107      * <div class="mdetail-params"><ul>
108      * <li><b><tt>start</tt></b> : <b>Default</b><div class="sub-desc">child items are packed together at
109      * <b>left</b> side of container</div></li>
110      * <li><b><tt>center</tt></b> : <div class="sub-desc">child items are packed together at
111      * <b>mid-width</b> of container</div></li>
112      * <li><b><tt>end</tt></b> : <div class="sub-desc">child items are packed together at <b>right</b>
113      * side of container</div></li>
114      * </ul></div>
115      */
116     /**
117      * @cfg {Number} flex
118      * This configuration option is to be applied to <b>child <tt>items</tt></b> of the container managed
119      * by this layout. Each child item with a <tt>flex</tt> property will be flexed <b>horizontally</b>
120      * according to each item's <b>relative</b> <tt>flex</tt> value compared to the sum of all items with
121      * a <tt>flex</tt> value specified.  Any child items that have either a <tt>flex = 0</tt> or
122      * <tt>flex = undefined</tt> will not be 'flexed' (the initial size will not be changed).
123      */
124
125     type: 'box',
126     scrollOffset: 0,
127     itemCls: Ext.baseCSSPrefix + 'box-item',
128     targetCls: Ext.baseCSSPrefix + 'box-layout-ct',
129     innerCls: Ext.baseCSSPrefix + 'box-inner',
130
131     bindToOwnerCtContainer: true,
132
133     // availableSpaceOffset is used to adjust the availableWidth, typically used
134     // to reserve space for a scrollbar
135     availableSpaceOffset: 0,
136     
137     // whether or not to reserve the availableSpaceOffset in layout calculations
138     reserveOffset: true,
139     
140     /**
141      * @cfg {Boolean} clearInnerCtOnLayout
142      */
143     clearInnerCtOnLayout: false,
144
145     flexSortFn: function (a, b) {
146         var maxParallelPrefix = 'max' + this.parallelPrefixCap,
147             infiniteValue = Infinity;
148         a = a.component[maxParallelPrefix] || infiniteValue;
149         b = b.component[maxParallelPrefix] || infiniteValue;
150         // IE 6/7 Don't like Infinity - Infinity...
151         if (!isFinite(a) && !isFinite(b)) {
152             return false;
153         }
154         return a - b;
155     },
156
157     // Sort into *descending* order.
158     minSizeSortFn: function(a, b) {
159         return b.available - a.available;
160     },
161
162     constructor: function(config) {
163         var me = this;
164
165         me.callParent(arguments);
166
167         // The sort function needs access to properties in this, so must be bound.
168         me.flexSortFn = Ext.Function.bind(me.flexSortFn, me);
169
170         me.initOverflowHandler();
171     },
172
173     /**
174      * @private
175      * Returns the current size and positioning of the passed child item.
176      * @param {Component} child The child Component to calculate the box for
177      * @return {Object} Object containing box measurements for the child. Properties are left,top,width,height.
178      */
179     getChildBox: function(child) {
180         child = child.el || this.owner.getComponent(child).el;
181         var size = child.getBox(false, true);
182         return {
183             left: size.left,
184             top: size.top,
185             width: size.width,
186             height: size.height
187         };
188     },
189
190     /**
191      * @private
192      * Calculates the size and positioning of the passed child item.
193      * @param {Component} child The child Component to calculate the box for
194      * @return {Object} Object containing box measurements for the child. Properties are left,top,width,height.
195      */
196     calculateChildBox: function(child) {
197         var me = this,
198             boxes = me.calculateChildBoxes(me.getVisibleItems(), me.getLayoutTargetSize()).boxes,
199             ln = boxes.length,
200             i = 0;
201
202         child = me.owner.getComponent(child);
203         for (; i < ln; i++) {
204             if (boxes[i].component === child) {
205                 return boxes[i];
206             }
207         }
208     },
209
210     /**
211      * @private
212      * Calculates the size and positioning of each item in the box. This iterates over all of the rendered,
213      * visible items and returns a height, width, top and left for each, as well as a reference to each. Also
214      * returns meta data such as maxSize which are useful when resizing layout wrappers such as this.innerCt.
215      * @param {Array} visibleItems The array of all rendered, visible items to be calculated for
216      * @param {Object} targetSize Object containing target size and height
217      * @return {Object} Object containing box measurements for each child, plus meta data
218      */
219     calculateChildBoxes: function(visibleItems, targetSize) {
220         var me = this,
221             math = Math,
222             mmax = math.max,
223             infiniteValue = Infinity,
224             undefinedValue,
225
226             parallelPrefix = me.parallelPrefix,
227             parallelPrefixCap = me.parallelPrefixCap,
228             perpendicularPrefix = me.perpendicularPrefix,
229             perpendicularPrefixCap = me.perpendicularPrefixCap,
230             parallelMinString = 'min' + parallelPrefixCap,
231             perpendicularMinString = 'min' + perpendicularPrefixCap,
232             perpendicularMaxString = 'max' + perpendicularPrefixCap,
233
234             parallelSize = targetSize[parallelPrefix] - me.scrollOffset,
235             perpendicularSize = targetSize[perpendicularPrefix],
236             padding = me.padding,
237             parallelOffset = padding[me.parallelBefore],
238             paddingParallel = parallelOffset + padding[me.parallelAfter],
239             perpendicularOffset = padding[me.perpendicularLeftTop],
240             paddingPerpendicular =  perpendicularOffset + padding[me.perpendicularRightBottom],
241             availPerpendicularSize = mmax(0, perpendicularSize - paddingPerpendicular),
242
243             innerCtBorderWidth = me.innerCt.getBorderWidth(me.perpendicularLT + me.perpendicularRB),
244             
245             isStart = me.pack == 'start',
246             isCenter = me.pack == 'center',
247             isEnd = me.pack == 'end',
248
249             constrain = Ext.Number.constrain,
250             visibleCount = visibleItems.length,
251             nonFlexSize = 0,
252             totalFlex = 0,
253             desiredSize = 0,
254             minimumSize = 0,
255             maxSize = 0,
256             boxes = [],
257             minSizes = [],
258             calculatedWidth,
259
260             i, child, childParallel, childPerpendicular, childMargins, childSize, minParallel, tmpObj, shortfall, 
261             tooNarrow, availableSpace, minSize, item, length, itemIndex, box, oldSize, newSize, reduction, diff, 
262             flexedBoxes, remainingSpace, remainingFlex, flexedSize, parallelMargins, calcs, offset, 
263             perpendicularMargins, stretchSize;
264
265         //gather the total flex of all flexed items and the width taken up by fixed width items
266         for (i = 0; i < visibleCount; i++) {
267             child = visibleItems[i];
268             childPerpendicular = child[perpendicularPrefix];
269             me.layoutItem(child);
270             childMargins = child.margins;
271             parallelMargins = childMargins[me.parallelBefore] + childMargins[me.parallelAfter];
272
273             // Create the box description object for this child item.
274             tmpObj = {
275                 component: child,
276                 margins: childMargins
277             };
278
279             // flex and not 'auto' width
280             if (child.flex) {
281                 totalFlex += child.flex;
282                 childParallel = undefinedValue;
283             }
284             // Not flexed or 'auto' width or undefined width
285             else {
286                 if (!(child[parallelPrefix] && childPerpendicular)) {
287                     childSize = child.getSize();
288                 }
289                 childParallel = child[parallelPrefix] || childSize[parallelPrefix];
290                 childPerpendicular = childPerpendicular || childSize[perpendicularPrefix];
291             }
292
293             nonFlexSize += parallelMargins + (childParallel || 0);
294             desiredSize += parallelMargins + (child.flex ? child[parallelMinString] || 0 : childParallel);
295             minimumSize += parallelMargins + (child[parallelMinString] || childParallel || 0);
296
297             // Max height for align - force layout of non-laid out subcontainers without a numeric height
298             if (typeof childPerpendicular != 'number') {
299                 // Clear any static sizing and revert to flow so we can get a proper measurement
300                 // child['set' + perpendicularPrefixCap](null);
301                 childPerpendicular = child['get' + perpendicularPrefixCap]();
302             }
303
304             // Track the maximum perpendicular size for use by the stretch and stretchmax align config values.
305             maxSize = mmax(maxSize, childPerpendicular + childMargins[me.perpendicularLeftTop] + childMargins[me.perpendicularRightBottom]);
306
307             tmpObj[parallelPrefix] = childParallel || undefinedValue;
308             tmpObj[perpendicularPrefix] = childPerpendicular || undefinedValue;
309             boxes.push(tmpObj);
310         }
311         shortfall = desiredSize - parallelSize;
312         tooNarrow = minimumSize > parallelSize;
313
314         //the space available to the flexed items
315         availableSpace = mmax(0, parallelSize - nonFlexSize - paddingParallel - (me.reserveOffset ? me.availableSpaceOffset : 0));
316
317         if (tooNarrow) {
318             for (i = 0; i < visibleCount; i++) {
319                 box = boxes[i];
320                 minSize = visibleItems[i][parallelMinString] || visibleItems[i][parallelPrefix] || box[parallelPrefix];
321                 box.dirtySize = box.dirtySize || box[parallelPrefix] != minSize;
322                 box[parallelPrefix] = minSize;
323             }
324         }
325         else {
326             //all flexed items should be sized to their minimum size, other items should be shrunk down until
327             //the shortfall has been accounted for
328             if (shortfall > 0) {
329                 /*
330                  * When we have a shortfall but are not tooNarrow, we need to shrink the width of each non-flexed item.
331                  * Flexed items are immediately reduced to their minWidth and anything already at minWidth is ignored.
332                  * The remaining items are collected into the minWidths array, which is later used to distribute the shortfall.
333                  */
334                 for (i = 0; i < visibleCount; i++) {
335                     item = visibleItems[i];
336                     minSize = item[parallelMinString] || 0;
337
338                     //shrink each non-flex tab by an equal amount to make them all fit. Flexed items are all
339                     //shrunk to their minSize because they're flexible and should be the first to lose size
340                     if (item.flex) {
341                         box = boxes[i];
342                         box.dirtySize = box.dirtySize || box[parallelPrefix] != minSize;
343                         box[parallelPrefix] = minSize;
344                     }
345                     else {
346                         minSizes.push({
347                             minSize: minSize,
348                             available: boxes[i][parallelPrefix] - minSize,
349                             index: i
350                         });
351                     }
352                 }
353
354                 //sort by descending amount of width remaining before minWidth is reached
355                 Ext.Array.sort(minSizes, me.minSizeSortFn);
356
357                 /*
358                  * Distribute the shortfall (difference between total desired size of all items and actual size available)
359                  * between the non-flexed items. We try to distribute the shortfall evenly, but apply it to items with the
360                  * smallest difference between their size and minSize first, so that if reducing the size by the average
361                  * amount would make that item less than its minSize, we carry the remainder over to the next item.
362                  */
363                 for (i = 0, length = minSizes.length; i < length; i++) {
364                     itemIndex = minSizes[i].index;
365
366                     if (itemIndex == undefinedValue) {
367                         continue;
368                     }
369                     item = visibleItems[itemIndex];
370                     minSize = minSizes[i].minSize;
371
372                     box = boxes[itemIndex];
373                     oldSize = box[parallelPrefix];
374                     newSize = mmax(minSize, oldSize - math.ceil(shortfall / (length - i)));
375                     reduction = oldSize - newSize;
376
377                     box.dirtySize = box.dirtySize || box[parallelPrefix] != newSize;
378                     box[parallelPrefix] = newSize;
379                     shortfall -= reduction;
380                 }
381             }
382             else {
383                 remainingSpace = availableSpace;
384                 remainingFlex = totalFlex;
385                 flexedBoxes = [];
386
387                 // Create an array containing *just the flexed boxes* for allocation of remainingSpace
388                 for (i = 0; i < visibleCount; i++) {
389                     child = visibleItems[i];
390                     if (isStart && child.flex) {
391                         flexedBoxes.push(boxes[Ext.Array.indexOf(visibleItems, child)]);
392                     }
393                 }
394                 // The flexed boxes need to be sorted in ascending order of maxSize to work properly
395                 // so that unallocated space caused by maxWidth being less than flexed width
396                 // can be reallocated to subsequent flexed boxes.
397                 Ext.Array.sort(flexedBoxes, me.flexSortFn);
398
399                 // Calculate the size of each flexed item, and attempt to set it.
400                 for (i = 0; i < flexedBoxes.length; i++) {
401                     calcs = flexedBoxes[i];
402                     child = calcs.component;
403                     childMargins = calcs.margins;
404
405                     flexedSize = math.ceil((child.flex / remainingFlex) * remainingSpace);
406
407                     // Implement maxSize and minSize check
408                     flexedSize = Math.max(child['min' + parallelPrefixCap] || 0, math.min(child['max' + parallelPrefixCap] || infiniteValue, flexedSize));
409
410                     // Remaining space has already had all parallel margins subtracted from it, so just subtract consumed size
411                     remainingSpace -= flexedSize;
412                     remainingFlex -= child.flex;
413
414                     calcs.dirtySize = calcs.dirtySize || calcs[parallelPrefix] != flexedSize;
415                     calcs[parallelPrefix] = flexedSize;
416                 }
417             }
418         }
419
420         if (isCenter) {
421             parallelOffset += availableSpace / 2;
422         }
423         else if (isEnd) {
424             parallelOffset += availableSpace;
425         }
426
427         // Fix for left and right docked Components in a dock component layout. This is for docked Headers and docked Toolbars.
428         // Older Microsoft browsers do not size a position:absolute element's width to match its content.
429         // So in this case, in the updateInnerCtSize method we may need to adjust the size of the owning Container's element explicitly based upon
430         // the discovered max width. So here we put a calculatedWidth property in the metadata to facilitate this.
431         if (me.owner.dock && (Ext.isIE6 || Ext.isIE7 || Ext.isIEQuirks) && !me.owner.width && me.direction == 'vertical') {
432
433             calculatedWidth = maxSize + me.owner.el.getPadding('lr') + me.owner.el.getBorderWidth('lr');
434             if (me.owner.frameSize) {
435                 calculatedWidth += me.owner.frameSize.left + me.owner.frameSize.right;
436             }
437             // If the owning element is not sized, calculate the available width to center or stretch in based upon maxSize
438             availPerpendicularSize = Math.min(availPerpendicularSize, targetSize.width = maxSize + padding.left + padding.right);
439         }
440
441         //finally, calculate the left and top position of each item
442         for (i = 0; i < visibleCount; i++) {
443             child = visibleItems[i];
444             calcs = boxes[i];
445
446             childMargins = calcs.margins;
447
448             perpendicularMargins = childMargins[me.perpendicularLeftTop] + childMargins[me.perpendicularRightBottom];
449
450             // Advance past the "before" margin
451             parallelOffset += childMargins[me.parallelBefore];
452
453             calcs[me.parallelBefore] = parallelOffset;
454             calcs[me.perpendicularLeftTop] = perpendicularOffset + childMargins[me.perpendicularLeftTop];
455
456             if (me.align == 'stretch') {
457                 stretchSize = constrain(availPerpendicularSize - perpendicularMargins, child[perpendicularMinString] || 0, child[perpendicularMaxString] || infiniteValue);
458                 calcs.dirtySize = calcs.dirtySize || calcs[perpendicularPrefix] != stretchSize;
459                 calcs[perpendicularPrefix] = stretchSize;
460             }
461             else if (me.align == 'stretchmax') {
462                 stretchSize = constrain(maxSize - perpendicularMargins, child[perpendicularMinString] || 0, child[perpendicularMaxString] || infiniteValue);
463                 calcs.dirtySize = calcs.dirtySize || calcs[perpendicularPrefix] != stretchSize;
464                 calcs[perpendicularPrefix] = stretchSize;
465             }
466             else if (me.align == me.alignCenteringString) {
467                 // When calculating a centered position within the content box of the innerCt, the width of the borders must be subtracted from
468                 // the size to yield the space available to center within.
469                 // The updateInnerCtSize method explicitly adds the border widths to the set size of the innerCt.
470                 diff = mmax(availPerpendicularSize, maxSize) - innerCtBorderWidth - calcs[perpendicularPrefix];
471                 if (diff > 0) {
472                     calcs[me.perpendicularLeftTop] = perpendicularOffset + Math.round(diff / 2);
473                 }
474             }
475
476             // Advance past the box size and the "after" margin
477             parallelOffset += (calcs[parallelPrefix] || 0) + childMargins[me.parallelAfter];
478         }
479
480         return {
481             boxes: boxes,
482             meta : {
483                 calculatedWidth: calculatedWidth,
484                 maxSize: maxSize,
485                 nonFlexSize: nonFlexSize,
486                 desiredSize: desiredSize,
487                 minimumSize: minimumSize,
488                 shortfall: shortfall,
489                 tooNarrow: tooNarrow
490             }
491         };
492     },
493     
494     onRemove: function(comp){
495         this.callParent(arguments);
496         if (this.overflowHandler) {
497             this.overflowHandler.onRemove(comp);
498         }
499     },
500
501     /**
502      * @private
503      */
504     initOverflowHandler: function() {
505         var handler = this.overflowHandler;
506
507         if (typeof handler == 'string') {
508             handler = {
509                 type: handler
510             };
511         }
512
513         var handlerType = 'None';
514         if (handler && handler.type !== undefined) {
515             handlerType = handler.type;
516         }
517
518         var constructor = Ext.layout.container.boxOverflow[handlerType];
519         if (constructor[this.type]) {
520             constructor = constructor[this.type];
521         }
522
523         this.overflowHandler = Ext.create('Ext.layout.container.boxOverflow.' + handlerType, this, handler);
524     },
525
526     /**
527      * @private
528      * Runs the child box calculations and caches them in childBoxCache. Subclasses can used these cached values
529      * when laying out
530      */
531     onLayout: function() {
532         this.callParent();
533         // Clear the innerCt size so it doesn't influence the child items.
534         if (this.clearInnerCtOnLayout === true && this.adjustmentPass !== true) {
535             this.innerCt.setSize(null, null);
536         }
537
538         var me = this,
539             targetSize = me.getLayoutTargetSize(),
540             items = me.getVisibleItems(),
541             calcs = me.calculateChildBoxes(items, targetSize),
542             boxes = calcs.boxes,
543             meta = calcs.meta,
544             handler, method, results;
545
546         if (me.autoSize && calcs.meta.desiredSize) {
547             targetSize[me.parallelPrefix] = calcs.meta.desiredSize;
548         }
549
550         //invoke the overflow handler, if one is configured
551         if (meta.shortfall > 0) {
552             handler = me.overflowHandler;
553             method = meta.tooNarrow ? 'handleOverflow': 'clearOverflow';
554
555             results = handler[method](calcs, targetSize);
556
557             if (results) {
558                 if (results.targetSize) {
559                     targetSize = results.targetSize;
560                 }
561
562                 if (results.recalculate) {
563                     items = me.getVisibleItems();
564                     calcs = me.calculateChildBoxes(items, targetSize);
565                     boxes = calcs.boxes;
566                 }
567             }
568         } else {
569             me.overflowHandler.clearOverflow();
570         }
571
572         /**
573          * @private
574          * @property layoutTargetLastSize
575          * @type Object
576          * Private cache of the last measured size of the layout target. This should never be used except by
577          * BoxLayout subclasses during their onLayout run.
578          */
579         me.layoutTargetLastSize = targetSize;
580
581         /**
582          * @private
583          * @property childBoxCache
584          * @type Array
585          * Array of the last calculated height, width, top and left positions of each visible rendered component
586          * within the Box layout.
587          */
588         me.childBoxCache = calcs;
589
590         me.updateInnerCtSize(targetSize, calcs);
591         me.updateChildBoxes(boxes);
592         me.handleTargetOverflow(targetSize);
593     },
594
595     /**
596      * Resizes and repositions each child component
597      * @param {Array} boxes The box measurements
598      */
599     updateChildBoxes: function(boxes) {
600         var me = this,
601             i = 0,
602             length = boxes.length,
603             animQueue = [],
604             dd = Ext.dd.DDM.getDDById(me.innerCt.id), // Any DD active on this layout's element (The BoxReorderer plugin does this.)
605             oldBox, newBox, changed, comp, boxAnim, animCallback;
606
607         for (; i < length; i++) {
608             newBox = boxes[i];
609             comp = newBox.component;
610
611             // If a Component is being drag/dropped, skip positioning it.
612             // Accomodate the BoxReorderer plugin: Its current dragEl must not be positioned by the layout
613             if (dd && (dd.getDragEl() === comp.el.dom)) {
614                 continue;
615             }
616
617             changed = false;
618
619             oldBox = me.getChildBox(comp);
620
621             // If we are animating, we build up an array of Anim config objects, one for each
622             // child Component which has any changed box properties. Those with unchanged
623             // properties are not animated.
624             if (me.animate) {
625                 // Animate may be a config object containing callback.
626                 animCallback = me.animate.callback || me.animate;
627                 boxAnim = {
628                     layoutAnimation: true,  // Component Target handler must use set*Calculated*Size
629                     target: comp,
630                     from: {},
631                     to: {},
632                     listeners: {}
633                 };
634                 // Only set from and to properties when there's a change.
635                 // Perform as few Component setter methods as possible.
636                 // Temporarily set the property values that we are not animating
637                 // so that doComponentLayout does not auto-size them.
638                 if (!isNaN(newBox.width) && (newBox.width != oldBox.width)) {
639                     changed = true;
640                     // boxAnim.from.width = oldBox.width;
641                     boxAnim.to.width = newBox.width;
642                 }
643                 if (!isNaN(newBox.height) && (newBox.height != oldBox.height)) {
644                     changed = true;
645                     // boxAnim.from.height = oldBox.height;
646                     boxAnim.to.height = newBox.height;
647                 }
648                 if (!isNaN(newBox.left) && (newBox.left != oldBox.left)) {
649                     changed = true;
650                     // boxAnim.from.left = oldBox.left;
651                     boxAnim.to.left = newBox.left;
652                 }
653                 if (!isNaN(newBox.top) && (newBox.top != oldBox.top)) {
654                     changed = true;
655                     // boxAnim.from.top = oldBox.top;
656                     boxAnim.to.top = newBox.top;
657                 }
658                 if (changed) {
659                     animQueue.push(boxAnim);
660                 }
661             } else {
662                 if (newBox.dirtySize) {
663                     if (newBox.width !== oldBox.width || newBox.height !== oldBox.height) {
664                         me.setItemSize(comp, newBox.width, newBox.height);
665                     }
666                 }
667                 // Don't set positions to NaN
668                 if (isNaN(newBox.left) || isNaN(newBox.top)) {
669                     continue;
670                 }
671                 comp.setPosition(newBox.left, newBox.top);
672             }
673         }
674
675         // Kick off any queued animations
676         length = animQueue.length;
677         if (length) {
678
679             // A function which cleans up when a Component's animation is done.
680             // The last one to finish calls the callback.
681             var afterAnimate = function(anim) {
682                 // When we've animated all changed boxes into position, clear our busy flag and call the callback.
683                 length -= 1;
684                 if (!length) {
685                     me.layoutBusy = false;
686                     if (Ext.isFunction(animCallback)) {
687                         animCallback();
688                     }
689                 }
690             };
691
692             var beforeAnimate = function() {
693                 me.layoutBusy = true;
694             };
695
696             // Start each box animation off
697             for (i = 0, length = animQueue.length; i < length; i++) {
698                 boxAnim = animQueue[i];
699
700                 // Clean up the Component after. Clean up the *layout* after the last animation finishes
701                 boxAnim.listeners.afteranimate = afterAnimate;
702
703                 // The layout is busy during animation, and may not be called, so set the flag when the first animation begins
704                 if (!i) {
705                     boxAnim.listeners.beforeanimate = beforeAnimate;
706                 }
707                 if (me.animate.duration) {
708                     boxAnim.duration = me.animate.duration;
709                 }
710                 comp = boxAnim.target;
711                 delete boxAnim.target;
712                 // Stop any currently running animation
713                 comp.stopAnimation();
714                 comp.animate(boxAnim);
715             }
716         }
717     },
718
719     /**
720      * @private
721      * Called by onRender just before the child components are sized and positioned. This resizes the innerCt
722      * to make sure all child items fit within it. We call this before sizing the children because if our child
723      * items are larger than the previous innerCt size the browser will insert scrollbars and then remove them
724      * again immediately afterwards, giving a performance hit.
725      * Subclasses should provide an implementation.
726      * @param {Object} currentSize The current height and width of the innerCt
727      * @param {Array} calculations The new box calculations of all items to be laid out
728      */
729     updateInnerCtSize: function(tSize, calcs) {
730         var me = this,
731             mmax = Math.max,
732             align = me.align,
733             padding = me.padding,
734             width = tSize.width,
735             height = tSize.height,
736             meta = calcs.meta,
737             innerCtWidth,
738             innerCtHeight;
739
740         if (me.direction == 'horizontal') {
741             innerCtWidth = width;
742             innerCtHeight = meta.maxSize + padding.top + padding.bottom + me.innerCt.getBorderWidth('tb');
743
744             if (align == 'stretch') {
745                 innerCtHeight = height;
746             }
747             else if (align == 'middle') {
748                 innerCtHeight = mmax(height, innerCtHeight);
749             }
750         } else {
751             innerCtHeight = height;
752             innerCtWidth = meta.maxSize + padding.left + padding.right + me.innerCt.getBorderWidth('lr');
753
754             if (align == 'stretch') {
755                 innerCtWidth = width;
756             }
757             else if (align == 'center') {
758                 innerCtWidth = mmax(width, innerCtWidth);
759             }
760         }
761         me.getRenderTarget().setSize(innerCtWidth || undefined, innerCtHeight || undefined);
762
763         // If a calculated width has been found (and this only happens for auto-width vertical docked Components in old Microsoft browsers)
764         // then, if the Component has not assumed the size of its content, set it to do so.
765         if (meta.calculatedWidth && me.owner.el.getWidth() > meta.calculatedWidth) {
766             me.owner.el.setWidth(meta.calculatedWidth);
767         }
768
769         if (me.innerCt.dom.scrollTop) {
770             me.innerCt.dom.scrollTop = 0;
771         }
772     },
773
774     /**
775      * @private
776      * This should be called after onLayout of any BoxLayout subclass. If the target's overflow is not set to 'hidden',
777      * we need to lay out a second time because the scrollbars may have modified the height and width of the layout
778      * target. Having a Box layout inside such a target is therefore not recommended.
779      * @param {Object} previousTargetSize The size and height of the layout target before we just laid out
780      * @param {Ext.container.Container} container The container
781      * @param {Ext.core.Element} target The target element
782      * @return True if the layout overflowed, and was reflowed in a secondary onLayout call.
783      */
784     handleTargetOverflow: function(previousTargetSize) {
785         var target = this.getTarget(),
786             overflow = target.getStyle('overflow'),
787             newTargetSize;
788
789         if (overflow && overflow != 'hidden' && !this.adjustmentPass) {
790             newTargetSize = this.getLayoutTargetSize();
791             if (newTargetSize.width != previousTargetSize.width || newTargetSize.height != previousTargetSize.height) {
792                 this.adjustmentPass = true;
793                 this.onLayout();
794                 return true;
795             }
796         }
797
798         delete this.adjustmentPass;
799     },
800
801     // private
802     isValidParent : function(item, target, position) {
803         // Note: Box layouts do not care about order within the innerCt element because it's an absolutely positioning layout
804         // We only care whether the item is a direct child of the innerCt element.
805         var itemEl = item.el ? item.el.dom : Ext.getDom(item);
806         return (itemEl && this.innerCt && itemEl.parentNode === this.innerCt.dom) || false;
807     },
808
809     // Overridden method from AbstractContainer.
810     // Used in the base AbstractLayout.beforeLayout method to render all items into.
811     getRenderTarget: function() {
812         if (!this.innerCt) {
813             // the innerCt prevents wrapping and shuffling while the container is resizing
814             this.innerCt = this.getTarget().createChild({
815                 cls: this.innerCls,
816                 role: 'presentation'
817             });
818             this.padding = Ext.util.Format.parseBox(this.padding);
819         }
820         return this.innerCt;
821     },
822
823     // private
824     renderItem: function(item, target) {
825         this.callParent(arguments);
826         var me = this,
827             itemEl = item.getEl(),
828             style = itemEl.dom.style,
829             margins = item.margins || item.margin;
830
831         // Parse the item's margin/margins specification
832         if (margins) {
833             if (Ext.isString(margins) || Ext.isNumber(margins)) {
834                 margins = Ext.util.Format.parseBox(margins);
835             } else {
836                 Ext.applyIf(margins, {top: 0, right: 0, bottom: 0, left: 0});
837             }
838         } else {
839             margins = Ext.apply({}, me.defaultMargins);
840         }
841
842         // Add any before/after CSS margins to the configured margins, and zero the CSS margins
843         margins.top    += itemEl.getMargin('t');
844         margins.right  += itemEl.getMargin('r');
845         margins.bottom += itemEl.getMargin('b');
846         margins.left   += itemEl.getMargin('l');
847         style.marginTop = style.marginRight = style.marginBottom = style.marginLeft = '0';
848
849         // Item must reference calculated margins.
850         item.margins = margins;
851     },
852
853     /**
854      * @private
855      */
856     destroy: function() {
857         Ext.destroy(this.overflowHandler);
858         this.callParent(arguments);
859     }
860 });