/** * @class Ext.layout.container.Table * @extends Ext.layout.container.Auto * <p>This layout allows you to easily render content into an HTML table. The total number of columns can be * specified, and rowspan and colspan can be used to create complex layouts within the table. * This class is intended to be extended or created via the <code>layout: {type: 'table'}</code> * {@link Ext.container.Container#layout} config, and should generally not need to be created directly via the new keyword.</p> * <p>Note that when creating a layout via config, the layout-specific config properties must be passed in via * the {@link Ext.container.Container#layout} object which will then be applied internally to the layout. In the * case of TableLayout, the only valid layout config properties are {@link #columns} and {@link #tableAttrs}. * However, the items added to a TableLayout can supply the following table-specific config properties:</p> * <ul> * <li><b>rowspan</b> Applied to the table cell containing the item.</li> * <li><b>colspan</b> Applied to the table cell containing the item.</li> * <li><b>cellId</b> An id applied to the table cell containing the item.</li> * <li><b>cellCls</b> A CSS class name added to the table cell containing the item.</li> * </ul> * <p>The basic concept of building up a TableLayout is conceptually very similar to building up a standard * HTML table. You simply add each panel (or "cell") that you want to include along with any span attributes * specified as the special config properties of rowspan and colspan which work exactly like their HTML counterparts. * Rather than explicitly creating and nesting rows and columns as you would in HTML, you simply specify the * total column count in the layoutConfig and start adding panels in their natural order from left to right, * top to bottom. The layout will automatically figure out, based on the column count, rowspans and colspans, * how to position each panel within the table. Just like with HTML tables, your rowspans and colspans must add * up correctly in your overall layout or you'll end up with missing and/or extra cells! Example usage:</p> * {@img Ext.layout.container.Table/Ext.layout.container.Table.png Ext.layout.container.Table container layout} * <pre><code> // This code will generate a layout table that is 3 columns by 2 rows // with some spanning included. The basic layout will be: // +--------+-----------------+ // | A | B | // | |--------+--------| // | | C | D | // +--------+--------+--------+ Ext.create('Ext.panel.Panel', { title: 'Table Layout', width: 300, height: 150, layout: { type: 'table', // The total column count must be specified here columns: 3 }, defaults: { // applied to each contained panel bodyStyle:'padding:20px' }, items: [{ html: 'Cell A content', rowspan: 2 },{ html: 'Cell B content', colspan: 2 },{ html: 'Cell C content', cellCls: 'highlight' },{ html: 'Cell D content' }], renderTo: Ext.getBody() }); </code></pre> */ Ext.define('Ext.layout.container.Table', { /* Begin Definitions */ alias: ['layout.table'], extend: 'Ext.layout.container.Auto', alternateClassName: 'Ext.layout.TableLayout', /* End Definitions */ /** * @cfg {Number} columns * The total number of columns to create in the table for this layout. If not specified, all Components added to * this layout will be rendered into a single row using one column per Component. */ // private monitorResize:false, type: 'table', // Table layout is a self-sizing layout. When an item of for example, a dock layout, the Panel must expand to accommodate // a table layout. See in particular AbstractDock::onLayout for use of this flag. autoSize: true, clearEl: true, // Base class will not create it if already truthy. Not needed in tables. targetCls: Ext.baseCSSPrefix + 'table-layout-ct', tableCls: Ext.baseCSSPrefix + 'table-layout', cellCls: Ext.baseCSSPrefix + 'table-layout-cell', /** * @cfg {Object} tableAttrs * <p>An object containing properties which are added to the {@link Ext.core.DomHelper DomHelper} specification * used to create the layout's <tt><table></tt> element. Example:</p><pre><code> { xtype: 'panel', layout: { type: 'table', columns: 3, tableAttrs: { style: { width: '100%' } } } }</code></pre> */ tableAttrs:null, /** * @private * Iterates over all passed items, ensuring they are rendered in a cell in the proper * location in the table structure. */ renderItems: function(items) { var tbody = this.getTable().tBodies[0], rows = tbody.rows, i = 0, len = items.length, cells, curCell, rowIdx, cellIdx, item, trEl, tdEl, itemCt; // Calculate the correct cell structure for the current items cells = this.calculateCells(items); // Loop over each cell and compare to the current cells in the table, inserting/ // removing/moving cells as needed, and making sure each item is rendered into // the correct cell. for (; i < len; i++) { curCell = cells[i]; rowIdx = curCell.rowIdx; cellIdx = curCell.cellIdx; item = items[i]; // If no row present, create and insert one trEl = rows[rowIdx]; if (!trEl) { trEl = tbody.insertRow(rowIdx); } // If no cell present, create and insert one itemCt = tdEl = Ext.get(trEl.cells[cellIdx] || trEl.insertCell(cellIdx)); if (this.needsDivWrap()) { //create wrapper div if needed - see docs below itemCt = tdEl.first() || tdEl.createChild({tag: 'div'}); itemCt.setWidth(null); } // Render or move the component into the cell if (!item.rendered) { this.renderItem(item, itemCt, 0); } else if (!this.isValidParent(item, itemCt, 0)) { this.moveItem(item, itemCt, 0); } // Set the cell properties tdEl.set({ colSpan: item.colspan || 1, rowSpan: item.rowspan || 1, id: item.cellId || '', cls: this.cellCls + ' ' + (item.cellCls || '') }); // If at the end of a row, remove any extra cells if (!cells[i + 1] || cells[i + 1].rowIdx !== rowIdx) { cellIdx++; while (trEl.cells[cellIdx]) { trEl.deleteCell(cellIdx); } } } // Delete any extra rows rowIdx++; while (tbody.rows[rowIdx]) { tbody.deleteRow(rowIdx); } }, afterLayout: function() { this.callParent(); if (this.needsDivWrap()) { // set wrapper div width to match layed out item - see docs below Ext.Array.forEach(this.getLayoutItems(), function(item) { Ext.fly(item.el.dom.parentNode).setWidth(item.getWidth()); }); } }, /** * @private * Determine the row and cell indexes for each component, taking into consideration * the number of columns and each item's configured colspan/rowspan values. * @param {Array} items The layout components * @return {Array} List of row and cell indexes for each of the components */ calculateCells: function(items) { var cells = [], rowIdx = 0, colIdx = 0, cellIdx = 0, totalCols = this.columns || Infinity, rowspans = [], //rolling list of active rowspans for each column i = 0, j, len = items.length, item; for (; i < len; i++) { item = items[i]; // Find the first available row/col slot not taken up by a spanning cell while (colIdx >= totalCols || rowspans[colIdx] > 0) { if (colIdx >= totalCols) { // move down to next row colIdx = 0; cellIdx = 0; rowIdx++; // decrement all rowspans for (j = 0; j < totalCols; j++) { if (rowspans[j] > 0) { rowspans[j]--; } } } else { colIdx++; } } // Add the cell info to the list cells.push({ rowIdx: rowIdx, cellIdx: cellIdx }); // Increment rowspans[colIdx] = item.rowspan || 1; colIdx += item.colspan || 1; cellIdx++; } return cells; }, /** * @private * Return the layout's table element, creating it if necessary. */ getTable: function() { var table = this.table; if (!table) { table = this.table = this.getTarget().createChild( Ext.apply({ tag: 'table', role: 'presentation', cls: this.tableCls, cellspacing: 0, //TODO should this be specified or should CSS handle it? cn: {tag: 'tbody'} }, this.tableAttrs), null, true ); } return table; }, /** * @private * Opera 10.5 has a bug where if a table cell's child has box-sizing:border-box and padding, it * will include that padding in the size of the cell, making it always larger than the * shrink-wrapped size of its contents. To get around this we have to wrap the contents in a div * and then set that div's width to match the item rendered within it afterLayout. This method * determines whether we need the wrapper div; it currently does a straight UA sniff as this bug * seems isolated to just Opera 10.5, but feature detection could be added here if needed. */ needsDivWrap: function() { return Ext.isOpera10_5; } });