Upgrade to ExtJS 3.1.0 - Released 12/16/2009
[extjs.git] / src / widgets / layout / BoxLayout.js
1 /*!
2  * Ext JS Library 3.1.0
3  * Copyright(c) 2006-2009 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     scrollOffset : 0,
67     extraCls : 'x-box-item',
68     targetCls : 'x-box-layout-ct',
69     innerCls : 'x-box-inner',
70
71     constructor : function(config){
72         Ext.layout.BoxLayout.superclass.constructor.call(this, config);
73         if(Ext.isString(this.defaultMargins)){
74             this.defaultMargins = this.parseMargins(this.defaultMargins);
75         }
76     },
77
78     // private
79     isValidParent : function(c, target){
80         return c.getPositionEl().dom.parentNode == this.innerCt.dom;
81     },
82
83     // private
84     onLayout : function(ct, target){
85         var cs = ct.items.items, len = cs.length, c, i, last = len-1, cm;
86
87         if(!this.innerCt){
88             // the innerCt prevents wrapping and shuffling while
89             // the container is resizing
90             this.innerCt = target.createChild({cls:this.innerCls});
91             this.padding = this.parseMargins(this.padding);
92         }
93         this.renderAll(ct, this.innerCt);
94     },
95
96     // private
97     renderItem : function(c){
98         if(Ext.isString(c.margins)){
99             c.margins = this.parseMargins(c.margins);
100         }else if(!c.margins){
101             c.margins = this.defaultMargins;
102         }
103         Ext.layout.BoxLayout.superclass.renderItem.apply(this, arguments);
104     },
105
106     // deprecate
107     getTargetSize : function(target){
108         return (Ext.isIE6 && Ext.isStrict && target.dom == document.body) ? target.getStyleSize() : target.getViewSize(true);
109     },
110
111     getItems: function(ct){
112         var items = [];
113         ct.items.each(function(c){
114             if(c.isVisible()){
115                 items.push(c);
116             }
117         });
118         return items;
119     }
120 });
121
122 /**
123  * @class Ext.layout.VBoxLayout
124  * @extends Ext.layout.BoxLayout
125  * <p>A layout that arranges items vertically down a Container. This layout optionally divides available vertical
126  * space between child items containing a numeric <code>flex</code> configuration.</p>
127  * This layout may also be used to set the widths of child items by configuring it with the {@link #align} option.
128  */
129 Ext.layout.VBoxLayout = Ext.extend(Ext.layout.BoxLayout, {
130     /**
131      * @cfg {String} align
132      * Controls how the child items of the container are aligned. Acceptable configuration values for this
133      * property are:
134      * <div class="mdetail-params"><ul>
135      * <li><b><tt>left</tt></b> : <b>Default</b><div class="sub-desc">child items are aligned horizontally
136      * at the <b>left</b> side of the container</div></li>
137      * <li><b><tt>center</tt></b> : <div class="sub-desc">child items are aligned horizontally at the
138      * <b>mid-width</b> of the container</div></li>
139      * <li><b><tt>stretch</tt></b> : <div class="sub-desc">child items are stretched horizontally to fill
140      * the width of the container</div></li>
141      * <li><b><tt>stretchmax</tt></b> : <div class="sub-desc">child items are stretched horizontally to
142      * the size of the largest item.</div></li>
143      * </ul></div>
144      */
145     align : 'left', // left, center, stretch, strechmax
146     /**
147      * @cfg {String} pack
148      * Controls how the child items of the container are packed together. Acceptable configuration values
149      * for this property are:
150      * <div class="mdetail-params"><ul>
151      * <li><b><tt>start</tt></b> : <b>Default</b><div class="sub-desc">child items are packed together at
152      * <b>top</b> side of container</div></li>
153      * <li><b><tt>center</tt></b> : <div class="sub-desc">child items are packed together at
154      * <b>mid-height</b> of container</div></li>
155      * <li><b><tt>end</tt></b> : <div class="sub-desc">child items are packed together at <b>bottom</b>
156      * side of container</div></li>
157      * </ul></div>
158      */
159     /**
160      * @cfg {Number} flex
161      * This configuation option is to be applied to <b>child <tt>items</tt></b> of the container managed
162      * by this layout. Each child item with a <tt>flex</tt> property will be flexed <b>vertically</b>
163      * according to each item's <b>relative</b> <tt>flex</tt> value compared to the sum of all items with
164      * a <tt>flex</tt> value specified.  Any child items that have either a <tt>flex = 0</tt> or
165      * <tt>flex = undefined</tt> will not be 'flexed' (the initial size will not be changed).
166      */
167
168     // private
169     onLayout : function(ct, target){
170         Ext.layout.VBoxLayout.superclass.onLayout.call(this, ct, target);
171
172         var cs = this.getItems(ct), cm, ch, margin, cl, diff, aw,
173             size = target.getViewSize(true),
174             w = size.width,
175             h = size.height - this.scrollOffset,
176             l = this.padding.left, t = this.padding.top,
177             isStart = this.pack == 'start',
178             stretchWidth = w - (this.padding.left + this.padding.right),
179             extraHeight = 0,
180             maxWidth = 0,
181             totalFlex = 0,
182             flexHeight = 0,
183             usedHeight = 0,
184             idx = 0,
185             heights = [],
186             restore = [],
187             c,
188             csLen = cs.length;
189
190         // Do only width calculations and apply those first, as they can affect height
191         for (i = 0 ; i < csLen; i++) {
192             c = cs[i];
193             cm = c.margins;
194             margin = cm.top + cm.bottom;
195             maxWidth = Math.max(maxWidth, c.getWidth() + cm.left + cm.right);
196         }
197
198         var innerCtWidth = maxWidth + this.padding.left + this.padding.right;
199         switch(this.align){
200             case 'stretch':
201                 this.innerCt.setSize(w, h);
202                 break;
203             case 'stretchmax':
204             case 'left':
205                 this.innerCt.setSize(innerCtWidth, h);
206                 break;
207             case 'center':
208                 this.innerCt.setSize(w = Math.max(w, innerCtWidth), h);
209                 break;
210         }
211
212         var availableWidth = Math.max(0, w - this.padding.left - this.padding.right);
213         // Apply widths
214         for (i = 0 ; i < csLen; i++) {
215             c = cs[i];
216             cm = c.margins;
217             if(this.align == 'stretch'){
218                 c.setWidth((stretchWidth - (cm.left + cm.right)).constrain(
219                     c.minWidth || 0, c.maxWidth || 1000000));
220             }else if(this.align == 'stretchmax'){
221                 c.setWidth((maxWidth - (cm.left + cm.right)).constrain(
222                     c.minWidth || 0, c.maxWidth || 1000000));
223             }else if(isStart && c.flex){
224                 c.setWidth();
225             }
226
227         }
228
229         // Do height calculations
230         for (i = 0 ; i < csLen; i++) {
231             c = cs[i];
232             cm = c.margins;
233             totalFlex += c.flex || 0;
234             ch = c.getHeight();
235             margin = cm.top + cm.bottom;
236             extraHeight += ch + margin;
237             flexHeight += margin + (c.flex ? 0 : ch);
238         }
239         extraHeight = h - extraHeight - this.padding.top - this.padding.bottom;
240
241         var availHeight = Math.max(0, h - this.padding.top - this.padding.bottom - flexHeight),
242             leftOver = availHeight;
243         for (i = 0 ; i < csLen; i++) {
244             c = cs[i];
245             if(isStart && c.flex){
246                 ch = Math.floor(availHeight * (c.flex / totalFlex));
247                 leftOver -= ch;
248                 heights.push(ch);
249             }
250         }
251         if(this.pack == 'center'){
252             t += extraHeight ? extraHeight / 2 : 0;
253         }else if(this.pack == 'end'){
254             t += extraHeight;
255         }
256         idx = 0;
257         // Apply heights
258         for (i = 0 ; i < csLen; i++) {
259             c = cs[i];
260             cm = c.margins;
261             t += cm.top;
262             aw = availableWidth;
263             cl = l + cm.left // default left pos
264
265 //          Adjust left pos for centering
266             if(this.align == 'center'){
267                 if((diff = availableWidth - (c.getWidth() + cm.left + cm.right)) > 0){
268                     cl += (diff/2);
269                     aw -= diff;
270                 }
271             }
272
273             c.setPosition(cl, t);
274             if(isStart && c.flex){
275                 ch = Math.max(0, heights[idx++] + (leftOver-- > 0 ? 1 : 0));
276                 c.setSize(aw, ch);
277             }else{
278                 ch = c.getHeight();
279             }
280             t += ch + cm.bottom;
281         }
282     }
283 });
284
285 Ext.Container.LAYOUTS.vbox = Ext.layout.VBoxLayout;
286
287 /**
288  * @class Ext.layout.HBoxLayout
289  * @extends Ext.layout.BoxLayout
290  * <p>A layout that arranges items horizontally across a Container. This layout optionally divides available horizontal
291  * space between child items containing a numeric <code>flex</code> configuration.</p>
292  * This layout may also be used to set the heights of child items by configuring it with the {@link #align} option.
293  */
294 Ext.layout.HBoxLayout = Ext.extend(Ext.layout.BoxLayout, {
295     /**
296      * @cfg {String} align
297      * Controls how the child items of the container are aligned. Acceptable configuration values for this
298      * property are:
299      * <div class="mdetail-params"><ul>
300      * <li><b><tt>top</tt></b> : <b>Default</b><div class="sub-desc">child items are aligned vertically
301      * at the <b>top</b> of the container</div></li>
302      * <li><b><tt>middle</tt></b> : <div class="sub-desc">child items are aligned vertically in the
303      * <b>middle</b> of the container</div></li>
304      * <li><b><tt>stretch</tt></b> : <div class="sub-desc">child items are stretched vertically to fill
305      * the height of the container</div></li>
306      * <li><b><tt>stretchmax</tt></b> : <div class="sub-desc">child items are stretched vertically to
307      * the height of the largest item.</div></li>
308      */
309     align : 'top', // top, middle, stretch, strechmax
310     /**
311      * @cfg {String} pack
312      * Controls how the child items of the container are packed together. Acceptable configuration values
313      * for this property are:
314      * <div class="mdetail-params"><ul>
315      * <li><b><tt>start</tt></b> : <b>Default</b><div class="sub-desc">child items are packed together at
316      * <b>left</b> side of container</div></li>
317      * <li><b><tt>center</tt></b> : <div class="sub-desc">child items are packed together at
318      * <b>mid-width</b> of container</div></li>
319      * <li><b><tt>end</tt></b> : <div class="sub-desc">child items are packed together at <b>right</b>
320      * side of container</div></li>
321      * </ul></div>
322      */
323     /**
324      * @cfg {Number} flex
325      * This configuation option is to be applied to <b>child <tt>items</tt></b> of the container managed
326      * by this layout. Each child item with a <tt>flex</tt> property will be flexed <b>horizontally</b>
327      * according to each item's <b>relative</b> <tt>flex</tt> value compared to the sum of all items with
328      * a <tt>flex</tt> value specified.  Any child items that have either a <tt>flex = 0</tt> or
329      * <tt>flex = undefined</tt> will not be 'flexed' (the initial size will not be changed).
330      */
331
332     // private
333     onLayout : function(ct, target){
334         Ext.layout.HBoxLayout.superclass.onLayout.call(this, ct, target);
335
336         var cs = this.getItems(ct), cm, cw, margin, ch, diff,
337             size = target.getViewSize(true),
338             w = size.width - this.scrollOffset,
339             h = size.height,
340             l = this.padding.left, t = this.padding.top,
341             isStart = this.pack == 'start',
342             isRestore = ['stretch', 'stretchmax'].indexOf(this.align) == -1,
343             stretchHeight = h - (this.padding.top + this.padding.bottom),
344             extraWidth = 0,
345             maxHeight = 0,
346             totalFlex = 0,
347             flexWidth = 0,
348             usedWidth = 0;
349
350         Ext.each(cs, function(c){
351             cm = c.margins;
352             totalFlex += c.flex || 0;
353             cw = c.getWidth();
354             margin = cm.left + cm.right;
355             extraWidth += cw + margin;
356             flexWidth += margin + (c.flex ? 0 : cw);
357             maxHeight = Math.max(maxHeight, c.getHeight() + cm.top + cm.bottom);
358         });
359         extraWidth = w - extraWidth - this.padding.left - this.padding.right;
360
361         var innerCtHeight = maxHeight + this.padding.top + this.padding.bottom;
362         switch(this.align){
363             case 'stretch':
364                 this.innerCt.setSize(w, h);
365                 break;
366             case 'stretchmax':
367             case 'top':
368                 this.innerCt.setSize(w, innerCtHeight);
369                 break;
370             case 'middle':
371                 this.innerCt.setSize(w, h = Math.max(h, innerCtHeight));
372                 break;
373         }
374
375
376         var availWidth = Math.max(0, w - this.padding.left - this.padding.right - flexWidth),
377             leftOver = availWidth,
378             widths = [],
379             restore = [],
380             idx = 0,
381             availableHeight = Math.max(0, h - this.padding.top - this.padding.bottom);
382
383
384         Ext.each(cs, function(c){
385             if(isStart && c.flex){
386                 cw = Math.floor(availWidth * (c.flex / totalFlex));
387                 leftOver -= cw;
388                 widths.push(cw);
389             }
390         });
391
392         if(this.pack == 'center'){
393             l += extraWidth ? extraWidth / 2 : 0;
394         }else if(this.pack == 'end'){
395             l += extraWidth;
396         }
397         Ext.each(cs, function(c){
398             cm = c.margins;
399             l += cm.left;
400             c.setPosition(l, t + cm.top);
401             if(isStart && c.flex){
402                 cw = Math.max(0, widths[idx++] + (leftOver-- > 0 ? 1 : 0));
403                 if(isRestore){
404                     restore.push(c.getHeight());
405                 }
406                 c.setSize(cw, availableHeight);
407             }else{
408                 cw = c.getWidth();
409             }
410             l += cw + cm.right;
411         });
412
413         idx = 0;
414         Ext.each(cs, function(c){
415             cm = c.margins;
416             ch = c.getHeight();
417             if(isStart && c.flex){
418                 ch = restore[idx++];
419             }
420             if(this.align == 'stretch'){
421                 c.setHeight((stretchHeight - (cm.top + cm.bottom)).constrain(
422                     c.minHeight || 0, c.maxHeight || 1000000));
423             }else if(this.align == 'stretchmax'){
424                 c.setHeight((maxHeight - (cm.top + cm.bottom)).constrain(
425                     c.minHeight || 0, c.maxHeight || 1000000));
426             }else{
427                 if(this.align == 'middle'){
428                     diff = availableHeight - (ch + cm.top + cm.bottom);
429                     ch = t + cm.top + (diff/2);
430                     if(diff > 0){
431                         c.setPosition(c.x, ch);
432                     }
433                 }
434                 if(isStart && c.flex){
435                     c.setHeight(ch);
436                 }
437             }
438         }, this);
439     }
440 });
441
442 Ext.Container.LAYOUTS.hbox = Ext.layout.HBoxLayout;