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