Upgrade to ExtJS 3.1.1 - Released 02/08/2010
[extjs.git] / src / widgets / layout / BoxLayout.js
1 /*!
2  * Ext JS Library 3.1.1
3  * Copyright(c) 2006-2010 Ext JS, LLC
4  * licensing@extjs.com
5  * http://www.extjs.com/license
6  */
7 /**
8  * @class Ext.layout.BoxLayout
9  * @extends Ext.layout.ContainerLayout
10  * <p>Base Class for HBoxLayout and VBoxLayout Classes. Generally it should not need to be used directly.</p>
11  */
12 Ext.layout.BoxLayout = Ext.extend(Ext.layout.ContainerLayout, {
13     /**
14      * @cfg {Object} defaultMargins
15      * <p>If the individual contained items do not have a <tt>margins</tt>
16      * property specified, the default margins from this property will be
17      * applied to each item.</p>
18      * <br><p>This property may be specified as an object containing margins
19      * to apply in the format:</p><pre><code>
20 {
21     top: (top margin),
22     right: (right margin),
23     bottom: (bottom margin),
24     left: (left margin)
25 }</code></pre>
26      * <p>This property may also be specified as a string containing
27      * space-separated, numeric margin values. The order of the sides associated
28      * with each value matches the way CSS processes margin values:</p>
29      * <div class="mdetail-params"><ul>
30      * <li>If there is only one value, it applies to all sides.</li>
31      * <li>If there are two values, the top and bottom borders are set to the
32      * first value and the right and left are set to the second.</li>
33      * <li>If there are three values, the top is set to the first value, the left
34      * and right are set to the second, and the bottom is set to the third.</li>
35      * <li>If there are four values, they apply to the top, right, bottom, and
36      * left, respectively.</li>
37      * </ul></div>
38      * <p>Defaults to:</p><pre><code>
39      * {top:0, right:0, bottom:0, left:0}
40      * </code></pre>
41      */
42     defaultMargins : {left:0,top:0,right:0,bottom:0},
43     /**
44      * @cfg {String} padding
45      * <p>Sets the padding to be applied to all child items managed by this layout.</p>
46      * <p>This property must be specified as a string containing
47      * space-separated, numeric padding values. The order of the sides associated
48      * with each value matches the way CSS processes padding values:</p>
49      * <div class="mdetail-params"><ul>
50      * <li>If there is only one value, it applies to all sides.</li>
51      * <li>If there are two values, the top and bottom borders are set to the
52      * first value and the right and left are set to the second.</li>
53      * <li>If there are three values, the top is set to the first value, the left
54      * and right are set to the second, and the bottom is set to the third.</li>
55      * <li>If there are four values, they apply to the top, right, bottom, and
56      * left, respectively.</li>
57      * </ul></div>
58      * <p>Defaults to: <code>"0"</code></p>
59      */
60     padding : '0',
61     // documented in subclasses
62     pack : 'start',
63
64     // private
65     monitorResize : true,
66     type: 'box',
67     scrollOffset : 0,
68     extraCls : 'x-box-item',
69     targetCls : 'x-box-layout-ct',
70     innerCls : 'x-box-inner',
71
72     constructor : function(config){
73         Ext.layout.BoxLayout.superclass.constructor.call(this, config);
74         if(Ext.isString(this.defaultMargins)){
75             this.defaultMargins = this.parseMargins(this.defaultMargins);
76         }
77     },
78
79     // private
80     isValidParent : function(c, target){
81         return this.innerCt && c.getPositionEl().dom.parentNode == this.innerCt.dom;
82     },
83
84     // private
85     renderAll : function(ct, target){
86         if(!this.innerCt){
87             // the innerCt prevents wrapping and shuffling while
88             // the container is resizing
89             this.innerCt = target.createChild({cls:this.innerCls});
90             this.padding = this.parseMargins(this.padding);
91         }
92         Ext.layout.BoxLayout.superclass.renderAll.call(this, ct, this.innerCt);
93     },
94
95     onLayout : function(ct, target){
96         this.renderAll(ct, target);
97     },
98
99     getLayoutTargetSize : function(){
100         var target = this.container.getLayoutTarget(), ret;
101         if (target) {
102             ret = target.getViewSize();
103             ret.width -= target.getPadding('lr');
104             ret.height -= target.getPadding('tb');
105         }
106         return ret;
107     },
108
109     // private
110     renderItem : function(c){
111         if(Ext.isString(c.margins)){
112             c.margins = this.parseMargins(c.margins);
113         }else if(!c.margins){
114             c.margins = this.defaultMargins;
115         }
116         Ext.layout.BoxLayout.superclass.renderItem.apply(this, arguments);
117     }
118 });
119
120 /**
121  * @class Ext.layout.VBoxLayout
122  * @extends Ext.layout.BoxLayout
123  * <p>A layout that arranges items vertically down a Container. This layout optionally divides available vertical
124  * space between child items containing a numeric <code>flex</code> configuration.</p>
125  * This layout may also be used to set the widths of child items by configuring it with the {@link #align} option.
126  */
127 Ext.layout.VBoxLayout = Ext.extend(Ext.layout.BoxLayout, {
128     /**
129      * @cfg {String} align
130      * Controls how the child items of the container are aligned. Acceptable configuration values for this
131      * property are:
132      * <div class="mdetail-params"><ul>
133      * <li><b><tt>left</tt></b> : <b>Default</b><div class="sub-desc">child items are aligned horizontally
134      * at the <b>left</b> side of the container</div></li>
135      * <li><b><tt>center</tt></b> : <div class="sub-desc">child items are aligned horizontally at the
136      * <b>mid-width</b> of the container</div></li>
137      * <li><b><tt>stretch</tt></b> : <div class="sub-desc">child items are stretched horizontally to fill
138      * the width of the container</div></li>
139      * <li><b><tt>stretchmax</tt></b> : <div class="sub-desc">child items are stretched horizontally to
140      * the size of the largest item.</div></li>
141      * </ul></div>
142      */
143     align : 'left', // left, center, stretch, strechmax
144     type: 'vbox',
145     /**
146      * @cfg {String} pack
147      * Controls how the child items of the container are packed together. Acceptable configuration values
148      * for this property are:
149      * <div class="mdetail-params"><ul>
150      * <li><b><tt>start</tt></b> : <b>Default</b><div class="sub-desc">child items are packed together at
151      * <b>top</b> side of container</div></li>
152      * <li><b><tt>center</tt></b> : <div class="sub-desc">child items are packed together at
153      * <b>mid-height</b> of container</div></li>
154      * <li><b><tt>end</tt></b> : <div class="sub-desc">child items are packed together at <b>bottom</b>
155      * side of container</div></li>
156      * </ul></div>
157      */
158     /**
159      * @cfg {Number} flex
160      * This configuation option is to be applied to <b>child <tt>items</tt></b> of the container managed
161      * by this layout. Each child item with a <tt>flex</tt> property will be flexed <b>vertically</b>
162      * according to each item's <b>relative</b> <tt>flex</tt> value compared to the sum of all items with
163      * a <tt>flex</tt> value specified.  Any child items that have either a <tt>flex = 0</tt> or
164      * <tt>flex = undefined</tt> will not be 'flexed' (the initial size will not be changed).
165      */
166
167     // private
168     onLayout : function(ct, target){
169         Ext.layout.VBoxLayout.superclass.onLayout.call(this, ct, target);
170
171         var cs = this.getRenderedItems(ct), csLen = cs.length,
172             c, i, cm, ch, margin, cl, diff, aw, availHeight,
173             size = this.getLayoutTargetSize(),
174             w = size.width,
175             h = size.height - this.scrollOffset,
176             l = this.padding.left,
177             t = this.padding.top,
178             isStart = this.pack == 'start',
179             extraHeight = 0,
180             maxWidth = 0,
181             totalFlex = 0,
182             usedHeight = 0,
183             idx = 0,
184             heights = [],
185             restore = [];
186
187         // Do only width calculations and apply those first, as they can affect height
188         for (i = 0 ; i < csLen; i++) {
189             c = cs[i];
190             cm = c.margins;
191             margin = cm.top + cm.bottom;
192             // Max height for align
193             maxWidth = Math.max(maxWidth, c.getWidth() + cm.left + cm.right);
194         }
195
196         var innerCtWidth = maxWidth + this.padding.left + this.padding.right;
197         switch(this.align){
198             case 'stretch':
199                 this.innerCt.setSize(w, h);
200                 break;
201             case 'stretchmax':
202             case 'left':
203                 this.innerCt.setSize(innerCtWidth, h);
204                 break;
205             case 'center':
206                 this.innerCt.setSize(w = Math.max(w, innerCtWidth), h);
207                 break;
208         }
209
210         var availableWidth = Math.max(0, w - this.padding.left - this.padding.right);
211         // Apply widths
212         for (i = 0 ; i < csLen; i++) {
213             c = cs[i];
214             cm = c.margins;
215             if(this.align == 'stretch'){
216                 c.setWidth(((w - (this.padding.left + this.padding.right)) - (cm.left + cm.right)).constrain(
217                     c.minWidth || 0, c.maxWidth || 1000000));
218             }else if(this.align == 'stretchmax'){
219                 c.setWidth((maxWidth - (cm.left + cm.right)).constrain(
220                     c.minWidth || 0, c.maxWidth || 1000000));
221             }else if(isStart && c.flex){
222                 c.setWidth();
223             }
224
225         }
226
227         // Height calculations
228         for (i = 0 ; i < csLen; i++) {
229             c = cs[i];
230             // Total of all the flex values
231             totalFlex += c.flex || 0;
232             // Don't run height calculations on flexed items
233             if (!c.flex) {
234                 // Render and layout sub-containers without a flex or height, once
235                 if (!c.height && !c.hasLayout && c.doLayout) {
236                     c.doLayout();
237                 }
238                 ch = c.getHeight();
239             } else {
240                 ch = 0;
241             }
242
243             cm = c.margins;
244             // Determine how much height is available to flex
245             extraHeight += ch + cm.top + cm.bottom;
246         }
247         // Final avail height calc
248         availHeight = Math.max(0, (h - extraHeight - this.padding.top - this.padding.bottom));
249
250         var leftOver = availHeight;
251         for (i = 0 ; i < csLen; i++) {
252             c = cs[i];
253             if(isStart && c.flex){
254                 ch = Math.floor(availHeight * (c.flex / totalFlex));
255                 leftOver -= ch;
256                 heights.push(ch);
257             }
258         }
259         if(this.pack == 'center'){
260             t += availHeight ? availHeight / 2 : 0;
261         }else if(this.pack == 'end'){
262             t += availHeight;
263         }
264         idx = 0;
265         // Apply heights
266         for (i = 0 ; i < csLen; i++) {
267             c = cs[i];
268             cm = c.margins;
269             t += cm.top;
270             aw = availableWidth;
271             cl = l + cm.left // default left pos
272
273             // Adjust left pos for centering
274             if(this.align == 'center'){
275                 if((diff = availableWidth - (c.getWidth() + cm.left + cm.right)) > 0){
276                     cl += (diff/2);
277                     aw -= diff;
278                 }
279             }
280
281             c.setPosition(cl, t);
282             if(isStart && c.flex){
283                 ch = Math.max(0, heights[idx++] + (leftOver-- > 0 ? 1 : 0));
284                 c.setSize(aw, ch);
285             }else{
286                 ch = c.getHeight();
287             }
288             t += ch + cm.bottom;
289         }
290         // Putting a box layout into an overflowed container is NOT correct and will make a second layout pass necessary.
291         if (i = target.getStyle('overflow') && i != 'hidden' && !this.adjustmentPass) {
292             var ts = this.getLayoutTargetSize();
293             if (ts.width != size.width || ts.height != size.height){
294                 this.adjustmentPass = true;
295                 this.onLayout(ct, target);
296             }
297         }
298         delete this.adjustmentPass;
299     }
300 });
301
302 Ext.Container.LAYOUTS.vbox = Ext.layout.VBoxLayout;
303
304 /**
305  * @class Ext.layout.HBoxLayout
306  * @extends Ext.layout.BoxLayout
307  * <p>A layout that arranges items horizontally across a Container. This layout optionally divides available horizontal
308  * space between child items containing a numeric <code>flex</code> configuration.</p>
309  * This layout may also be used to set the heights of child items by configuring it with the {@link #align} option.
310  */
311 Ext.layout.HBoxLayout = Ext.extend(Ext.layout.BoxLayout, {
312     /**
313      * @cfg {String} align
314      * Controls how the child items of the container are aligned. Acceptable configuration values for this
315      * property are:
316      * <div class="mdetail-params"><ul>
317      * <li><b><tt>top</tt></b> : <b>Default</b><div class="sub-desc">child items are aligned vertically
318      * at the <b>top</b> of the container</div></li>
319      * <li><b><tt>middle</tt></b> : <div class="sub-desc">child items are aligned vertically in the
320      * <b>middle</b> of the container</div></li>
321      * <li><b><tt>stretch</tt></b> : <div class="sub-desc">child items are stretched vertically to fill
322      * the height of the container</div></li>
323      * <li><b><tt>stretchmax</tt></b> : <div class="sub-desc">child items are stretched vertically to
324      * the height of the largest item.</div></li>
325      */
326     align : 'top', // top, middle, stretch, strechmax
327     type: 'hbox',
328     /**
329      * @cfg {String} pack
330      * Controls how the child items of the container are packed together. Acceptable configuration values
331      * for this property are:
332      * <div class="mdetail-params"><ul>
333      * <li><b><tt>start</tt></b> : <b>Default</b><div class="sub-desc">child items are packed together at
334      * <b>left</b> side of container</div></li>
335      * <li><b><tt>center</tt></b> : <div class="sub-desc">child items are packed together at
336      * <b>mid-width</b> of container</div></li>
337      * <li><b><tt>end</tt></b> : <div class="sub-desc">child items are packed together at <b>right</b>
338      * side of container</div></li>
339      * </ul></div>
340      */
341     /**
342      * @cfg {Number} flex
343      * This configuation option is to be applied to <b>child <tt>items</tt></b> of the container managed
344      * by this layout. Each child item with a <tt>flex</tt> property will be flexed <b>horizontally</b>
345      * according to each item's <b>relative</b> <tt>flex</tt> value compared to the sum of all items with
346      * a <tt>flex</tt> value specified.  Any child items that have either a <tt>flex = 0</tt> or
347      * <tt>flex = undefined</tt> will not be 'flexed' (the initial size will not be changed).
348      */
349
350     // private
351     onLayout : function(ct, target){
352         Ext.layout.HBoxLayout.superclass.onLayout.call(this, ct, target);
353
354         var cs = this.getRenderedItems(ct), csLen = cs.length,
355             c, i, cm, cw, ch, diff, availWidth,
356             size = this.getLayoutTargetSize(),
357             w = size.width - this.scrollOffset,
358             h = size.height,
359             l = this.padding.left,
360             t = this.padding.top,
361             isStart = this.pack == 'start',
362             isRestore = ['stretch', 'stretchmax'].indexOf(this.align) == -1,
363             extraWidth = 0,
364             maxHeight = 0,
365             totalFlex = 0,
366             usedWidth = 0;
367
368         for (i = 0 ; i < csLen; i++) {
369             c = cs[i];
370             // Total of all the flex values
371             totalFlex += c.flex || 0;
372             // Don't run width calculations on flexed items
373             if (!c.flex) {
374                 // Render and layout sub-containers without a flex or width, once
375                 if (!c.width && !c.hasLayout && c.doLayout) {
376                     c.doLayout();
377                 }
378                 cw = c.getWidth();
379             } else {
380                 cw = 0;
381             }
382             cm = c.margins;
383             // Determine how much width is available to flex
384             extraWidth += cw + cm.left + cm.right;
385             // Max height for align
386             maxHeight = Math.max(maxHeight, c.getHeight() + cm.top + cm.bottom);
387         }
388         // Final avail width calc
389         availWidth = Math.max(0, (w - extraWidth - this.padding.left - this.padding.right));
390
391         var innerCtHeight = maxHeight + this.padding.top + this.padding.bottom;
392         switch(this.align){
393             case 'stretch':
394                 this.innerCt.setSize(w, h);
395                 break;
396             case 'stretchmax':
397             case 'top':
398                 this.innerCt.setSize(w, innerCtHeight);
399                 break;
400             case 'middle':
401                 this.innerCt.setSize(w, h = Math.max(h, innerCtHeight));
402                 break;
403         }
404
405         var leftOver = availWidth,
406             widths = [],
407             restore = [],
408             idx = 0,
409             availableHeight = Math.max(0, h - this.padding.top - this.padding.bottom);
410
411         for (i = 0 ; i < csLen; i++) {
412             c = cs[i];
413             if(isStart && c.flex){
414                 cw = Math.floor(availWidth * (c.flex / totalFlex));
415                 leftOver -= cw;
416                 widths.push(cw);
417             }
418         }
419
420         if(this.pack == 'center'){
421             l += availWidth ? availWidth / 2 : 0;
422         }else if(this.pack == 'end'){
423             l += availWidth;
424         }
425         for (i = 0 ; i < csLen; i++) {
426             c = cs[i];
427             cm = c.margins;
428             l += cm.left;
429             c.setPosition(l, t + cm.top);
430             if(isStart && c.flex){
431                 cw = Math.max(0, widths[idx++] + (leftOver-- > 0 ? 1 : 0));
432                 if(isRestore){
433                     restore.push(c.getHeight());
434                 }
435                 c.setSize(cw, availableHeight);
436             }else{
437                 cw = c.getWidth();
438             }
439             l += cw + cm.right;
440         }
441
442         idx = 0;
443         for (i = 0 ; i < csLen; i++) {
444             c = cs[i];
445             cm = c.margins;
446             ch = c.getHeight();
447             if(isStart && c.flex){
448                 ch = restore[idx++];
449             }
450             if(this.align == 'stretch'){
451                 c.setHeight(((h - (this.padding.top + this.padding.bottom)) - (cm.top + cm.bottom)).constrain(
452                     c.minHeight || 0, c.maxHeight || 1000000));
453             }else if(this.align == 'stretchmax'){
454                 c.setHeight((maxHeight - (cm.top + cm.bottom)).constrain(
455                     c.minHeight || 0, c.maxHeight || 1000000));
456             }else{
457                 if(this.align == 'middle'){
458                     diff = availableHeight - (ch + cm.top + cm.bottom);
459                     ch = t + cm.top + (diff/2);
460                     if(diff > 0){
461                         c.setPosition(c.x, ch);
462                     }
463                 }
464                 if(isStart && c.flex){
465                     c.setHeight(ch);
466                 }
467             }
468         }
469         // Putting a box layout into an overflowed container is NOT correct and will make a second layout pass necessary.
470         if (i = target.getStyle('overflow') && i != 'hidden' && !this.adjustmentPass) {
471             var ts = this.getLayoutTargetSize();
472             if (ts.width != size.width || ts.height != size.height){
473                 this.adjustmentPass = true;
474                 this.onLayout(ct, target);
475             }
476         }
477         delete this.adjustmentPass;
478     }
479 });
480
481 Ext.Container.LAYOUTS.hbox = Ext.layout.HBoxLayout;