Upgrade to ExtJS 4.0.2 - Released 06/09/2011
[extjs.git] / src / layout / container / Table.js
1 /*
2
3 This file is part of Ext JS 4
4
5 Copyright (c) 2011 Sencha Inc
6
7 Contact:  http://www.sencha.com/contact
8
9 GNU General Public License Usage
10 This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file.  Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
11
12 If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
13
14 */
15 /**
16  * @class Ext.layout.container.Table
17  * @extends Ext.layout.container.Auto
18  * <p>This layout allows you to easily render content into an HTML table.  The total number of columns can be
19  * specified, and rowspan and colspan can be used to create complex layouts within the table.
20  * This class is intended to be extended or created via the <code>layout: {type: 'table'}</code>
21  * {@link Ext.container.Container#layout} config, and should generally not need to be created directly via the new keyword.</p>
22  * <p>Note that when creating a layout via config, the layout-specific config properties must be passed in via
23  * the {@link Ext.container.Container#layout} object which will then be applied internally to the layout.  In the
24  * case of TableLayout, the only valid layout config properties are {@link #columns} and {@link #tableAttrs}.
25  * However, the items added to a TableLayout can supply the following table-specific config properties:</p>
26  * <ul>
27  * <li><b>rowspan</b> Applied to the table cell containing the item.</li>
28  * <li><b>colspan</b> Applied to the table cell containing the item.</li>
29  * <li><b>cellId</b> An id applied to the table cell containing the item.</li>
30  * <li><b>cellCls</b> A CSS class name added to the table cell containing the item.</li>
31  * </ul>
32  * <p>The basic concept of building up a TableLayout is conceptually very similar to building up a standard
33  * HTML table.  You simply add each panel (or "cell") that you want to include along with any span attributes
34  * specified as the special config properties of rowspan and colspan which work exactly like their HTML counterparts.
35  * Rather than explicitly creating and nesting rows and columns as you would in HTML, you simply specify the
36  * total column count in the layoutConfig and start adding panels in their natural order from left to right,
37  * top to bottom.  The layout will automatically figure out, based on the column count, rowspans and colspans,
38  * how to position each panel within the table.  Just like with HTML tables, your rowspans and colspans must add
39  * up correctly in your overall layout or you'll end up with missing and/or extra cells!  Example usage:</p>
40  * {@img Ext.layout.container.Table/Ext.layout.container.Table.png Ext.layout.container.Table container layout}
41  * <pre><code>
42 // This code will generate a layout table that is 3 columns by 2 rows
43 // with some spanning included.  The basic layout will be:
44 // +--------+-----------------+
45 // |   A    |   B             |
46 // |        |--------+--------|
47 // |        |   C    |   D    |
48 // +--------+--------+--------+
49     Ext.create('Ext.panel.Panel', {
50         title: 'Table Layout',
51         width: 300,
52         height: 150,
53         layout: {
54             type: 'table',
55             // The total column count must be specified here
56             columns: 3
57         },
58         defaults: {
59             // applied to each contained panel
60             bodyStyle:'padding:20px'
61         },
62         items: [{
63             html: 'Cell A content',
64             rowspan: 2
65         },{
66             html: 'Cell B content',
67             colspan: 2
68         },{
69             html: 'Cell C content',
70             cellCls: 'highlight'
71         },{
72             html: 'Cell D content'
73         }],
74         renderTo: Ext.getBody()
75     });
76 </code></pre>
77  */
78
79 Ext.define('Ext.layout.container.Table', {
80
81     /* Begin Definitions */
82
83     alias: ['layout.table'],
84     extend: 'Ext.layout.container.Auto',
85     alternateClassName: 'Ext.layout.TableLayout',
86
87     /* End Definitions */
88
89     /**
90      * @cfg {Number} columns
91      * The total number of columns to create in the table for this layout.  If not specified, all Components added to
92      * this layout will be rendered into a single row using one column per Component.
93      */
94
95     // private
96     monitorResize:false,
97
98     type: 'table',
99
100     // Table layout is a self-sizing layout. When an item of for example, a dock layout, the Panel must expand to accommodate
101     // a table layout. See in particular AbstractDock::onLayout for use of this flag.
102     autoSize: true,
103
104     clearEl: true, // Base class will not create it if already truthy. Not needed in tables.
105
106     targetCls: Ext.baseCSSPrefix + 'table-layout-ct',
107     tableCls: Ext.baseCSSPrefix + 'table-layout',
108     cellCls: Ext.baseCSSPrefix + 'table-layout-cell',
109
110     /**
111      * @cfg {Object} tableAttrs
112      * <p>An object containing properties which are added to the {@link Ext.core.DomHelper DomHelper} specification
113      * used to create the layout's <tt>&lt;table&gt;</tt> element. Example:</p><pre><code>
114 {
115     xtype: 'panel',
116     layout: {
117         type: 'table',
118         columns: 3,
119         tableAttrs: {
120             style: {
121                 width: '100%'
122             }
123         }
124     }
125 }</code></pre>
126      */
127     tableAttrs:null,
128
129     /**
130      * @cfg {Object} trAttrs
131      * <p>An object containing properties which are added to the {@link Ext.core.DomHelper DomHelper} specification
132      * used to create the layout's <tt>&lt;tr&gt;</tt> elements.
133      */
134
135     /**
136      * @cfg {Object} tdAttrs
137      * <p>An object containing properties which are added to the {@link Ext.core.DomHelper DomHelper} specification
138      * used to create the layout's <tt>&lt;td&gt;</tt> elements.
139      */
140
141     /**
142      * @private
143      * Iterates over all passed items, ensuring they are rendered in a cell in the proper
144      * location in the table structure.
145      */
146     renderItems: function(items) {
147         var tbody = this.getTable().tBodies[0],
148             rows = tbody.rows,
149             i = 0,
150             len = items.length,
151             cells, curCell, rowIdx, cellIdx, item, trEl, tdEl, itemCt;
152
153         // Calculate the correct cell structure for the current items
154         cells = this.calculateCells(items);
155
156         // Loop over each cell and compare to the current cells in the table, inserting/
157         // removing/moving cells as needed, and making sure each item is rendered into
158         // the correct cell.
159         for (; i < len; i++) {
160             curCell = cells[i];
161             rowIdx = curCell.rowIdx;
162             cellIdx = curCell.cellIdx;
163             item = items[i];
164
165             // If no row present, create and insert one
166             trEl = rows[rowIdx];
167             if (!trEl) {
168                 trEl = tbody.insertRow(rowIdx);
169                 if (this.trAttrs) {
170                     trEl.set(this.trAttrs);
171                 }
172             }
173
174             // If no cell present, create and insert one
175             itemCt = tdEl = Ext.get(trEl.cells[cellIdx] || trEl.insertCell(cellIdx));
176             if (this.needsDivWrap()) { //create wrapper div if needed - see docs below
177                 itemCt = tdEl.first() || tdEl.createChild({tag: 'div'});
178                 itemCt.setWidth(null);
179             }
180
181             // Render or move the component into the cell
182             if (!item.rendered) {
183                 this.renderItem(item, itemCt, 0);
184             }
185             else if (!this.isValidParent(item, itemCt, 0)) {
186                 this.moveItem(item, itemCt, 0);
187             }
188
189             // Set the cell properties
190             if (this.tdAttrs) {
191                 tdEl.set(this.tdAttrs);
192             }
193             tdEl.set({
194                 colSpan: item.colspan || 1,
195                 rowSpan: item.rowspan || 1,
196                 id: item.cellId || '',
197                 cls: this.cellCls + ' ' + (item.cellCls || '')
198             });
199
200             // If at the end of a row, remove any extra cells
201             if (!cells[i + 1] || cells[i + 1].rowIdx !== rowIdx) {
202                 cellIdx++;
203                 while (trEl.cells[cellIdx]) {
204                     trEl.deleteCell(cellIdx);
205                 }
206             }
207         }
208
209         // Delete any extra rows
210         rowIdx++;
211         while (tbody.rows[rowIdx]) {
212             tbody.deleteRow(rowIdx);
213         }
214     },
215
216     afterLayout: function() {
217         this.callParent();
218
219         if (this.needsDivWrap()) {
220             // set wrapper div width to match layed out item - see docs below
221             Ext.Array.forEach(this.getLayoutItems(), function(item) {
222                 Ext.fly(item.el.dom.parentNode).setWidth(item.getWidth());
223             });
224         }
225     },
226
227     /**
228      * @private
229      * Determine the row and cell indexes for each component, taking into consideration
230      * the number of columns and each item's configured colspan/rowspan values.
231      * @param {Array} items The layout components
232      * @return {Array} List of row and cell indexes for each of the components
233      */
234     calculateCells: function(items) {
235         var cells = [],
236             rowIdx = 0,
237             colIdx = 0,
238             cellIdx = 0,
239             totalCols = this.columns || Infinity,
240             rowspans = [], //rolling list of active rowspans for each column
241             i = 0, j,
242             len = items.length,
243             item;
244
245         for (; i < len; i++) {
246             item = items[i];
247
248             // Find the first available row/col slot not taken up by a spanning cell
249             while (colIdx >= totalCols || rowspans[colIdx] > 0) {
250                 if (colIdx >= totalCols) {
251                     // move down to next row
252                     colIdx = 0;
253                     cellIdx = 0;
254                     rowIdx++;
255
256                     // decrement all rowspans
257                     for (j = 0; j < totalCols; j++) {
258                         if (rowspans[j] > 0) {
259                             rowspans[j]--;
260                         }
261                     }
262                 } else {
263                     colIdx++;
264                 }
265             }
266
267             // Add the cell info to the list
268             cells.push({
269                 rowIdx: rowIdx,
270                 cellIdx: cellIdx
271             });
272
273             // Increment
274             for (j = item.colspan || 1; j; --j) {
275                 rowspans[colIdx] = item.rowspan || 1;
276                 ++colIdx;
277             }
278             ++cellIdx;
279         }
280
281         return cells;
282     },
283
284     /**
285      * @private
286      * Return the layout's table element, creating it if necessary.
287      */
288     getTable: function() {
289         var table = this.table;
290         if (!table) {
291             table = this.table = this.getTarget().createChild(
292                 Ext.apply({
293                     tag: 'table',
294                     role: 'presentation',
295                     cls: this.tableCls,
296                     cellspacing: 0, //TODO should this be specified or should CSS handle it?
297                     cn: {tag: 'tbody'}
298                 }, this.tableAttrs),
299                 null, true
300             );
301         }
302         return table;
303     },
304
305     /**
306      * @private
307      * Opera 10.5 has a bug where if a table cell's child has box-sizing:border-box and padding, it
308      * will include that padding in the size of the cell, making it always larger than the
309      * shrink-wrapped size of its contents. To get around this we have to wrap the contents in a div
310      * and then set that div's width to match the item rendered within it afterLayout. This method
311      * determines whether we need the wrapper div; it currently does a straight UA sniff as this bug
312      * seems isolated to just Opera 10.5, but feature detection could be added here if needed.
313      */
314     needsDivWrap: function() {
315         return Ext.isOpera10_5;
316     }
317 });