X-Git-Url: http://git.ithinksw.org/extjs.git/blobdiff_plain/0494b8d9b9bb03ab6c22b34dae81261e3cd7e3e6..7a654f8d43fdb43d78b63d90528bed6e86b608cc:/src/layout/component/AbstractDock.js diff --git a/src/layout/component/AbstractDock.js b/src/layout/component/AbstractDock.js new file mode 100644 index 00000000..5a7f1054 --- /dev/null +++ b/src/layout/component/AbstractDock.js @@ -0,0 +1,718 @@ +/** + * @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.core.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) { + 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: {} + }; + + 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 === undefined || height === null || width === undefined || 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 === undefined || height === null) && (width === undefined || width === null)) { + autoHeight = true; + autoWidth = true; + me.setTargetSize(null); + me.setBodyBox({width: null, height: null}); + } + // Auto-height + else if (height === undefined || 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; + 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; + } + + // 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(autoWidth, autoHeight); + me.setTargetSize(info.size.width, info.size.height); + } + else { + me.setTargetSize(width, height); + me.dockItems(); + } + me.callParent(arguments); + }, + + /** + * @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(autoWidth, autoHeight) { + this.calculateDockBoxes(autoWidth, autoHeight); + + // 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, + boxes = info.boxes, + ln = boxes.length, + dock, i; + + // 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]; + dock.item.setPosition(dock.x, dock.y); + if ((autoWidth || autoHeight) && dock.layout && dock.layout.isLayout) { + // Auto-Sized so have the container layout notify the component layout. + dock.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(autoWidth, autoHeight) { + // 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, + 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); + } + }, + + /** + * @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, + 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; + } + size.height += box.height; + break; + + case 'bottom': + box.y = (bodyBox.y + bodyBox.height); + size.height += box.height; + break; + + case 'left': + box.x = bodyBox.x; + if (!box.overlay) { + bodyBox.x += box.width; + if (autoSizedCtLayout) { + size.width += box.width; + } else { + bodyBox.width -= box.width; + } + } + break; + + case 'right': + if (!box.overlay) { + if (autoSizedCtLayout) { + 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.core.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 visible docked items inside this layout's owner Panel + * @return {Array} An array containing all the visible 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.down('#' + 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); + + 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; + } +}); \ No newline at end of file