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