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