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