/** * This layout extends `Ext.layout.container.Column` and adds splitters between adjacent * columns allowing the user to resize them. * @private */Ext.define('Ext.layout.container.Dashboard', { extend: 'Ext.layout.container.Column', alias: 'layout.dashboard', requires: [ 'Ext.layout.container.ColumnSplitter' ], type: 'dashboard', firstColumnCls: Ext.baseCSSPrefix + 'dashboard-column-first', lastColumnCls: Ext.baseCSSPrefix + 'dashboard-column-last', /* * The geometry of a Column layout with splitters between respective items: * * 0 1 2 3 4 * +-----------------------------------------------+ * | +-----------+ || +---------+ || +-----------+ | \ * | | | || | | || | | | \ * | | | || | | || | | | \ * | | | || | | || | | | \ * | +-----------+ || | | || | | | row[0] * | || | | || | | | / * | || | | || | | | / * | || | | || +-----------+ | / * | || | | || | / * | || +---------+ || | * | +-------------------+ || +------------------+ | \ * | | | || | | | \ * | | | || | | | \ * | | | || | | | row[1] * | | | || | | | / * | | | || +------------------+ | / * | +-------------------+ || | / * +-----------------------------------------------+ * 6 7 8 * * The splitter between 4 and 6 will be hidden but still present in the items. It is * considered part of row[0]. */ getSplitterConfig: function() { return { xtype: 'columnsplitter' }; }, /** * @private * Returns a filtered item list sans splitters * @param items * @return {Array|*} */ getColumns: function(items) { var array = Ext.Array; return array.filter(array.from(items), function(item) { return item.target && item.target.isSplitter !== true; }); }, beginLayout: function(ownerContext) { var me = this; me.callParent([ownerContext]); // We need to reset the heights of the splitters so that they don't influence the // layout (mostly overflow management). // eslint-disable-next-line vars-on-top, one-var var childItems = ownerContext.childItems, rows = (ownerContext.rows = []), length = childItems.length, totalWidth = 2, columnTargets = 0, lastRow = 0, maxColumns = me.owner.getMaxColumns(), child, i, prev, row, splitter, target, width; for (i = 0; i < length; ++i) { target = (child = childItems[i]).target; splitter = target && target.isSplitter; columnTargets += (splitter ? 0 : 1); width = splitter ? 0 : target.columnWidth || 1; if (totalWidth + width > 1 || (maxColumns && (columnTargets > maxColumns))) { if (prev) { // We have wrapped and we have a previous item which is a splitter by // definition. We have previously seen that splitter and setHeight(0) // on it. We now setHeight(0) to effectively hide it. prev.orphan = 1; prev.el.setHeight(0); } totalWidth = 0; columnTargets = 1; if (rows.length) { // We have encountered a row break condition // As this is floating layout, classify the current row // before proceeding lastRow = rows.length - 1; me.syncFirstLast( me.getColumns(rows[lastRow].items) ); } rows.push(row = { index: rows.length, items: [], maxHeight: 0 }); } totalWidth += width; row.items.push(child); child.row = row; target.rowIndex = row.index; if (splitter) { child.el.setHeight(1); } prev = child; } if (rows.length) { me.syncFirstLast( me.getColumns(rows[rows.length - 1].items) ); } }, beforeLayoutCycle: function(ownerContext) { var me = this, items = me.owner.items; // We need to do this in beforeLayoutCycle because this changes the child items // and hence needs to be considered before recursing. if (me.splitterGen !== items.generation) { me.syncSplitters(); // The syncSplitters call will change items.generation so do this last. me.splitterGen = items.generation; } me.callParent(arguments); }, finishedLayout: function(ownerContext) { var items = ownerContext.childItems, len = items.length, box, child, i, target, row; this.callParent([ownerContext]); for (i = 0; i < len; i += 2) { target = (child = items[i]).target; box = target.lastBox; row = child.row; row.maxHeight = Math.max(row.maxHeight, box.height); // Put this on the component so that it gets saved (we use this to fix up // columnWidth on restore) target.width = box.width; } for (i = 0; i < len; i ++) { target = (child = items[i]).target; if (!child.orphan) { if (i % 2 === 0) { // Set min height of column to the max height // So that it can increase on adding placeholder target.el.setMinHeight(child.row.maxHeight); } else { // Set height for splitter target.el.setHeight(child.row.maxHeight); } } else { // since this is an orphan child, set its width to 0 target.el.setWidth(0); } } }, /** * This method synchronizes the splitters so that we have exactly one between each * column. * @private */ syncSplitters: function() { var me = this, owner = me.owner, items = owner.items.items, index = items.length, ok = true, shouldBeSplitter = false, item, splitter; // eslint-disable-line no-unused-vars // Walk backwards over the items so that an insertion index is stable. while (index-- > 0) { item = items[index]; if (shouldBeSplitter) { if (item.isSplitter) { shouldBeSplitter = false; } else { // An item is adjacent to an item, so inject a splitter beyond // the current item to separate the columns. Keep shouldBeSplitter // at true since we just encountered an item. if (ok) { ok = false; owner.suspendLayouts(); } splitter = owner.add(index + 1, me.getSplitterConfig()); } } else { if (item.isSplitter) { // A splitter is adjacent to a splitter so we remove this one. We // leave shouldBeSplitter at false because the next thing we see // should still not be a splitter. if (ok) { ok = false; owner.suspendLayouts(); } owner.remove(item); } else { shouldBeSplitter = true; } } } // It is possible to exit the above with a splitter as the first item, but // this is invalid so remove any such splitters. while (items.length && (item = items[0]).isSplitter) { if (ok) { ok = false; owner.suspendLayouts(); } owner.remove(item); } if (!ok) { owner.resumeLayouts(); } }, syncFirstLast: function(items) { var me = this, firstCls = me.firstColumnCls, lastCls = me.lastColumnCls, len, firstAndLast = [firstCls, lastCls], i, item, last; items = Ext.Array.from(items); len = items.length; for (i = 0; i < len; ++i) { item = items[i].target; last = (i === len - 1); if (!i) { // if (first) if (last) { item.addCls(firstAndLast); } else { item.addCls(firstCls); item.removeCls(lastCls); } } else if (last) { item.addCls(lastCls); item.removeCls(firstCls); } else { item.removeCls(firstAndLast); } } }, calculateItemSizeWithContent: function(availableWidth, contentWidth, items) { var itemMarginWidth, itemContext, splitterItemWidth = 0, halfSplitterItemWidth = 0, itemWidth, i, len = items.length, rowindex, rowLen; availableWidth = (availableWidth < contentWidth) ? 0 : availableWidth; for (i = 0; i < len; i += 2) { itemContext = items[i]; rowLen = itemContext.row.items.length; rowindex = itemContext.row.items.indexOf(itemContext); itemMarginWidth = itemContext.marginInfo.width; // always set by above loop itemWidth = itemContext.target.columnWidth; itemWidth = Math.floor(itemWidth * availableWidth) - itemMarginWidth; // Get the width of splitter item. We calculate the half value for width // since splitter must form a part of both items between which it lies equally. if (splitterItemWidth === 0 && (rowindex + 1 < rowLen)) { splitterItemWidth = items[i + 1].getProp('width'); halfSplitterItemWidth = Math.ceil(splitterItemWidth / 2); } if (halfSplitterItemWidth) { // if there exists a splitter to the right and this splitter // is not the last item of this row, reduce the width of the // column since splitter will take half width from the item if (rowindex + 2 < rowLen) { itemWidth -= halfSplitterItemWidth; } // if there exists a splitter to the left, reduce the width // of the column since splitter will take half width from the item if (rowindex > 0) { itemWidth -= halfSplitterItemWidth; } } itemWidth = itemContext.setWidth(itemWidth); // constrains to min/maxWidth contentWidth += itemWidth + itemMarginWidth; } return contentWidth; }});