/** * @class Ext.layout.component.AbstractDock * @extends Ext.layout.component.Component * @private * This ComponentLayout handles docking for Panels. It takes care of panels that are * part of a ContainerLayout that sets this Panel's size and Panels that are part of * an AutoContainerLayout in which this panel get his height based of the CSS or * or its content. */ Ext.define('Ext.layout.component.AbstractDock', { /* Begin Definitions */ extend: 'Ext.layout.component.Component', /* End Definitions */ type: 'dock', /** * @private * @property autoSizing * @type Boolean * This flag is set to indicate this layout may have an autoHeight/autoWidth. */ autoSizing: true, beforeLayout: function() { var returnValue = this.callParent(arguments); if (returnValue !== false && (!this.initializedBorders || this.childrenChanged) && (!this.owner.border || this.owner.manageBodyBorders)) { this.handleItemBorders(); this.initializedBorders = true; } return returnValue; }, handleItemBorders: function() { var owner = this.owner, body = owner.body, docked = this.getLayoutItems(), borders = { top: [], right: [], bottom: [], left: [] }, oldBorders = this.borders, opposites = { top: 'bottom', right: 'left', bottom: 'top', left: 'right' }, i, ln, item, dock, side; for (i = 0, ln = docked.length; i < ln; i++) { item = docked[i]; dock = item.dock; if (item.ignoreBorderManagement) { continue; } if (!borders[dock].satisfied) { borders[dock].push(item); borders[dock].satisfied = true; } if (!borders.top.satisfied && opposites[dock] !== 'top') { borders.top.push(item); } if (!borders.right.satisfied && opposites[dock] !== 'right') { borders.right.push(item); } if (!borders.bottom.satisfied && opposites[dock] !== 'bottom') { borders.bottom.push(item); } if (!borders.left.satisfied && opposites[dock] !== 'left') { borders.left.push(item); } } if (oldBorders) { for (side in oldBorders) { if (oldBorders.hasOwnProperty(side)) { ln = oldBorders[side].length; if (!owner.manageBodyBorders) { for (i = 0; i < ln; i++) { oldBorders[side][i].removeCls(Ext.baseCSSPrefix + 'docked-noborder-' + side); } if (!oldBorders[side].satisfied && !owner.bodyBorder) { body.removeCls(Ext.baseCSSPrefix + 'docked-noborder-' + side); } } else if (oldBorders[side].satisfied) { body.setStyle('border-' + side + '-width', ''); } } } } for (side in borders) { if (borders.hasOwnProperty(side)) { ln = borders[side].length; if (!owner.manageBodyBorders) { for (i = 0; i < ln; i++) { borders[side][i].addCls(Ext.baseCSSPrefix + 'docked-noborder-' + side); } if ((!borders[side].satisfied && !owner.bodyBorder) || owner.bodyBorder === false) { body.addCls(Ext.baseCSSPrefix + 'docked-noborder-' + side); } } else if (borders[side].satisfied) { body.setStyle('border-' + side + '-width', '1px'); } } } this.borders = borders; }, /** * @protected * @param {Ext.Component} owner The Panel that owns this DockLayout * @param {Ext.Element} target The target in which we are going to render the docked items * @param {Array} args The arguments passed to the ComponentLayout.layout method */ onLayout: function(width, height) { if (this.onLayout_running) { return; } this.onLayout_running = true; var me = this, owner = me.owner, body = owner.body, layout = owner.layout, target = me.getTarget(), autoWidth = false, autoHeight = false, padding, border, frameSize; // We start of by resetting all the layouts info var info = me.info = { boxes: [], size: { width: width, height: height }, bodyBox: {} }; // Clear isAutoDock flag delete layout.isAutoDock; Ext.applyIf(info, me.getTargetInfo()); // We need to bind to the ownerCt whenever we do not have a user set height or width. if (owner && owner.ownerCt && owner.ownerCt.layout && owner.ownerCt.layout.isLayout) { if (!Ext.isNumber(owner.height) || !Ext.isNumber(owner.width)) { owner.ownerCt.layout.bindToOwnerCtComponent = true; } else { owner.ownerCt.layout.bindToOwnerCtComponent = false; } } // Determine if we have an autoHeight or autoWidth. if (height == null || width == null) { padding = info.padding; border = info.border; frameSize = me.frameSize; // Auto-everything, clear out any style height/width and read from css if ((height == null) && (width == null)) { autoHeight = true; autoWidth = true; me.setTargetSize(null); me.setBodyBox({width: null, height: null}); } // Auto-height else if (height == null) { autoHeight = true; // Clear any sizing that we already set in a previous layout me.setTargetSize(width); me.setBodyBox({width: width - padding.left - border.left - padding.right - border.right - frameSize.left - frameSize.right, height: null}); // Auto-width } else { autoWidth = true; // Clear any sizing that we already set in a previous layout me.setTargetSize(null, height); me.setBodyBox({width: null, height: height - padding.top - padding.bottom - border.top - border.bottom - frameSize.top - frameSize.bottom}); } // Run the container if (layout && layout.isLayout) { // Auto-Sized so have the container layout notify the component layout. layout.bindToOwnerCtComponent = true; // Set flag so we don't do a redundant container layout layout.isAutoDock = layout.autoSize !== true; layout.layout(); // If this is an autosized container layout, then we must compensate for a // body that is being autosized. We do not want to adjust the body's size // to accommodate the dock items, but rather we will want to adjust the // target's size. // // This is necessary because, particularly in a Box layout, all child items // are set with absolute dimensions that are not flexible to the size of its // innerCt/target. So once they are laid out, they are sized for good. By // shrinking the body box to accommodate dock items, we're merely cutting off // parts of the body. Not good. Instead, the target's size should expand // to fit the dock items in. This is valid because the target container is // suppose to be autosized to fit everything accordingly. info.autoSizedCtLayout = layout.autoSize === true; info.autoHeight = autoHeight; info.autoWidth = autoWidth; } // The dockItems method will add all the top and bottom docked items height // to the info.panelSize height. That's why we have to call setSize after // we dock all the items to actually set the panel's width and height. // We have to do this because the panel body and docked items will be position // absolute which doesn't stretch the panel. me.dockItems(); me.setTargetSize(info.size.width, info.size.height); } else { me.setTargetSize(width, height); me.dockItems(); } me.callParent(arguments); this.onLayout_running = false; }, /** * @protected * This method will first update all the information about the docked items, * body dimensions and position, the panel's total size. It will then * set all these values on the docked items and panel body. * @param {Array} items Array containing all the docked items * @param {Boolean} autoBoxes Set this to true if the Panel is part of an * AutoContainerLayout */ dockItems : function() { this.calculateDockBoxes(); // Both calculateAutoBoxes and calculateSizedBoxes are changing the // information about the body, panel size, and boxes for docked items // inside a property called info. var info = this.info, autoWidth = info.autoWidth, autoHeight = info.autoHeight, boxes = info.boxes, ln = boxes.length, dock, i, item; // We are going to loop over all the boxes that were calculated // and set the position of each item the box belongs to. for (i = 0; i < ln; i++) { dock = boxes[i]; item = dock.item; item.setPosition(dock.x, dock.y); if ((autoWidth || autoHeight) && item.layout && item.layout.isLayout) { // Auto-Sized so have the container layout notify the component layout. item.layout.bindToOwnerCtComponent = true; } } // Don't adjust body width/height if the target is using an auto container layout. // But, we do want to adjust the body size if the container layout is auto sized. if (!info.autoSizedCtLayout) { if (autoWidth) { info.bodyBox.width = null; } if (autoHeight) { info.bodyBox.height = null; } } // If the bodyBox has been adjusted because of the docked items // we will update the dimensions and position of the panel's body. this.setBodyBox(info.bodyBox); }, /** * @protected * This method will set up some initial information about the panel size and bodybox * and then loop over all the items you pass it to take care of stretching, aligning, * dock position and all calculations involved with adjusting the body box. * @param {Array} items Array containing all the docked items we have to layout */ calculateDockBoxes : function() { if (this.calculateDockBoxes_running) { // [AbstractDock#calculateDockBoxes] attempted to run again while it was already running return; } this.calculateDockBoxes_running = true; // We want to use the Panel's el width, and the Panel's body height as the initial // size we are going to use in calculateDockBoxes. We also want to account for // the border of the panel. var me = this, target = me.getTarget(), items = me.getLayoutItems(), owner = me.owner, bodyEl = owner.body, info = me.info, autoWidth = info.autoWidth, autoHeight = info.autoHeight, size = info.size, ln = items.length, padding = info.padding, border = info.border, frameSize = me.frameSize, item, i, box, rect; // If this Panel is inside an AutoContainerLayout, we will base all the calculations // around the height of the body and the width of the panel. if (autoHeight) { size.height = bodyEl.getHeight() + padding.top + border.top + padding.bottom + border.bottom + frameSize.top + frameSize.bottom; } else { size.height = target.getHeight(); } if (autoWidth) { size.width = bodyEl.getWidth() + padding.left + border.left + padding.right + border.right + frameSize.left + frameSize.right; } else { size.width = target.getWidth(); } info.bodyBox = { x: padding.left + frameSize.left, y: padding.top + frameSize.top, width: size.width - padding.left - border.left - padding.right - border.right - frameSize.left - frameSize.right, height: size.height - border.top - padding.top - border.bottom - padding.bottom - frameSize.top - frameSize.bottom }; // Loop over all the docked items for (i = 0; i < ln; i++) { item = items[i]; // The initBox method will take care of stretching and alignment // In some cases it will also layout the dock items to be able to // get a width or height measurement box = me.initBox(item); if (autoHeight === true) { box = me.adjustAutoBox(box, i); } else { box = me.adjustSizedBox(box, i); } // Save our box. This allows us to loop over all docked items and do all // calculations first. Then in one loop we will actually size and position // all the docked items that have changed. info.boxes.push(box); } this.calculateDockBoxes_running = false; }, /** * @protected * This method will adjust the position of the docked item and adjust the body box * accordingly. * @param {Object} box The box containing information about the width and height * of this docked item * @param {Number} index The index position of this docked item * @return {Object} The adjusted box */ adjustSizedBox : function(box, index) { var bodyBox = this.info.bodyBox, frameSize = this.frameSize, info = this.info, padding = info.padding, pos = box.type, border = info.border; switch (pos) { case 'top': box.y = bodyBox.y; break; case 'left': box.x = bodyBox.x; break; case 'bottom': box.y = (bodyBox.y + bodyBox.height) - box.height; break; case 'right': box.x = (bodyBox.x + bodyBox.width) - box.width; break; } if (box.ignoreFrame) { if (pos == 'bottom') { box.y += (frameSize.bottom + padding.bottom + border.bottom); } else { box.y -= (frameSize.top + padding.top + border.top); } if (pos == 'right') { box.x += (frameSize.right + padding.right + border.right); } else { box.x -= (frameSize.left + padding.left + border.left); } } // If this is not an overlaying docked item, we have to adjust the body box if (!box.overlay) { switch (pos) { case 'top': bodyBox.y += box.height; bodyBox.height -= box.height; break; case 'left': bodyBox.x += box.width; bodyBox.width -= box.width; break; case 'bottom': bodyBox.height -= box.height; break; case 'right': bodyBox.width -= box.width; break; } } return box; }, /** * @protected * This method will adjust the position of the docked item inside an AutoContainerLayout * and adjust the body box accordingly. * @param {Object} box The box containing information about the width and height * of this docked item * @param {Number} index The index position of this docked item * @return {Object} The adjusted box */ adjustAutoBox : function (box, index) { var info = this.info, owner = this.owner, bodyBox = info.bodyBox, size = info.size, boxes = info.boxes, boxesLn = boxes.length, pos = box.type, frameSize = this.frameSize, padding = info.padding, border = info.border, autoSizedCtLayout = info.autoSizedCtLayout, ln = (boxesLn < index) ? boxesLn : index, i, adjustBox; if (pos == 'top' || pos == 'bottom') { // This can affect the previously set left and right and bottom docked items for (i = 0; i < ln; i++) { adjustBox = boxes[i]; if (adjustBox.stretched && adjustBox.type == 'left' || adjustBox.type == 'right') { adjustBox.height += box.height; } else if (adjustBox.type == 'bottom') { adjustBox.y += box.height; } } } switch (pos) { case 'top': box.y = bodyBox.y; if (!box.overlay) { bodyBox.y += box.height; if (info.autoHeight) { size.height += box.height; } else { bodyBox.height -= box.height; } } break; case 'bottom': if (!box.overlay) { if (info.autoHeight) { size.height += box.height; } else { bodyBox.height -= box.height; } } box.y = (bodyBox.y + bodyBox.height); break; case 'left': box.x = bodyBox.x; if (!box.overlay) { bodyBox.x += box.width; if (info.autoWidth) { size.width += box.width; } else { bodyBox.width -= box.width; } } break; case 'right': if (!box.overlay) { if (info.autoWidth) { size.width += box.width; } else { bodyBox.width -= box.width; } } box.x = (bodyBox.x + bodyBox.width); break; } if (box.ignoreFrame) { if (pos == 'bottom') { box.y += (frameSize.bottom + padding.bottom + border.bottom); } else { box.y -= (frameSize.top + padding.top + border.top); } if (pos == 'right') { box.x += (frameSize.right + padding.right + border.right); } else { box.x -= (frameSize.left + padding.left + border.left); } } return box; }, /** * @protected * This method will create a box object, with a reference to the item, the type of dock * (top, left, bottom, right). It will also take care of stretching and aligning of the * docked items. * @param {Ext.Component} item The docked item we want to initialize the box for * @return {Object} The initial box containing width and height and other useful information */ initBox : function(item) { var me = this, bodyBox = me.info.bodyBox, horizontal = (item.dock == 'top' || item.dock == 'bottom'), owner = me.owner, frameSize = me.frameSize, info = me.info, padding = info.padding, border = info.border, box = { item: item, overlay: item.overlay, type: item.dock, offsets: Ext.Element.parseBox(item.offsets || {}), ignoreFrame: item.ignoreParentFrame }; // First we are going to take care of stretch and align properties for all four dock scenarios. if (item.stretch !== false) { box.stretched = true; if (horizontal) { box.x = bodyBox.x + box.offsets.left; box.width = bodyBox.width - (box.offsets.left + box.offsets.right); if (box.ignoreFrame) { box.width += (frameSize.left + frameSize.right + border.left + border.right + padding.left + padding.right); } item.setCalculatedSize(box.width - item.el.getMargin('lr'), undefined, owner); } else { box.y = bodyBox.y + box.offsets.top; box.height = bodyBox.height - (box.offsets.bottom + box.offsets.top); if (box.ignoreFrame) { box.height += (frameSize.top + frameSize.bottom + border.top + border.bottom + padding.top + padding.bottom); } item.setCalculatedSize(undefined, box.height - item.el.getMargin('tb'), owner); // At this point IE will report the left/right-docked toolbar as having a width equal to the // container's full width. Forcing a repaint kicks it into shape so it reports the correct width. if (!Ext.supports.ComputedStyle) { item.el.repaint(); } } } else { item.doComponentLayout(); box.width = item.getWidth() - (box.offsets.left + box.offsets.right); box.height = item.getHeight() - (box.offsets.bottom + box.offsets.top); box.y += box.offsets.top; if (horizontal) { box.x = (item.align == 'right') ? bodyBox.width - box.width : bodyBox.x; box.x += box.offsets.left; } } // If we haven't calculated the width or height of the docked item yet // do so, since we need this for our upcoming calculations if (box.width === undefined) { box.width = item.getWidth() + item.el.getMargin('lr'); } if (box.height === undefined) { box.height = item.getHeight() + item.el.getMargin('tb'); } return box; }, /** * @protected * Returns an array containing all the <b>visible</b> docked items inside this layout's owner Panel * @return {Array} An array containing all the <b>visible</b> docked items of the Panel */ getLayoutItems : function() { var it = this.owner.getDockedItems(), ln = it.length, i = 0, result = []; for (; i < ln; i++) { if (it[i].isVisible(true)) { result.push(it[i]); } } return result; }, /** * @protected * Render the top and left docked items before any existing DOM nodes in our render target, * and then render the right and bottom docked items after. This is important, for such things * as tab stops and ARIA readers, that the DOM nodes are in a meaningful order. * Our collection of docked items will already be ordered via Panel.getDockedItems(). */ renderItems: function(items, target) { var cns = target.dom.childNodes, cnsLn = cns.length, ln = items.length, domLn = 0, i, j, cn, item; // Calculate the number of DOM nodes in our target that are not our docked items for (i = 0; i < cnsLn; i++) { cn = Ext.get(cns[i]); for (j = 0; j < ln; j++) { item = items[j]; if (item.rendered && (cn.id == item.el.id || cn.contains(item.el.id))) { break; } } if (j === ln) { domLn++; } } // Now we go through our docked items and render/move them for (i = 0, j = 0; i < ln; i++, j++) { item = items[i]; // If we're now at the right/bottom docked item, we jump ahead in our // DOM position, just past the existing DOM nodes. // // TODO: This is affected if users provide custom weight values to their // docked items, which puts it out of (t,l,r,b) order. Avoiding a second // sort operation here, for now, in the name of performance. getDockedItems() // needs the sort operation not just for this layout-time rendering, but // also for getRefItems() to return a logical ordering (FocusManager, CQ, et al). if (i === j && (item.dock === 'right' || item.dock === 'bottom')) { j += domLn; } // Same logic as Layout.renderItems() if (item && !item.rendered) { this.renderItem(item, target, j); } else if (!this.isValidParent(item, target, j)) { this.moveItem(item, target, j); } } }, /** * @protected * This function will be called by the dockItems method. Since the body is positioned absolute, * we need to give it dimensions and a position so that it is in the middle surrounded by * docked items * @param {Object} box An object containing new x, y, width and height values for the * Panel's body */ setBodyBox : function(box) { var me = this, owner = me.owner, body = owner.body, info = me.info, bodyMargin = info.bodyMargin, padding = info.padding, border = info.border, frameSize = me.frameSize; // Panel collapse effectively hides the Panel's body, so this is a no-op. if (owner.collapsed) { return; } if (Ext.isNumber(box.width)) { box.width -= bodyMargin.left + bodyMargin.right; } if (Ext.isNumber(box.height)) { box.height -= bodyMargin.top + bodyMargin.bottom; } me.setElementSize(body, box.width, box.height); if (Ext.isNumber(box.x)) { body.setLeft(box.x - padding.left - frameSize.left); } if (Ext.isNumber(box.y)) { body.setTop(box.y - padding.top - frameSize.top); } }, /** * @protected * We are overriding the Ext.layout.Layout configureItem method to also add a class that * indicates the position of the docked item. We use the itemCls (x-docked) as a prefix. * An example of a class added to a dock: right item is x-docked-right * @param {Ext.Component} item The item we are configuring */ configureItem : function(item, pos) { this.callParent(arguments); if (item.dock == 'top' || item.dock == 'bottom') { item.layoutManagedWidth = 1; item.layoutManagedHeight = 2; } else { item.layoutManagedWidth = 2; item.layoutManagedHeight = 1; } item.addCls(Ext.baseCSSPrefix + 'docked'); item.addClsWithUI('docked-' + item.dock); }, afterRemove : function(item) { this.callParent(arguments); if (this.itemCls) { item.el.removeCls(this.itemCls + '-' + item.dock); } var dom = item.el.dom; if (!item.destroying && dom) { dom.parentNode.removeChild(dom); } this.childrenChanged = true; } });