Upgrade to ExtJS 3.3.0 - Released 10/06/2010
[extjs.git] / src / widgets / layout / VBoxLayout.js
1 /*!
2  * Ext JS Library 3.3.0
3  * Copyright(c) 2006-2010 Ext JS, Inc.
4  * licensing@extjs.com
5  * http://www.extjs.com/license
6  */
7 /**
8  * @class Ext.layout.VBoxLayout
9  * @extends Ext.layout.BoxLayout
10  * <p>A layout that arranges items vertically down a Container. This layout optionally divides available vertical
11  * space between child items containing a numeric <code>flex</code> configuration.</p>
12  * This layout may also be used to set the widths of child items by configuring it with the {@link #align} option.
13  */
14 Ext.layout.VBoxLayout = Ext.extend(Ext.layout.BoxLayout, {
15     /**
16      * @cfg {String} align
17      * Controls how the child items of the container are aligned. Acceptable configuration values for this
18      * property are:
19      * <div class="mdetail-params"><ul>
20      * <li><b><tt>left</tt></b> : <b>Default</b><div class="sub-desc">child items are aligned horizontally
21      * at the <b>left</b> side of the container</div></li>
22      * <li><b><tt>center</tt></b> : <div class="sub-desc">child items are aligned horizontally at the
23      * <b>mid-width</b> of the container</div></li>
24      * <li><b><tt>stretch</tt></b> : <div class="sub-desc">child items are stretched horizontally to fill
25      * the width of the container</div></li>
26      * <li><b><tt>stretchmax</tt></b> : <div class="sub-desc">child items are stretched horizontally to
27      * the size of the largest item.</div></li>
28      * </ul></div>
29      */
30     align : 'left', // left, center, stretch, strechmax
31     type: 'vbox',
32
33     /**
34      * @cfg {String} pack
35      * Controls how the child items of the container are packed together. Acceptable configuration values
36      * for this property are:
37      * <div class="mdetail-params"><ul>
38      * <li><b><tt>start</tt></b> : <b>Default</b><div class="sub-desc">child items are packed together at
39      * <b>top</b> side of container</div></li>
40      * <li><b><tt>center</tt></b> : <div class="sub-desc">child items are packed together at
41      * <b>mid-height</b> of container</div></li>
42      * <li><b><tt>end</tt></b> : <div class="sub-desc">child items are packed together at <b>bottom</b>
43      * side of container</div></li>
44      * </ul></div>
45      */
46
47     /**
48      * @cfg {Number} flex
49      * This configuation option is to be applied to <b>child <tt>items</tt></b> of the container managed
50      * by this layout. Each child item with a <tt>flex</tt> property will be flexed <b>vertically</b>
51      * according to each item's <b>relative</b> <tt>flex</tt> value compared to the sum of all items with
52      * a <tt>flex</tt> value specified.  Any child items that have either a <tt>flex = 0</tt> or
53      * <tt>flex = undefined</tt> will not be 'flexed' (the initial size will not be changed).
54      */
55
56     /**
57      * @private
58      * Calculates the size and positioning of each item in the VBox. This iterates over all of the rendered,
59      * visible items and returns a height, width, top and left for each, as well as a reference to each. Also
60      * returns meta data such as maxHeight which are useful when resizing layout wrappers such as this.innerCt.
61      * @param {Array} visibleItems The array of all rendered, visible items to be calculated for
62      * @param {Object} targetSize Object containing target size and height
63      * @return {Object} Object containing box measurements for each child, plus meta data
64      */
65     calculateChildBoxes: function(visibleItems, targetSize) {
66         var visibleCount = visibleItems.length,
67
68             padding      = this.padding,
69             topOffset    = padding.top,
70             leftOffset   = padding.left,
71             paddingVert  = topOffset  + padding.bottom,
72             paddingHoriz = leftOffset + padding.right,
73
74             width        = targetSize.width - this.scrollOffset,
75             height       = targetSize.height,
76             availWidth   = Math.max(0, width - paddingHoriz),
77
78             isStart      = this.pack == 'start',
79             isCenter     = this.pack == 'center',
80             isEnd        = this.pack == 'end',
81
82             nonFlexHeight= 0,
83             maxWidth     = 0,
84             totalFlex    = 0,
85             desiredHeight= 0,
86             minimumHeight= 0,
87
88             //used to cache the calculated size and position values for each child item
89             boxes        = [],
90             
91             //used in the for loops below, just declared here for brevity
92             child, childWidth, childHeight, childSize, childMargins, canLayout, i, calcs, flexedWidth, 
93             horizMargins, vertMargins, stretchWidth;
94
95         //gather the total flex of all flexed items and the width taken up by fixed width items
96         for (i = 0; i < visibleCount; i++) {
97             child = visibleItems[i];
98             childHeight = child.height;
99             childWidth  = child.width;
100             canLayout   = !child.hasLayout && typeof child.doLayout == 'function';
101
102             // Static height (numeric) requires no calcs
103             if (typeof childHeight != 'number') {
104
105                 // flex and not 'auto' height
106                 if (child.flex && !childHeight) {
107                     totalFlex += child.flex;
108
109                 // Not flexed or 'auto' height or undefined height
110                 } else {
111                     //Render and layout sub-containers without a flex or width defined, as otherwise we
112                     //don't know how wide the sub-container should be and cannot calculate flexed widths
113                     if (!childHeight && canLayout) {
114                         child.doLayout();
115                     }
116
117                     childSize = child.getSize();
118                     childWidth = childSize.width;
119                     childHeight = childSize.height;
120                 }
121             }
122             
123             childMargins = child.margins;
124             vertMargins  = childMargins.top + childMargins.bottom;
125
126             nonFlexHeight += vertMargins + (childHeight || 0);
127             desiredHeight += vertMargins + (child.flex ? child.minHeight || 0 : childHeight);
128             minimumHeight += vertMargins + (child.minHeight || childHeight || 0);
129
130             // Max width for align - force layout of non-layed out subcontainers without a numeric width
131             if (typeof childWidth != 'number') {
132                 if (canLayout) {
133                     child.doLayout();
134                 }
135                 childWidth = child.getWidth();
136             }
137
138             maxWidth = Math.max(maxWidth, childWidth + childMargins.left + childMargins.right);
139
140             //cache the size of each child component
141             boxes.push({
142                 component: child,
143                 height   : childHeight || undefined,
144                 width    : childWidth || undefined
145             });
146         }
147                 
148         var shortfall = desiredHeight - height,
149             tooNarrow = minimumHeight > height;
150
151         //the height available to the flexed items
152         var availableHeight = Math.max(0, (height - nonFlexHeight - paddingVert));
153         
154         if (tooNarrow) {
155             for (i = 0, length = visibleCount; i < length; i++) {
156                 boxes[i].height = visibleItems[i].minHeight || visibleItems[i].height || boxes[i].height;
157             }
158         } else {
159             //all flexed items should be sized to their minimum width, other items should be shrunk down until
160             //the shortfall has been accounted for
161             if (shortfall > 0) {
162                 var minHeights = [];
163
164                 /**
165                  * When we have a shortfall but are not tooNarrow, we need to shrink the height of each non-flexed item.
166                  * Flexed items are immediately reduced to their minHeight and anything already at minHeight is ignored.
167                  * The remaining items are collected into the minHeights array, which is later used to distribute the shortfall.
168                  */
169                 for (var index = 0, length = visibleCount; index < length; index++) {
170                     var item      = visibleItems[index],
171                         minHeight = item.minHeight || 0;
172
173                     //shrink each non-flex tab by an equal amount to make them all fit. Flexed items are all
174                     //shrunk to their minHeight because they're flexible and should be the first to lose height
175                     if (item.flex) {
176                         boxes[index].height = minHeight;
177                     } else {
178                         minHeights.push({
179                             minHeight: minHeight, 
180                             available: boxes[index].height - minHeight,
181                             index    : index
182                         });
183                     }
184                 }
185
186                 //sort by descending minHeight value
187                 minHeights.sort(function(a, b) {
188                     return a.available > b.available ? 1 : -1;
189                 });
190
191                 /*
192                  * Distribute the shortfall (difference between total desired with of all items and actual height available)
193                  * between the non-flexed items. We try to distribute the shortfall evenly, but apply it to items with the
194                  * smallest difference between their height and minHeight first, so that if reducing the height by the average
195                  * amount would make that item less than its minHeight, we carry the remainder over to the next item.
196                  */
197                 for (var i = 0, length = minHeights.length; i < length; i++) {
198                     var itemIndex = minHeights[i].index;
199
200                     if (itemIndex == undefined) {
201                         continue;
202                     }
203
204                     var item      = visibleItems[itemIndex],
205                         box       = boxes[itemIndex],
206                         oldHeight  = box.height,
207                         minHeight  = item.minHeight,
208                         newHeight  = Math.max(minHeight, oldHeight - Math.ceil(shortfall / (length - i))),
209                         reduction = oldHeight - newHeight;
210
211                     boxes[itemIndex].height = newHeight;
212                     shortfall -= reduction;
213                 }
214             } else {
215                 //temporary variables used in the flex height calculations below
216                 var remainingHeight = availableHeight,
217                     remainingFlex   = totalFlex;
218                 
219                 //calculate the height of each flexed item
220                 for (i = 0; i < visibleCount; i++) {
221                     child = visibleItems[i];
222                     calcs = boxes[i];
223
224                     childMargins = child.margins;
225                     horizMargins = childMargins.left + childMargins.right;
226
227                     if (isStart && child.flex && !child.height) {
228                         flexedHeight     = Math.ceil((child.flex / remainingFlex) * remainingHeight);
229                         remainingHeight -= flexedHeight;
230                         remainingFlex   -= child.flex;
231
232                         calcs.height = flexedHeight;
233                         calcs.dirtySize = true;
234                     }
235                 }
236             }
237         }
238
239         if (isCenter) {
240             topOffset += availableHeight / 2;
241         } else if (isEnd) {
242             topOffset += availableHeight;
243         }
244
245         //finally, calculate the left and top position of each item
246         for (i = 0; i < visibleCount; i++) {
247             child = visibleItems[i];
248             calcs = boxes[i];
249
250             childMargins = child.margins;
251             topOffset   += childMargins.top;
252             horizMargins = childMargins.left + childMargins.right;
253             
254
255             calcs.left = leftOffset + childMargins.left;
256             calcs.top  = topOffset;
257             
258             switch (this.align) {
259                 case 'stretch':
260                     stretchWidth = availWidth - horizMargins;
261                     calcs.width  = stretchWidth.constrain(child.minWidth || 0, child.maxWidth || 1000000);
262                     calcs.dirtySize = true;
263                     break;
264                 case 'stretchmax':
265                     stretchWidth = maxWidth - horizMargins;
266                     calcs.width  = stretchWidth.constrain(child.minWidth || 0, child.maxWidth || 1000000);
267                     calcs.dirtySize = true;
268                     break;
269                 case 'center':
270                     var diff = availWidth - calcs.width - horizMargins;
271                     if (diff > 0) {
272                         calcs.left = leftOffset + horizMargins + (diff / 2);
273                     }
274             }
275
276             topOffset += calcs.height + childMargins.bottom;
277         }
278         
279         return {
280             boxes: boxes,
281             meta : {
282                 maxWidth     : maxWidth,
283                 nonFlexHeight: nonFlexHeight,
284                 desiredHeight: desiredHeight,
285                 minimumHeight: minimumHeight,
286                 shortfall    : desiredHeight - height,
287                 tooNarrow    : tooNarrow
288             }
289         };
290     }
291 });
292
293 Ext.Container.LAYOUTS.vbox = Ext.layout.VBoxLayout;