ag-grid
Version:
Advanced Data Grid / Data Table supporting Javascript / React / AngularJS / Web Components
193 lines (159 loc) • 9.23 kB
text/typescript
import {Bean, Context, Autowired} from "../../context/context";
import {RowNode} from "../../entities/rowNode";
import {Utils as _} from "../../utils";
import {GridOptionsWrapper} from "../../gridOptionsWrapper";
import {SelectionController} from "../../selectionController";
import {EventService} from "../../eventService";
import {IRowNodeStage, StageExecuteParams} from "../../interfaces/iRowNodeStage";
import {ColumnController} from "../../columnController/columnController";
export class FlattenStage implements IRowNodeStage {
private gridOptionsWrapper: GridOptionsWrapper;
private selectionController: SelectionController;
private eventService: EventService;
private context: Context;
private columnController: ColumnController;
public execute(params: StageExecuteParams): RowNode[] {
let rootNode = params.rowNode;
// even if not doing grouping, we do the mapping, as the client might
// of passed in data that already has a grouping in it somewhere
let result: RowNode[] = [];
// putting value into a wrapper so it's passed by reference
let nextRowTop: NumberWrapper = {value: 0};
let skipLeafNodes = this.columnController.isPivotMode();
// if we are reducing, and not grouping, then we want to show the root node, as that
// is where the pivot values are
let showRootNode = skipLeafNodes && rootNode.leafGroup;
let topList = showRootNode ? [rootNode] : rootNode.childrenAfterSort;
// set all row tops to null, then set row tops on all visible rows. if we don't
// do this, then the algorithm below only sets row tops, old row tops from old rows
// will still lie around
this.resetRowTops(rootNode);
this.recursivelyAddToRowsToDisplay(topList, result, nextRowTop, skipLeafNodes, 0);
// don't show total footer when showRootNode is true (i.e. in pivot mode and no groups)
let includeGroupTotalFooter = !showRootNode && this.gridOptionsWrapper.isGroupIncludeTotalFooter();
if (includeGroupTotalFooter) {
this.ensureFooterNodeExists(rootNode);
this.addRowNodeToRowsToDisplay(rootNode.sibling, result, nextRowTop, 0);
}
return result;
}
private resetRowTops(rowNode: RowNode): void {
rowNode.clearRowTop();
if (rowNode.hasChildren()) {
if (rowNode.childrenAfterGroup) {
for (let i = 0; i<rowNode.childrenAfterGroup.length; i++) {
this.resetRowTops(rowNode.childrenAfterGroup[i])
}
}
if (rowNode.sibling) {
rowNode.sibling.clearRowTop();
}
}
}
private recursivelyAddToRowsToDisplay(rowsToFlatten: RowNode[], result: RowNode[],
nextRowTop: NumberWrapper, skipLeafNodes: boolean, uiLevel: number) {
if (_.missingOrEmpty(rowsToFlatten)) { return; }
let groupSuppressRow = this.gridOptionsWrapper.isGroupSuppressRow();
let hideOpenParents = this.gridOptionsWrapper.isGroupHideOpenParents();
// these two are mutually exclusive, so if first set, we don't set the second
let groupRemoveSingleChildren = this.gridOptionsWrapper.isGroupRemoveSingleChildren();
let groupRemoveLowestSingleChildren = !groupRemoveSingleChildren && this.gridOptionsWrapper.isGroupRemoveLowestSingleChildren();
for (let i = 0; i < rowsToFlatten.length; i++) {
let rowNode = rowsToFlatten[i];
// check all these cases, for working out if this row should be included in the final mapped list
let isParent = rowNode.hasChildren();
let isGroupSuppressedNode = groupSuppressRow && isParent;
let isSkippedLeafNode = skipLeafNodes && !isParent;
let isRemovedSingleChildrenGroup = groupRemoveSingleChildren && isParent && rowNode.childrenAfterGroup.length === 1;
let isRemovedLowestSingleChildrenGroup = groupRemoveLowestSingleChildren && isParent && rowNode.leafGroup && rowNode.childrenAfterGroup.length === 1;
// hide open parents means when group is open, we don't show it. we also need to make sure the
// group is expandable in the first place (as leaf groups are not expandable if pivot mode is on).
// the UI will never allow expanding leaf groups, however the user might via the API (or menu option 'expand all')
let neverAllowToExpand = skipLeafNodes && rowNode.leafGroup;
let isHiddenOpenParent = hideOpenParents && rowNode.expanded && (!neverAllowToExpand);
let thisRowShouldBeRendered = !isSkippedLeafNode && !isGroupSuppressedNode && !isHiddenOpenParent && !isRemovedSingleChildrenGroup && !isRemovedLowestSingleChildrenGroup;
if (thisRowShouldBeRendered) {
this.addRowNodeToRowsToDisplay(rowNode, result, nextRowTop, uiLevel);
}
// if we are pivoting, we never map below the leaf group
if (skipLeafNodes && rowNode.leafGroup) { continue; }
if (isParent) {
let excludedParent = isRemovedSingleChildrenGroup || isRemovedLowestSingleChildrenGroup;
// we traverse the group if it is expended, however we always traverse if the parent node
// was removed (as the group will never be opened if it is not displayed, we show the children instead)
if (rowNode.expanded || excludedParent) {
// if the parent was excluded, then ui level is that of the parent
let uiLevelForChildren = excludedParent ? uiLevel : uiLevel + 1;
this.recursivelyAddToRowsToDisplay(rowNode.childrenAfterSort, result,
nextRowTop, skipLeafNodes, uiLevelForChildren);
// put a footer in if user is looking for it
if (this.gridOptionsWrapper.isGroupIncludeFooter()) {
this.ensureFooterNodeExists(rowNode);
this.addRowNodeToRowsToDisplay(rowNode.sibling, result, nextRowTop, uiLevel);
}
} else {
}
} else if (rowNode.master && rowNode.expanded) {
let detailNode = this.createDetailNode(rowNode);
this.addRowNodeToRowsToDisplay(detailNode, result, nextRowTop, uiLevel);
}
}
}
// duplicated method, it's also in floatingRowModel
private addRowNodeToRowsToDisplay(rowNode: RowNode, result: RowNode[], nextRowTop: NumberWrapper, uiLevel: number): void {
result.push(rowNode);
if (_.missing(rowNode.rowHeight)) {
let rowHeight = this.gridOptionsWrapper.getRowHeightForNode(rowNode);
rowNode.setRowHeight(rowHeight);
}
rowNode.setUiLevel(uiLevel);
rowNode.setRowTop(nextRowTop.value);
rowNode.setRowIndex(result.length - 1);
nextRowTop.value += rowNode.rowHeight;
}
private ensureFooterNodeExists(groupNode: RowNode): void {
// only create footer node once, otherwise we have daemons and
// the animate screws up with the daemons hanging around
if (_.exists(groupNode.sibling)) { return; }
let footerNode = new RowNode();
this.context.wireBean(footerNode);
Object.keys(groupNode).forEach(function (key) {
(<any>footerNode)[key] = (<any>groupNode)[key];
});
footerNode.footer = true;
footerNode.rowTop = null;
footerNode.oldRowTop = null;
if (_.exists(footerNode.id)) {
footerNode.id = 'rowGroupFooter_' + footerNode.id;
}
// get both header and footer to reference each other as siblings. this is never undone,
// only overwritten. so if a group is expanded, then contracted, it will have a ghost
// sibling - but that's fine, as we can ignore this if the header is contracted.
footerNode.sibling = groupNode;
groupNode.sibling = footerNode;
}
private createDetailNode(masterNode: RowNode): RowNode {
if (_.exists(masterNode.detailNode)) {
return masterNode.detailNode;
} else {
let detailNode = new RowNode();
this.context.wireBean(detailNode);
detailNode.detail = true;
// flower was renamed to 'detail', but keeping for backwards compatibility
detailNode.flower = detailNode.detail;
detailNode.parent = masterNode;
if (_.exists(masterNode.id)) {
detailNode.id = 'detail_' + masterNode.id;
}
detailNode.data = masterNode.data;
detailNode.level = masterNode.level + 1;
masterNode.detailNode = detailNode;
masterNode.childFlower = masterNode.detailNode; // for backwards compatibility
return detailNode;
}
}
}
interface NumberWrapper {
value: number
}