/** * This layout implements the column arrangement for {@link Ext.form.CheckboxGroup} and {@link Ext.form.RadioGroup}. * It groups the component's sub-items into columns based on the component's * {@link Ext.form.CheckboxGroup#columns columns} and {@link Ext.form.CheckboxGroup#vertical} config properties. */ Ext.define('Ext.layout.container.CheckboxGroup', { extend: 'Ext.layout.container.Container', alias: ['layout.checkboxgroup'], /** * @cfg {Boolean} [autoFlex=true] * By default, CheckboxGroup allocates all available space to the configured columns meaning that * column are evenly spaced across the container. * * To have each column only be wide enough to fit the container Checkboxes (or Radios), set `autoFlex` to `false` */ autoFlex: true, type: 'checkboxgroup', childEls: [ 'innerCt' ], renderTpl: [ '<table id="{ownerId}-innerCt" role="presentation" style="{tableStyle}"><tbody><tr>', '<tpl for="columns">', '<td class="{parent.colCls}" valign="top" style="{style}">', '{% this.renderColumn(out,parent,xindex-1) %}', '</td>', '</tpl>', '</tr></tbody></table>' ], beginLayout: function(ownerContext) { var me = this, columns, numCols, numChildren, childItems, i, width, cwidth, totalFlex = 0, flexedCols = 0, autoFlex = me.autoFlex, currGen = me.owner.items.generation, innerCtStyle = me.innerCt.dom.style; me.callParent(arguments); childItems = ownerContext.childItems; // If the child items have changed since the last layout then we need to fixup // the association of items to columns: if (me.lastChildGeneration != currGen) { me.lastChildGeneration = currGen; me.fixColumns(); } // fixColumns may have updated the element cache, no need to get the columnEls // now... columns = me.columnEls; ownerContext.innerCtContext = ownerContext.getEl('innerCt', me); // The columns config may be an array of widths. Any value < 1 is taken to be a fraction: if (!ownerContext.widthModel.shrinkWrap) { numCols = columns.length; // If columns is an array of numeric widths if (me.columnsArray) { // first calculate total flex for (i = 0; i < numCols; i++) { width = me.owner.columns[i]; if (width < 1) { totalFlex += width; flexedCols++; } } // now apply widths for (i = 0; i < numCols; i++) { width = me.owner.columns[i]; if (width < 1) { cwidth = ((width / totalFlex) * 100) + '%'; } else { cwidth = width + 'px'; } columns[i].style.width = cwidth; } } // Otherwise it's the *number* of columns, so distributed the widths evenly else { for (i = 0; i < numCols; i++) { // autoFlex: true will automatically calculate % widths // autoFlex: false allows the table to decide (shrinkWrap, in effect) // on a per-column basis cwidth = autoFlex ? (1 / numCols * 100) + '%' : ''; columns[i].style.width = cwidth; flexedCols++; } } // no flexed cols -- all widths are fixed if (!flexedCols) { innerCtStyle.tableLayout = 'fixed'; innerCtStyle.width = ''; // some flexed cols -- need to fix some } else if (flexedCols < numCols) { innerCtStyle.tableLayout = 'fixed'; innerCtStyle.width = '100%'; // let the table decide } else { innerCtStyle.tableLayout = 'auto'; // if autoFlex, fill available space, else compact down if (autoFlex) { innerCtStyle.width = '100%'; } else { innerCtStyle.width = ''; } } } else { innerCtStyle.tableLayout = 'auto'; innerCtStyle.width = ''; } }, cacheElements: function () { var me = this; // Grab defined childEls me.callParent(); // Grab columns TDs me.columnEls = me.innerCt.query('td.' + me.owner.groupCls); // we just rendered so the items are in the correct columns: me.lastChildGeneration = me.owner.items.generation; }, /* * Just wait for the child items to all lay themselves out in the width we are configured * to make available to them. Then we can measure our height. */ calculate: function(ownerContext) { var me = this, targetContext, widthShrinkWrap, heightShrinkWrap, shrinkWrap, table, targetPadding; // The columnEls are widthed using their own width attributes, we just need to wait // for all children to have arranged themselves in that width, and then collect our height. if (!ownerContext.getDomProp('containerChildrenDone')) { me.done = false; } else { targetContext = ownerContext.innerCtContext; widthShrinkWrap = ownerContext.widthModel.shrinkWrap; heightShrinkWrap = ownerContext.heightModel.shrinkWrap; shrinkWrap = heightShrinkWrap || widthShrinkWrap; table = targetContext.el.dom; targetPadding = shrinkWrap && targetContext.getPaddingInfo(); if (widthShrinkWrap) { ownerContext.setContentWidth(table.offsetWidth + targetPadding.width, true); } if (heightShrinkWrap) { ownerContext.setContentHeight(table.offsetHeight + targetPadding.height, true); } } }, doRenderColumn: function (out, renderData, columnIndex) { // Careful! This method is bolted on to the renderTpl so all we get for context is // the renderData! The "this" pointer is the renderTpl instance! var me = renderData.$layout, owner = me.owner, columnCount = renderData.columnCount, items = owner.items.items, itemCount = items.length, item, itemIndex, rowCount, increment, tree; // Example: // columnCount = 3 // items.length = 10 if (owner.vertical) { // 0 1 2 // +---+---+---+ // 0 | 0 | 4 | 8 | // +---+---+---+ // 1 | 1 | 5 | 9 | // +---+---+---+ // 2 | 2 | 6 | | // +---+---+---+ // 3 | 3 | 7 | | // +---+---+---+ rowCount = Math.ceil(itemCount / columnCount); // = 4 itemIndex = columnIndex * rowCount; itemCount = Math.min(itemCount, itemIndex + rowCount); increment = 1; } else { // 0 1 2 // +---+---+---+ // 0 | 0 | 1 | 2 | // +---+---+---+ // 1 | 3 | 4 | 5 | // +---+---+---+ // 2 | 6 | 7 | 8 | // +---+---+---+ // 3 | 9 | | | // +---+---+---+ itemIndex = columnIndex; increment = columnCount; } for ( ; itemIndex < itemCount; itemIndex += increment) { item = items[itemIndex]; me.configureItem(item); tree = item.getRenderTree(); Ext.DomHelper.generateMarkup(tree, out); } }, // Distribute child items between column elements according to row first or // column first order fixColumns: function () { var me = this, owner = me.owner, cfgCols = owner.columns, columns = me.columnEls, items = owner.items.items, columnCount = columns.length, itemCount = items.length, columnIndex, i, rowCount, tr, td, newCount; // if running in 'auto' columns mode, and the number of items has increased (add call) // then we need to add a new td to the table if ((!cfgCols || cfgCols == 'auto') && (itemCount > columnCount)) { // add column td's for each new item added newCount = itemCount - columnCount; for (i = 0; i < newCount; i++) { tr = me.innerCt.down('tr'); tr.createChild({ cls: owner.groupCls, tag: 'td', vAlign: 'top' }); } me.cacheElements(); columns = me.columnEls; columnCount = columns.length; } if (owner.vertical) { columnIndex = -1; // first loop will increment this to 0 rowCount = Math.ceil(itemCount / columnCount); for (i = 0; i < itemCount; ++i) { if (i % rowCount === 0) { ++columnIndex; } columns[columnIndex].appendChild(items[i].el.dom); } } else { for (i = 0; i < itemCount; ++i) { columnIndex = i % columnCount; columns[columnIndex].appendChild(items[i].el.dom); } } }, /** * Returns the number of columns in the checkbox group. * @private */ getColumnCount: function() { var me = this, owner = me.owner, ownerColumns = owner.columns; // Our columns config is an array of numeric widths. // Calculate our total width if (me.columnsArray) { return ownerColumns.length; } if (Ext.isNumber(ownerColumns)) { return ownerColumns; } return owner.items.length; }, getItemSizePolicy: function (item) { return this.autoSizePolicy; }, getRenderData: function () { var me = this, data = me.callParent(), owner = me.owner, i, columns = me.getColumnCount(), width, column, cwidth, autoFlex = me.autoFlex, totalFlex = 0, flexedCols = 0; // calculate total flex if (me.columnsArray) { for (i=0; i < columns; i++) { width = me.owner.columns[i]; if (width < 1) { totalFlex += width; flexedCols++; } } } data.colCls = owner.groupCls; data.columnCount = columns; data.columns = []; for (i = 0; i < columns; i++) { column = (data.columns[i] = {}); if (me.columnsArray) { width = me.owner.columns[i]; if (width < 1) { cwidth = ((width / totalFlex) * 100) + '%'; } else { cwidth = width + 'px'; } column.style = 'width:' + cwidth; } else { column.style = 'width:' + (1 / columns * 100) + '%'; flexedCols++; } } // If the columns config was an array of column widths, allow table to auto width data.tableStyle = !flexedCols ? 'table-layout:fixed;' : (flexedCols < columns) ? 'table-layout:fixed;width:100%' : (autoFlex) ? 'table-layout:auto;width:100%' : 'table-layout:auto;'; return data; }, // Overridden method from AbstractContainer. getRenderTarget: function() { return this.innerCt; }, initLayout: function () { var me = this, owner = me.owner; me.columnsArray = Ext.isArray(owner.columns); me.evenColumns = Ext.isNumber(owner.columns); me.callParent(); }, // Always valid. beginLayout ensures the encapsulating elements of all children are in the correct place isValidParent: function() { return true; }, setupRenderTpl: function (renderTpl) { this.callParent(arguments); renderTpl.renderColumn = this.doRenderColumn; } });