Upgrade to ExtJS 3.3.1 - Released 11/30/2010
[extjs.git] / src / widgets / layout / BoxLayout.js
1 /*!
2  * Ext JS Library 3.3.1
3  * Copyright(c) 2006-2010 Sencha Inc.
4  * licensing@sencha.com
5  * http://www.sencha.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
75         if (Ext.isString(this.defaultMargins)) {
76             this.defaultMargins = this.parseMargins(this.defaultMargins);
77         }
78         
79         var handler = this.overflowHandler;
80         
81         if (typeof handler == 'string') {
82             handler = {
83                 type: handler
84             };
85         }
86         
87         var handlerType = 'none';
88         if (handler && handler.type != undefined) {
89             handlerType = handler.type;
90         }
91         
92         var constructor = Ext.layout.boxOverflow[handlerType];
93         if (constructor[this.type]) {
94             constructor = constructor[this.type];
95         }
96         
97         this.overflowHandler = new constructor(this, handler);
98     },
99
100     /**
101      * @private
102      * Runs the child box calculations and caches them in childBoxCache. Subclasses can used these cached values
103      * when laying out
104      */
105     onLayout: function(container, target) {
106         Ext.layout.BoxLayout.superclass.onLayout.call(this, container, target);
107
108         var tSize = this.getLayoutTargetSize(),
109             items = this.getVisibleItems(container),
110             calcs = this.calculateChildBoxes(items, tSize),
111             boxes = calcs.boxes,
112             meta  = calcs.meta;
113         
114         //invoke the overflow handler, if one is configured
115         if (tSize.width > 0) {
116             var handler = this.overflowHandler,
117                 method  = meta.tooNarrow ? 'handleOverflow' : 'clearOverflow';
118             
119             var results = handler[method](calcs, tSize);
120             
121             if (results) {
122                 if (results.targetSize) {
123                     tSize = results.targetSize;
124                 }
125                 
126                 if (results.recalculate) {
127                     items = this.getVisibleItems(container);
128                     calcs = this.calculateChildBoxes(items, tSize);
129                     boxes = calcs.boxes;
130                 }
131             }
132         }
133         
134         /**
135          * @private
136          * @property layoutTargetLastSize
137          * @type Object
138          * Private cache of the last measured size of the layout target. This should never be used except by
139          * BoxLayout subclasses during their onLayout run.
140          */
141         this.layoutTargetLastSize = tSize;
142         
143         /**
144          * @private
145          * @property childBoxCache
146          * @type Array
147          * Array of the last calculated height, width, top and left positions of each visible rendered component
148          * within the Box layout.
149          */
150         this.childBoxCache = calcs;
151         
152         this.updateInnerCtSize(tSize, calcs);
153         this.updateChildBoxes(boxes);
154
155         // Putting a box layout into an overflowed container is NOT correct and will make a second layout pass necessary.
156         this.handleTargetOverflow(tSize, container, target);
157     },
158
159     /**
160      * Resizes and repositions each child component
161      * @param {Array} boxes The box measurements
162      */
163     updateChildBoxes: function(boxes) {
164         for (var i = 0, length = boxes.length; i < length; i++) {
165             var box  = boxes[i],
166                 comp = box.component;
167             
168             if (box.dirtySize) {
169                 comp.setSize(box.width, box.height);
170             }
171             // Don't set positions to NaN
172             if (isNaN(box.left) || isNaN(box.top)) {
173                 continue;
174             }
175             
176             comp.setPosition(box.left, box.top);
177         }
178     },
179
180     /**
181      * @private
182      * Called by onRender just before the child components are sized and positioned. This resizes the innerCt
183      * to make sure all child items fit within it. We call this before sizing the children because if our child
184      * items are larger than the previous innerCt size the browser will insert scrollbars and then remove them
185      * again immediately afterwards, giving a performance hit.
186      * Subclasses should provide an implementation.
187      * @param {Object} currentSize The current height and width of the innerCt
188      * @param {Array} calculations The new box calculations of all items to be laid out
189      */
190     updateInnerCtSize: function(tSize, calcs) {
191         var align   = this.align,
192             padding = this.padding,
193             width   = tSize.width,
194             height  = tSize.height;
195         
196         if (this.type == 'hbox') {
197             var innerCtWidth  = width,
198                 innerCtHeight = calcs.meta.maxHeight + padding.top + padding.bottom;
199
200             if (align == 'stretch') {
201                 innerCtHeight = height;
202             } else if (align == 'middle') {
203                 innerCtHeight = Math.max(height, innerCtHeight);
204             }
205         } else {
206             var innerCtHeight = height,
207                 innerCtWidth  = calcs.meta.maxWidth + padding.left + padding.right;
208
209             if (align == 'stretch') {
210                 innerCtWidth = width;
211             } else if (align == 'center') {
212                 innerCtWidth = Math.max(width, innerCtWidth);
213             }
214         }
215
216         this.innerCt.setSize(innerCtWidth || undefined, innerCtHeight || undefined);
217     },
218
219     /**
220      * @private
221      * This should be called after onLayout of any BoxLayout subclass. If the target's overflow is not set to 'hidden',
222      * we need to lay out a second time because the scrollbars may have modified the height and width of the layout
223      * target. Having a Box layout inside such a target is therefore not recommended.
224      * @param {Object} previousTargetSize The size and height of the layout target before we just laid out
225      * @param {Ext.Container} container The container
226      * @param {Ext.Element} target The target element
227      */
228     handleTargetOverflow: function(previousTargetSize, container, target) {
229         var overflow = target.getStyle('overflow');
230
231         if (overflow && overflow != 'hidden' &&!this.adjustmentPass) {
232             var newTargetSize = this.getLayoutTargetSize();
233             if (newTargetSize.width != previousTargetSize.width || newTargetSize.height != previousTargetSize.height){
234                 this.adjustmentPass = true;
235                 this.onLayout(container, target);
236             }
237         }
238
239         delete this.adjustmentPass;
240     },
241
242     // private
243     isValidParent : function(c, target) {
244         return this.innerCt && c.getPositionEl().dom.parentNode == this.innerCt.dom;
245     },
246
247     /**
248      * @private
249      * Returns all items that are both rendered and visible
250      * @return {Array} All matching items
251      */
252     getVisibleItems: function(ct) {
253         var ct  = ct || this.container,
254             t   = ct.getLayoutTarget(),
255             cti = ct.items.items,
256             len = cti.length,
257
258             i, c, items = [];
259
260         for (i = 0; i < len; i++) {
261             if((c = cti[i]).rendered && this.isValidParent(c, t) && c.hidden !== true  && c.collapsed !== true && c.shouldLayout !== false){
262                 items.push(c);
263             }
264         }
265
266         return items;
267     },
268
269     // private
270     renderAll : function(ct, target) {
271         if (!this.innerCt) {
272             // the innerCt prevents wrapping and shuffling while the container is resizing
273             this.innerCt = target.createChild({cls:this.innerCls});
274             this.padding = this.parseMargins(this.padding);
275         }
276         Ext.layout.BoxLayout.superclass.renderAll.call(this, ct, this.innerCt);
277     },
278
279     getLayoutTargetSize : function() {
280         var target = this.container.getLayoutTarget(), ret;
281         
282         if (target) {
283             ret = target.getViewSize();
284
285             // IE in strict mode will return a width of 0 on the 1st pass of getViewSize.
286             // Use getStyleSize to verify the 0 width, the adjustment pass will then work properly
287             // with getViewSize
288             if (Ext.isIE && Ext.isStrict && ret.width == 0){
289                 ret =  target.getStyleSize();
290             }
291
292             ret.width  -= target.getPadding('lr');
293             ret.height -= target.getPadding('tb');
294         }
295         
296         return ret;
297     },
298
299     // private
300     renderItem : function(c) {
301         if(Ext.isString(c.margins)){
302             c.margins = this.parseMargins(c.margins);
303         }else if(!c.margins){
304             c.margins = this.defaultMargins;
305         }
306         Ext.layout.BoxLayout.superclass.renderItem.apply(this, arguments);
307     },
308     
309     /**
310      * @private
311      */
312     destroy: function() {
313         Ext.destroy(this.overflowHandler);
314         
315         Ext.layout.BoxLayout.superclass.destroy.apply(this, arguments);
316     }
317 });
318
319
320
321 Ext.ns('Ext.layout.boxOverflow');
322
323 /**
324  * @class Ext.layout.boxOverflow.None
325  * @extends Object
326  * Base class for Box Layout overflow handlers. These specialized classes are invoked when a Box Layout
327  * (either an HBox or a VBox) has child items that are either too wide (for HBox) or too tall (for VBox)
328  * for its container.
329  */
330
331 Ext.layout.boxOverflow.None = Ext.extend(Object, {
332     constructor: function(layout, config) {
333         this.layout = layout;
334         
335         Ext.apply(this, config || {});
336     },
337     
338     handleOverflow: Ext.emptyFn,
339     
340     clearOverflow: Ext.emptyFn
341 });
342
343
344 Ext.layout.boxOverflow.none = Ext.layout.boxOverflow.None;