/** * 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. * @private */ Ext.define('Ext.layout.component.Dock', { /* Begin Definitions */ extend: 'Ext.layout.component.Component', alias: 'layout.dock', alternateClassName: 'Ext.layout.component.AbstractDock', /* End Definitions */ type: 'dock', initializedBorders: -1, horizontalCollapsePolicy: { width: true, x: true }, verticalCollapsePolicy: { height: true, y: true }, finishRender: function () { var me = this, target, items; me.callParent(); target = me.getRenderTarget(); items = me.getDockedItems(); me.finishRenderItems(target, items); }, isItemBoxParent: function (itemContext) { return true; }, isItemShrinkWrap: function (item) { return true; }, dockOpposites: { top: 'bottom', right: 'left', bottom: 'top', left: 'right' }, handleItemBorders: function() { var me = this, owner = me.owner, borders, docked, oldBorders = me.borders, opposites = me.dockOpposites, currentGeneration = owner.dockedItems.generation, i, ln, item, dock, side, collapsed = me.collapsed; if (me.initializedBorders == currentGeneration || (owner.border && !owner.manageBodyBorders)) { return; } me.initializedBorders = currentGeneration; // Borders have to be calculated using expanded docked item collection. me.collapsed = false; docked = me.getLayoutItems(); me.collapsed = collapsed; borders = { top: [], right: [], bottom: [], left: [] }; 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) { owner.removeBodyCls(Ext.baseCSSPrefix + 'docked-noborder-' + side); } } else if (oldBorders[side].satisfied) { owner.setBodyStyle('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) { owner.addBodyCls(Ext.baseCSSPrefix + 'docked-noborder-' + side); } } else if (borders[side].satisfied) { owner.setBodyStyle('border-' + side + '-width', '1px'); } } } me.borders = borders; }, beginLayout: function(ownerContext) { var me = this, owner = me.owner, docked = me.getLayoutItems(), layoutContext = ownerContext.context, dockedItemCount = docked.length, collapsedVert = false, collapsedHorz = false, dockedItems, i, item, itemContext, offsets, collapsed; me.callParent(arguments); me.handleItemBorders(); // Cache the children as ContextItems (like a Container). Also setup to handle // collapsed state: collapsed = owner.getCollapsed(); if (Ext.isDefined(me.lastCollapsedState) && (collapsed !== me.lastCollapsedState)) { // If we are collapsing... if (me.owner.collapsed) { ownerContext.isCollapsingOrExpanding = 1; // Add the collapsed class now, so that collapsed CSS rules are applied before measurements are taken by the layout. owner.addClsWithUI(owner.collapsedCls); } else { ownerContext.isCollapsingOrExpanding = 2; // Remove the collapsed class now, before layout calculations are done. owner.removeClsWithUI(owner.collapsedCls); ownerContext.lastCollapsedState = me.lastCollapsedState; } } me.lastCollapsedState = collapsed; ownerContext.dockedItems = dockedItems = []; for (i = 0; i < dockedItemCount; i++) { item = docked[i]; itemContext = layoutContext.getCmp(item); itemContext.dockedAt = { x: 0, y: 0 }; itemContext.offsets = offsets = Ext.Element.parseBox(item.offsets || {}); offsets.width = offsets.left + offsets.right; offsets.height = offsets.top + offsets.bottom; dockedItems.push(itemContext); } if (owner.collapsed) { if (owner.collapsedVertical()) { collapsedVert = true; ownerContext.measureDimensions = 1; } else { collapsedHorz = true; ownerContext.measureDimensions = 2; } } ownerContext.collapsedVert = collapsedVert; ownerContext.collapsedHorz = collapsedHorz; ownerContext.bodyContext = ownerContext.getEl('body'); }, beginLayoutCycle: function(ownerContext) { var me = this, docked = ownerContext.dockedItems, len = docked.length, owner = me.owner, frameBody = owner.frameBody, lastHeightModel = me.lastHeightModel, i, item, dock; me.callParent(arguments); // If we are collapsed, we want to auto-layout using the placeholder/expander // instead of the normal items/dockedItems. This must be done here since we could // be in a box layout w/stretchmax which sets the width/heightModel to allow it to // control the size. if (ownerContext.collapsedVert) { ownerContext.heightModel = me.sizeModels.shrinkWrap; } else if (ownerContext.collapsedHorz) { ownerContext.widthModel = me.sizeModels.shrinkWrap; } if (lastHeightModel && lastHeightModel.shrinkWrap && !ownerContext.heightModel.shrinkWrap && !me.owner.manageHeight) { owner.body.dom.style.marginBottom = ''; } if (ownerContext.widthModel.auto) { if (ownerContext.widthModel.shrinkWrap) { owner.el.setWidth(null); } owner.body.setWidth(null); if (frameBody) { frameBody.setWidth(null); } } if (ownerContext.heightModel.auto) { owner.body.setHeight(null); //owner.el.setHeight(null); Disable this for now if (frameBody) { frameBody.setHeight(null); } } // Each time we begin (2nd+ would be due to invalidate) we need to publish the // known contentWidth/Height if we are collapsed: if (ownerContext.collapsedVert) { ownerContext.setContentHeight(0); } else if (ownerContext.collapsedHorz) { ownerContext.setContentWidth(0); } // dock: 'right' items, when a panel gets narrower get "squished". Moving them to // left:0px avoids this! for (i = 0; i < len; i++) { item = docked[i].target; dock = item.dock; if (dock == 'right') { item.el.setLeft(0); } else if (dock != 'left') { continue; } // TODO - clear width/height? } }, calculate: function (ownerContext) { var me = this, measure = me.measureAutoDimensions(ownerContext, ownerContext.measureDimensions), state = ownerContext.state, horzDone = state.horzDone, vertDone = state.vertDone, bodyContext = ownerContext.bodyContext, horz, vert, forward, backward; // make sure we can use these value w/o calling methods to get them ownerContext.borderInfo || ownerContext.getBorderInfo(); ownerContext.paddingInfo || ownerContext.getPaddingInfo(); ownerContext.framingInfo || ownerContext.getFraming(); bodyContext.borderInfo || bodyContext.getBorderInfo(); bodyContext.paddingInfo || bodyContext.getPaddingInfo(); // Start the axes so they are ready to proceed inwards (fixed-size) or outwards // (shrinkWrap) and stash key property names as well: horz = !horzDone && me.createAxis(ownerContext, measure.contentWidth, ownerContext.widthModel, 'left', 'right', 'x', 'width', 'Width', ownerContext.collapsedHorz); vert = !vertDone && me.createAxis(ownerContext, measure.contentHeight, ownerContext.heightModel, 'top', 'bottom', 'y', 'height', 'Height', ownerContext.collapsedVert); // We iterate forward and backward over the dockedItems at the same time based on // whether an axis is shrinkWrap or fixed-size. For a fixed-size axis, the outer box // axis is allocated to docked items in forward order and is reduced accordingly. // To handle a shrinkWrap axis, the box starts at the inner (body) size and is used to // size docked items in backwards order. This is because the last docked item shares // an edge with the body. The item size is used to adjust the shrinkWrap axis outwards // until the first docked item (at the outermost edge) is processed. This backwards // order ensures that docked items never get an incorrect size for any dimension. for (forward = 0, backward = ownerContext.dockedItems.length; backward--; ++forward) { if (horz) { me.dockChild(ownerContext, horz, backward, forward); } if (vert) { me.dockChild(ownerContext, vert, backward, forward); } } if (horz && me.finishAxis(ownerContext, horz)) { state.horzDone = horzDone = horz; } if (vert && me.finishAxis(ownerContext, vert)) { state.vertDone = vertDone = vert; } // Once all items are docked, the final size of the outer panel or inner body can // be determined. If we can determine both width and height, we are done. if (horzDone && vertDone && me.finishConstraints(ownerContext, horzDone, vertDone)) { // Size information is published as we dock items but position is hard to do // that way (while avoiding published multiple times) so we publish all the // positions at the end. me.finishPositions(ownerContext, horzDone, vertDone); } else { me.done = false; } }, /** * Creates an axis object given the particulars. * @private */ createAxis: function (ownerContext, contentSize, sizeModel, dockBegin, dockEnd, posProp, sizeProp, sizePropCap, collapsedAxis) { var begin = 0, owner = this.owner, maxSize = owner['max' + sizePropCap], minSize = owner['min' + sizePropCap] || 0, hasMaxSize = maxSize != null, // exactly the same as "maxSize !== null && maxSize !== undefined" constrainedSize = ownerContext.state['constrained' + sizePropCap], isConstrainedSize = constrainedSize != null, setSize = 'set' + sizePropCap, border, bodyContext, frameSize, padding, end; if (sizeModel.shrinkWrap && !isConstrainedSize) { // End position before adding docks around the content is content size plus the body borders in this axis. // If collapsed in this axis, the body borders will not be shown. if (collapsedAxis) { end = 0; } else { bodyContext = ownerContext.bodyContext; end = contentSize + bodyContext.borderInfo[sizeProp]; } } else { border = ownerContext.borderInfo; frameSize = ownerContext.framingInfo; padding = ownerContext.paddingInfo; if (isConstrainedSize) { end = constrainedSize; sizeModel = this.sizeModels.calculated; // behave as if calculated ownerContext[setSize](constrainedSize); } else { end = ownerContext.getProp(sizeProp); } end -= border[dockEnd] + padding[dockEnd] + frameSize[dockEnd]; begin = border[dockBegin] + padding[dockBegin] + frameSize[dockBegin]; } return { shrinkWrap: sizeModel.shrinkWrap, sizeModel: sizeModel, // An axis tracks start and end+1 px positions. eg 0 to 10 for 10px high begin: begin, end: end, collapsed: collapsedAxis, horizontal: posProp == 'x', ignoreFrameBegin: false, ignoreFrameEnd: false, initialSize: end - begin, hasMinMaxConstraints: (minSize || hasMaxSize) && sizeModel.shrinkWrap, isConstrainedSize: isConstrainedSize, minSize: minSize, maxSize: hasMaxSize ? maxSize : 1e9, bodyPosProp: this.owner.manageHeight ? posProp : ('margin-' + dockBegin), // 'margin-left' or 'margin-top' dockBegin: dockBegin, // 'left' or 'top' dockEnd: dockEnd, // 'right' or 'end' posProp: posProp, // 'x' or 'y' sizeProp: sizeProp, // 'width' or 'height' sizePropCap: sizePropCap, // 'Width' or 'Height' setSize: setSize, dockedPixelsEnd: 0 }; }, /** * Docks a child item on the specified axis. This boils down to determining if the item * is docked at the "beginning" of the axis ("left" if horizontal, "top" if vertical), * the "end" of the axis ("right" if horizontal, "bottom" if vertical) or stretches * along the axis ("top" or "bottom" if horizontal, "left" or "right" if vertical). It * also has to differentiate between fixed and shrinkWrap sized dimensions. * @private */ dockChild: function (ownerContext, axis, backward, forward) { var me = this, itemContext = ownerContext.dockedItems[axis.shrinkWrap ? backward : forward], item = itemContext.target, dock = item.dock, // left/top/right/bottom pos; if(item.ignoreParentFrame && ownerContext.isCollapsingOrExpanding) { // collapsed window header margins may differ from expanded window header margins // so we need to make sure the old cached values are not used in axis calculations itemContext.clearMarginCache(); } if (dock == axis.dockBegin) { if (axis.shrinkWrap) { pos = me.dockOutwardBegin(ownerContext, itemContext, item, axis); } else { pos = me.dockInwardBegin(ownerContext, itemContext, item, axis); } } else if (dock == axis.dockEnd) { if (axis.shrinkWrap) { pos = me.dockOutwardEnd(ownerContext, itemContext, item, axis); } else { pos = me.dockInwardEnd(ownerContext, itemContext, item, axis); } } else { pos = me.dockStretch(ownerContext, itemContext, item, axis); } itemContext.dockedAt[axis.posProp] = pos; }, /** * Docks an item on a fixed-size axis at the "beginning". The "beginning" of the horizontal * axis is "left" and the vertical is "top". For a fixed-size axis, the size works from * the outer element (the panel) towards the body. * @private */ dockInwardBegin: function (ownerContext, itemContext, item, axis) { var pos = axis.begin, sizeProp = axis.sizeProp, size, dock; if (item.ignoreParentFrame) { dock = item.dock; pos -= ownerContext.borderInfo[dock] + ownerContext.paddingInfo[dock] + ownerContext.framingInfo[dock]; } if (!item.overlay) { size = itemContext.getProp(sizeProp) + itemContext.getMarginInfo()[sizeProp]; axis.begin += size; } return pos; }, /** * Docks an item on a fixed-size axis at the "end". The "end" of the horizontal axis is * "right" and the vertical is "bottom". * @private */ dockInwardEnd: function (ownerContext, itemContext, item, axis) { var sizeProp = axis.sizeProp, size = itemContext.getProp(sizeProp) + itemContext.getMarginInfo()[sizeProp], pos = axis.end - size; if (!item.overlay) { axis.end = pos; } if (item.ignoreParentFrame) { pos += ownerContext.borderInfo[item.dock] + ownerContext.paddingInfo[item.dock] + ownerContext.framingInfo[item.dock]; } return pos; }, /** * Docks an item on a shrinkWrap axis at the "beginning". The "beginning" of the horizontal * axis is "left" and the vertical is "top". For a shrinkWrap axis, the size works from * the body outward to the outermost element (the panel). * * During the docking process, coordinates are allowed to be negative. We start with the * body at (0,0) so items docked "top" or "left" will simply be assigned negative x/y. In * the {@link #finishPositions} method these are corrected and framing is added. This way * the correction is applied as a simple translation of delta x/y on all coordinates to * bring the origin back to (0,0). * @private */ dockOutwardBegin: function (ownerContext, itemContext, item, axis) { var pos = axis.begin, sizeProp = axis.sizeProp, dock, size; if (axis.collapsed) { axis.ignoreFrameBegin = axis.ignoreFrameEnd = true; } else if (item.ignoreParentFrame) { dock = item.dock; pos -= ownerContext.borderInfo[dock] + ownerContext.paddingInfo[dock] + ownerContext.framingInfo[dock]; axis.ignoreFrameBegin = true; } if (!item.overlay) { size = itemContext.getProp(sizeProp) + itemContext.getMarginInfo()[sizeProp]; pos -= size; axis.begin = pos; } return pos; }, /** * Docks an item on a shrinkWrap axis at the "end". The "end" of the horizontal axis is * "right" and the vertical is "bottom". * @private */ dockOutwardEnd: function (ownerContext, itemContext, item, axis) { var pos = axis.end, sizeProp = axis.sizeProp, dock, size; size = itemContext.getProp(sizeProp) + itemContext.getMarginInfo()[sizeProp]; if (axis.collapsed) { axis.ignoreFrameBegin = axis.ignoreFrameEnd = true; } else if (item.ignoreParentFrame) { dock = item.dock; pos += ownerContext.borderInfo[dock] + ownerContext.paddingInfo[dock] + ownerContext.framingInfo[dock]; axis.ignoreFrameEnd = true; } if (!item.overlay) { axis.end = pos + size; axis.dockedPixelsEnd += size; } return pos; }, /** * Docks an item that might stretch across an axis. This is done for dock "top" and * "bottom" items on the horizontal axis and dock "left" and "right" on the vertical. * @private */ dockStretch: function (ownerContext, itemContext, item, axis) { var dock = item.dock, // left/top/right/bottom (also used to index padding/border) sizeProp = axis.sizeProp, // 'width' or 'height' horizontal = dock == 'top' || dock == 'bottom', offsets = itemContext.offsets, border = ownerContext.borderInfo, padding = ownerContext.paddingInfo, endProp = horizontal ? 'right' : 'bottom', startProp = horizontal ? 'left' : 'top', pos = axis.begin + offsets[startProp], margin, size, framing; if (item.stretch !== false) { size = axis.end - pos - offsets[endProp]; if (item.ignoreParentFrame) { framing = ownerContext.framingInfo; pos -= border[startProp] + padding[startProp] + framing[startProp]; size += border[sizeProp] + padding[sizeProp] + framing[sizeProp]; } margin = itemContext.getMarginInfo(); size -= margin[sizeProp]; itemContext[axis.setSize](size); } return pos; }, /** * Finishes the calculation of an axis by determining its size. In non-shrink-wrap * cases, this is also where we set the body size. * @private */ finishAxis: function (ownerContext, axis) { var size = axis.end - axis.begin, setSizeMethod = axis.setSize, beginName = axis.dockBegin, // left or top endName = axis.dockEnd, // right or bottom border = ownerContext.borderInfo, padding = ownerContext.paddingInfo, framing = ownerContext.framingInfo, frameSize = padding[beginName] + border[beginName] + framing[beginName], bodyContext = ownerContext.bodyContext, bodyPos, bodySize, dirty; if (axis.shrinkWrap) { // Since items docked left/top on a shrinkWrap axis go into negative coordinates, // we apply a delta to all coordinates to adjust their relative origin back to // (0,0). axis.delta = -axis.begin; // either 0 or a positive number bodySize = axis.initialSize; if (axis.ignoreFrameBegin) { axis.delta -= border[beginName]; bodyPos = -axis.begin - frameSize; } else { size += frameSize; axis.delta += padding[beginName] + framing[beginName]; bodyPos = -axis.begin; } if (!axis.ignoreFrameEnd) { size += padding[endName] + border[endName] + framing[endName]; } axis.size = size; // we have to wait for min/maxWidth/Height processing if (!axis.horizontal && !this.owner.manageHeight) { // the height of the bodyEl will give the proper height to the outerEl so // we don't need to set heights in the DOM dirty = false; } } else { // For a fixed-size axis, we started at the outer box and already have the // proper origin... almost... except for the owner's border. axis.delta = -border[axis.dockBegin]; // 'left' or 'top' // Body size is remaining space between ends of Axis. bodySize = size; bodyPos = axis.begin - frameSize; } bodyContext[setSizeMethod](bodySize, dirty); bodyContext.setProp(axis.bodyPosProp, bodyPos); return !isNaN(size); }, /** * Finishes processing of each axis by applying the min/max size constraints. * @private */ finishConstraints: function (ownerContext, horz, vert) { var horzTooSmall = horz.size < horz.minSize, horzTooBig = horz.size > horz.maxSize, vertTooSmall = vert.size < vert.minSize, vertTooBig = vert.size > vert.maxSize, state = ownerContext.state, ret = true, configured = this.sizeModels.configured, dirty; // Analysis of the potential constraint feedback given the possibilities for the // various constraints: // // #1: h < min, v > max : (Expand width, Shrink height) // In general, making the panel wider could possibly cause the content to // be shorter thereby eliminating the need to reduce the height, but we // just measured the content width given effectively infinite space in // which to expand. This means it is very unlikey (if not impossible) for // the height to change given more width, so no special concerns. // // #2: h < min, v < min : (Expand width, Expand height) // Making panel bigger in both directions has no concerns. Again, making // the panel wider could only reduce height, so the need to expand the // height would remain. // // #3: h > max, v > max : (Shrink width, Shrink height) // Making the panel narrower cannot cause the maxHeight violation to go // away, so no special concerns. // // #4: h > max, v < min : (Shrink width, Expand height) // Finally an interesting case! Shrinking the width can cause the height // to increase. We cannot know if it will increase enough to avoid the // minHeight violation, but if we apply the minHeight constraint, we will // not be able to tell that we should not have done so. Which means, in // this case, we must only apply the maxWidth constraint, allowing the // layout to rerun and perhaps apply the minHeight constraint next time. // NOTE: if we are already applying a constraint on a given axis, that axis will // *not* be in shrinkWrap mode. if (horz.shrinkWrap && horzTooBig && vert.shrinkWrap && vertTooSmall) { // if (#4) state.constrainedWidth = horz.maxSize; ownerContext.widthModel = configured; // via maxWidth config ret = false; } else { if (horz.shrinkWrap) { if (horzTooBig) { state.constrainedWidth = horz.maxSize; ownerContext.widthModel = configured; ret = false; } else if (horzTooSmall) { state.constrainedWidth = horz.minSize; ownerContext.widthModel = configured; ret = false; } } if (vert.shrinkWrap) { if (vertTooBig) { state.constrainedHeight = vert.maxSize; ownerContext.heightModel = configured; ret = false; } else if (vertTooSmall) { state.constrainedHeight = vert.minSize; ownerContext.heightModel = configured; ret = false; } } } if (ret) { if (horz.shrinkWrap) { ownerContext.setWidth(horz.size); } if (vert.shrinkWrap) { if (!ownerContext.collapsedVert && !this.owner.manageHeight) { // height of the outerEl is provided by the height (including margins) of // the bodyEl, so this value does not need to be written to the DOM: dirty = false; // so long as we set top and bottom margins on the bodyEl! ownerContext.bodyContext.setProp('margin-bottom', vert.dockedPixelsEnd); } ownerContext.setHeight(vert.size, dirty); } } else { ownerContext.invalidate({ state: { constrainedWidth: state.constrainedWidth, constrainedHeight: state.constrainedHeight } }); } return ret; }, /** * Finishes the calculation by setting positions on the body and all of the items. * @private */ finishPositions: function (ownerContext, horz, vert) { var dockedItems = ownerContext.dockedItems, length = dockedItems.length, deltaX = horz.delta, deltaY = vert.delta, index, itemContext; for (index = 0; index < length; ++index) { itemContext = dockedItems[index]; itemContext.setProp('x', deltaX + itemContext.dockedAt.x); itemContext.setProp('y', deltaY + itemContext.dockedAt.y); } }, finishedLayout: function(ownerContext) { var me = this, target = ownerContext.target; me.callParent(arguments); if (!ownerContext.animatePolicy) { if (ownerContext.isCollapsingOrExpanding === 1) { target.afterCollapse(false); } else if (ownerContext.isCollapsingOrExpanding === 2) { target.afterExpand(false); } } }, getAnimatePolicy: function(ownerContext) { var me = this, lastCollapsedState, policy; if (ownerContext.isCollapsingOrExpanding == 1) { lastCollapsedState = me.lastCollapsedState; } else if (ownerContext.isCollapsingOrExpanding == 2) { lastCollapsedState = ownerContext.lastCollapsedState; } if (lastCollapsedState == 'left' || lastCollapsedState == 'right') { policy = me.horizontalCollapsePolicy; } else if (lastCollapsedState == 'top' || lastCollapsedState == 'bottom') { policy = me.verticalCollapsePolicy; } return policy; }, /** * Retrieve an ordered and/or filtered array of all docked Components. * @param {String} [order='render'] The desired ordering of the items ('render' or 'visual'). * @param {Boolean} [beforeBody] An optional flag to limit the set of items to only those * before the body (true) or after the body (false). All components are returned by * default. * @return {Ext.Component[]} An array of components. * @protected */ getDockedItems: function(order, beforeBody) { var me = this, all = me.owner.dockedItems.items, sort = all && all.length && order !== false, renderOrder, dock, dockedItems, i, isBefore, length; if (beforeBody == null) { dockedItems = sort ? all.slice() : all; } else { dockedItems = []; for (i = 0, length = all.length; i < length; ++i) { dock = all[i].dock; isBefore = (dock == 'top' || dock == 'left'); if (beforeBody ? isBefore : !isBefore) { dockedItems.push(all[i]); } } sort = sort && dockedItems.length; } if (sort) { renderOrder = (order = order || 'render') == 'render'; Ext.Array.sort(dockedItems, function(a, b) { var aw, bw; // If the two items are on opposite sides of the body, they must not be sorted by any weight value: // For rendering purposes, left/top *always* sorts before right/bottom if (renderOrder && ((aw = me.owner.dockOrder[a.dock]) !== (bw = me.owner.dockOrder[b.dock]))) { // The two dockOrder values cancel out when two items are on opposite sides. if (!(aw + bw)) { return aw - bw; } } aw = me.getItemWeight(a, order); bw = me.getItemWeight(b, order); if ((aw !== undefined) && (bw !== undefined)) { return aw - bw; } return 0; }); } return dockedItems || []; }, getItemWeight: function (item, order) { var weight = item.weight || this.owner.defaultDockWeights[item.dock]; return weight[order] || weight; }, /** * @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 me = this, items, itemCount, item, i, result; if (me.owner.collapsed) { result = me.owner.getCollapsedDockedItems(); } else { items = me.getDockedItems('visual'); itemCount = items.length; result = []; for (i = 0; i < itemCount; i++) { item = items[i]; if (!item.hidden) { result.push(item); } } } return result; }, // Content size includes padding but not borders, so subtract them off measureContentWidth: function (ownerContext) { var bodyContext = ownerContext.bodyContext; return bodyContext.el.getWidth() - bodyContext.getBorderInfo().width; }, measureContentHeight: function (ownerContext) { var bodyContext = ownerContext.bodyContext; return bodyContext.el.getHeight() - bodyContext.getBorderInfo().height; }, redoLayout: function(ownerContext) { var me = this, owner = me.owner; // If we are collapsing... if (ownerContext.isCollapsingOrExpanding == 1) { if (owner.reExpander) { owner.reExpander.el.show(); } // Add the collapsed class now, so that collapsed CSS rules are applied before measurements are taken by the layout. owner.addClsWithUI(owner.collapsedCls); ownerContext.redo(true); } else if (ownerContext.isCollapsingOrExpanding == 2) { // Remove the collapsed class now, before layout calculations are done. owner.removeClsWithUI(owner.collapsedCls); ownerContext.bodyContext.redo(); } }, // @private override inherited. // We need to render in the correct order, top/left before bottom/right renderChildren: function() { var me = this, items = me.getDockedItems(), target = me.getRenderTarget(); me.renderItems(items, target); }, /** * @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 me = this, dockedItemCount = items.length, itemIndex = 0, correctPosition = 0, item, staticNodeCount = 0, targetNodes = me.getRenderTarget().dom.childNodes, targetChildCount = targetNodes.length, i, j, targetChildNode, item; // Calculate the number of DOM nodes in our target that are not our docked items for (i = 0, j = 0; i < targetChildCount; i++) { targetChildNode = targetNodes[i]; if (Ext.fly(targetChildNode).hasCls('x-resizable-handle')) { break; } for (j = 0; j < dockedItemCount; j++) { item = items[j]; if (item.rendered && item.el.dom === targetChildNode) { break; } } // Walked off the end of the docked items without matching the found child node; // Then it's a static node. if (j === dockedItemCount) { staticNodeCount++; } } // Now we go through our docked items and render/move them for (; itemIndex < dockedItemCount; itemIndex++, correctPosition++) { item = items[itemIndex]; // If we're now at the first right/bottom docked item, we jump over the body element. // // 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 (itemIndex === correctPosition && (item.dock === 'right' || item.dock === 'bottom')) { correctPosition += staticNodeCount; } // Same logic as Layout.renderItems() if (item && !item.rendered) { me.renderItem(item, target, correctPosition); } else if (!me.isValidParent(item, target, correctPosition)) { me.moveItem(item, target, correctPosition); } } }, undoLayout: function(ownerContext) { var me = this, owner = me.owner; // If we are collapsing... if (ownerContext.isCollapsingOrExpanding == 1) { // We do not want to see the re-expander header until the final collapse is complete if (owner.reExpander) { owner.reExpander.el.hide(); } // Add the collapsed class now, so that collapsed CSS rules are applied before measurements are taken by the layout. owner.removeClsWithUI(owner.collapsedCls); ownerContext.undo(true); } else if (ownerContext.isCollapsingOrExpanding == 2) { // Remove the collapsed class now, before layout calculations are done. owner.addClsWithUI(owner.collapsedCls); ownerContext.bodyContext.undo(); } }, sizePolicy: { nostretch: { setsWidth: 0, setsHeight: 0 }, stretchH: { setsWidth: 1, setsHeight: 0 }, stretchV: { setsWidth: 0, setsHeight: 1 }, // Circular dependency with partial auto-sized panels: // // If we have an autoHeight docked item being stretched horizontally (top/bottom), // that stretching will determine its width and its width must be set before its // autoHeight can be determined. If that item is docked in an autoWidth panel, the // body will need its height set before it can determine its width, but the height // of the docked item is needed to subtract from the panel height in order to set // the body height. // // This same pattern occurs with autoHeight panels with autoWidth docked items on // left or right. If the panel is fully auto or fully fixed, these problems don't // come up because there is no dependency between the dimensions. // // Cutting the Gordian Knot: In these cases, we have to allow something to measure // itself without full context. This is OK as long as the managed dimension doesn't // effect the auto-dimension, which is often the case for things like toolbars. The // managed dimension only effects overflow handlers and such and does not change the // auto-dimension. To encourage the item to measure itself without waiting for the // managed dimension, we have to tell it that the layout will also be reading that // dimension. This is similar to how stretchmax works. autoStretchH: { readsWidth: 1, setsWidth: 1, setsHeight: 0 }, autoStretchV: { readsHeight: 1, setsWidth: 0, setsHeight: 1 } }, getItemSizePolicy: function (item) { var policy = this.sizePolicy, dock, vertical; if (item.stretch === false) { return policy.nostretch; } dock = item.dock; vertical = (dock == 'left' || dock == 'right'); /* owner = this.owner; autoWidth = !owner.isFixedWidth(); autoHeight = !owner.isFixedHeight(); if (autoWidth !== autoHeight) { // if (partial auto) // see above... if (vertical) { if (autoHeight) { return policy.autoStretchV; } } else if (autoWidth) { return policy.autoStretchH; } }*/ if (vertical) { return policy.stretchV; } return policy.stretchH; }, /** * @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; } });