我正在将AgGrid与树数据一起使用。 https://www.ag-grid.com/javascript-grid-tree-data/
问题是我需要在树上有两个具有相同名称的叶节点,因为它们共享名称,但不共享其属性。 我认为这是由getRowNodeId GridOption指定的。选项1:https://www.ag-grid.com/javascript-grid-rxjs/中的用法示例。 但事实并非如此。
他是我该财产的代码:
...
getRowNodeId: (data: any) => any = (data) => {
return (data.parent !== undefined ? data.parent * 1000 : 0) + data.properties.id;
},
...
如您所见,我想添加2个具有相同名称但只有1个渲染器的节点。我该怎么办?
更新: 已添加代码
我的AgGrid组件:
import { Component, ViewChild, ElementRef, OnDestroy, Input, Output, EventEmitter } from '@angular/core';
import { Events, ToastController, AlertController } from 'ionic-angular';
import * as _ from 'lodash';
import 'ag-grid-enterprise';
import { Http } from '@angular/http';
import { GridApi, TemplateService, GridOptions, ColDef } from 'ag-grid';
import { TemplateServiceProvider } from '../../providers/services';
import { NgModel } from '@angular/forms';
import { Toast } from '../../classes/classes';
import { EventSubscriber } from '../../classes/eventSubscriber/eventSubscriber';
@Component({
selector: 'page-datatableWithGroups',
templateUrl: 'datatableWithGroups.html'
})
export class DatatableWithGroupsPage extends EventSubscriber implements OnDestroy {
@ViewChild('quickFilterInput') quickFilterInput: ElementRef;
@Input() rowData: any[] = [];
@Input() columnDefs = [];
@Input() title: string;
@Output() onLoadRow: EventEmitter<any> = new EventEmitter();
@Output() onAddRow: EventEmitter<any> = new EventEmitter();
@Output() onDeleteRow: EventEmitter<any> = new EventEmitter();
@Output() onDuplicateRow: EventEmitter<any> = new EventEmitter();
@Output() onSelectedRow: EventEmitter<any> = new EventEmitter();
public gridApi: GridApi;
public gridColumnApi;
public printPending: boolean = true;
public showColumnTools: boolean;
public showColumnVisibilityTools: boolean;
public showFilterTools: boolean = true;
private groupDefaultExpanded;
private hoverNode = null;
private isControlPressed: boolean;
private newGroupsCounter: number = 0;
private autoGroupColumnDef: ColDef = {
rowDrag: true,
headerName: "Group",
width: 250,
suppressMovable: true,
cellRendererParams: {
suppressCount: true,
},
valueFormatter: (params) => {
if (!params.data.properties.checkpointList) return params.value;
return params.value + ' (' + (params.data.subGroups ? params.data.subGroups.length : 0) + ')'
+ '(' + (params.data.properties.checkpointList ? params.data.properties.checkpointList.length : 0) + ') ';
},
cellClassRules: {
"hover-over": (params) => {
return params.node === this.hoverNode;
},
"checkpointGroup-title": (params) => {
return this.isCheckpointGroup(params.node.data);
}
}
};
addRowFunction: () => void;
isCheckpointGroup: (nodeData) => boolean = (nodeData) => {
return nodeData && nodeData.properties.checkpointList;
}
getDataPath: (data: any) => any = (data) => {
return data.properties.orgHierarchy;
};
getRowNodeId: (data: any) => any = (data) => {
return (data.properties.parent !== undefined ? data.properties.parent * 1000 : 0) + data.properties.id + (data.properties.component !== undefined ? data.properties.component.id * 100000 : 0);
}
getRowNodeWithUpdatedId: (data: any) => any = (data) => {
return (data.properties.parentId !== undefined ? data.properties.parentId * 1000 : (data.properties.parent !== undefined ? data.properties.parent * 1000 : 0)) + data.properties.id + (data.properties.component !== undefined ? data.properties.component.id * 100000 : 0);
}
public gridOptions: GridOptions = {
enableFilter: this.showFilterTools,
enableColResize: true,
animateRows: true,
cacheQuickFilter: this.showFilterTools,
treeData: true,
quickFilterText: this.quickFilterInput ? this.quickFilterInput.nativeElement.value : '',
colResizeDefault: 'shift',
groupDefaultExpanded: this.groupDefaultExpanded,
rowSelection: 'single',
rowDeselection: true,
defaultColDef: {
filter: "agTextColumnFilter"
},
// components: this.components,
getDataPath: this.getDataPath,
getRowNodeId: this.getRowNodeId,
autoGroupColumnDef: this.autoGroupColumnDef,
deltaRowDataMode: true,
onModelUpdated: () => {
if (this.gridApi && this.columnDefs) {
this.gridColumnApi.autoSizeColumn('ag-Grid-AutoColumn');
this.adjustColumnsToFitWindow();
}
},
onSelectionChanged: (event) => {
let selectedRows = event.api.getSelectedNodes();
let selectedRow = selectedRows && selectedRows.length > 0 ? selectedRows[0] : undefined;
if (selectedRow && this.isCheckpointGroup(selectedRow.data)) {
this.onSelectedRow.emit(selectedRow);
} else {
this.onSelectedRow.emit(undefined);
}
},
};
constructor(
events: Events,
public http: Http,
public templateService: TemplateServiceProvider,
public toastController: ToastController,
public alertCtrl: AlertController,
) {
super(events, [
{ eventName: 'datatable:updateList', callbackFunction: () => this.onLoadRow.emit() },
{ eventName: 'datatable:resizeTable', callbackFunction: () => this.gridApi.sizeColumnsToFit() }
])
this.groupDefaultExpanded = -1;
}
ngOnInit() {
super.subscribeEvents();
this.subscribeEvents();
}
subscribeEvents() {
this.addRowFunction = () => { this.onAddRow.emit() };
window.onresize = () => this.adjustColumnsToFitWindow();
document.addEventListener('keydown', (evt: any) => {
evt = evt || window.event;
if ((evt.keyCode && evt.keyCode === 17) || (evt.which && evt.which === 17)) this.isControlPressed = !this.isControlPressed;
});
}
updateRowData(rowData) {
this.gridApi.setRowData(rowData);
this.gridApi.clearFocusedCell();
this.reAssignSortProperty();
}
onGridReady(params) {
this.gridApi = params.api;
this.gridColumnApi = params.columnApi;
this.adjustColumnsToFitWindow();
if (!this.rowData || this.rowData.length === 0) this.gridApi.hideOverlay()
}
onRowDragMove(event) {
this.setHoverNode(event);
}
onRowDragLeave() {
this.setHoverNode(undefined);
}
setHoverNode(event) {
let overNode = event ? event.overNode : undefined;
if (this.hoverNode === overNode) return;
var rowsToRefresh = [];
if (this.hoverNode) {
rowsToRefresh.push(this.hoverNode);
}
if (overNode) {
rowsToRefresh.push(overNode);
}
this.hoverNode = overNode;
this.refreshRows(rowsToRefresh);
}
refreshRows(rowsToRefresh) {
var params = {
rowNodes: rowsToRefresh,
force: true
};
this.gridApi.refreshCells(params);
}
onRowDragEnd(event) {
let toIndex;
let overNode = event.overNode;
let movingNode = event.node;
let movingData = movingNode.data;
if (overNode === movingNode) return;
if (!overNode) {
if (this.isCheckpointGroup(movingData)) {
overNode = (<any>this.gridApi.getModel()).rootNode;
toIndex = this.rowData.length;
} else {
return;
}
}
let overData = overNode.data;
let fromIndex = this.rowData.indexOf(movingData);
toIndex = toIndex ? toIndex : this.rowData.indexOf(overData) + 1;
let newParentNode = (!overNode.data || this.isCheckpointGroup(overNode.data)) ? overNode : overNode.parent;
if (overData && (overData.properties.parentId === 0 || (!overData.properties.parentId && overData.properties.parent === 0)) && this.isCheckpointGroup(movingData) && this.isControlPressed) {
overNode = (<any>this.gridApi.getModel()).rootNode;
newParentNode = (!overNode.data || this.isCheckpointGroup(overNode.data)) ? overNode : overNode.parent;
} else if (overData && this.isControlPressed) {
newParentNode = overNode.parent;
}
if (toIndex > fromIndex) toIndex--;
let oldParentNode = movingNode.parent;
let newParentPath = (newParentNode.data && this.isCheckpointGroup(newParentNode.data)) ? newParentNode.data.properties.orgHierarchy : [];
let needToChangeParent = !this.arePathsEqual(newParentPath, movingData.properties.orgHierarchy);
if (fromIndex === toIndex && !needToChangeParent) return;
if (this.isInvalidMoveTo(movingNode, newParentNode)) {
new Toast(this.toastController).showToast('Invalid Move');
return;
}
if (needToChangeParent) {
if (this.checkNodeExistsInParent(movingData, newParentNode)) return;
let updatedRows = [];
this.moveToPath(newParentPath, movingNode, updatedRows, oldParentNode.data, newParentNode.data || { properties: { id: 0 } });
this.gridApi.updateRowData({ update: updatedRows });
this.refreshRows(this.rowData);
}
let newStore = this.rowData.slice();
this.moveInArray(newStore, fromIndex, toIndex);
this.gridApi.setRowData(newStore);
this.rowData = newStore;
this.setHoverNode(undefined);
}
checkNodeExistsInParent(newRow, newParentNode) {
let cloneRow = _.cloneDeep(newRow);
cloneRow.properties.parentId = newParentNode && newParentNode.data ? newParentNode.data.properties.id : 0;
if (this.isCheckpointGroup(cloneRow) && this.existsInParent(cloneRow)) {
new Toast(this.toastController).showToast('"' + cloneRow.properties.name + '" already exists on "' + (newParentNode.data ? newParentNode.data.properties.name : 'root') + '"');
return true;
}
return false
}
checkNodeExistsInTemplate(newRow) {
if (!this.isCheckpointGroup(newRow) && this.existsInTemplate(newRow)) {
new Toast(this.toastController).showToast('"' + newRow.properties.name + '" already exists on the template');
return true;
}
return false
}
existsInParent(newRow) {
return this.rowData.find(row => this.getRowNodeWithUpdatedId(row) === this.getRowNodeWithUpdatedId(newRow));
}
existsInTemplate(newRow) {
return this.rowData.find(row => row.properties.id === newRow.properties.id);
}
moveInArray(arr, old_index, new_index) {
if (new_index >= arr.length) {
var k = new_index - arr.length + 1;
while (k--) {
arr.push(undefined);
}
}
arr.splice(new_index, 0, arr.splice(old_index, 1)[0]);
};
moveToPath(newParentPath, node, allUpdatedNodes, oldParentData?, newParentData?) {
var newChildPath = this.updateNodePath(node.data, newParentPath);
if (newParentData) this.updateParents(node.data, oldParentData, newParentData);
allUpdatedNodes.push(node.data);
if (oldParentData) allUpdatedNodes.push(oldParentData);
if (newParentData) allUpdatedNodes.push(newParentData);
if (node.childrenAfterGroup) this.moveChildrenPaths(node.childrenAfterGroup, newChildPath, allUpdatedNodes);
}
updateNodePath(nodeData, newParentPath) {
var oldPath = nodeData.properties.orgHierarchy;
var fileName = oldPath[oldPath.length - 1];
var newChildPath = newParentPath.slice();
newChildPath.push(fileName);
nodeData.properties.orgHierarchy = newChildPath;
return newChildPath;
}
updateParents(nodeData, oldParentData, newParentData) {
nodeData.properties.parentId = newParentData.properties.id;
if (this.isCheckpointGroup(nodeData)) {
if (oldParentData) oldParentData.subGroups = oldParentData.subGroups.filter(subGroup => subGroup.properties.id !== nodeData.properties.id);
if (newParentData && newParentData.subGroups !== undefined) newParentData.subGroups.push(nodeData);
} else {
if (oldParentData) oldParentData.properties.checkpointList = oldParentData.properties.checkpointList.filter(checkpoint => checkpoint.properties.id !== nodeData.properties.id);
if (newParentData && newParentData.properties.checkpointList) newParentData.properties.checkpointList.push(nodeData);
}
}
moveChildrenPaths(children, newChildPath, allUpdatedNodes) {
children.forEach((child) => {
this.moveToPath(newChildPath, child, allUpdatedNodes);
});
}
isInvalidMoveTo(selectedNode, targetNode) {
let isInvalid = false;
if (targetNode.parent && targetNode.parent.data) {
isInvalid = this.isInvalidMoveTo(selectedNode, targetNode.parent);
if (this.getRowNodeWithUpdatedId(targetNode.parent.data) === this.getRowNodeWithUpdatedId(selectedNode.data)) isInvalid = true;
}
if (targetNode && targetNode.data && this.getRowNodeWithUpdatedId(targetNode.data) === this.getRowNodeWithUpdatedId(selectedNode.data)) isInvalid = true;
return isInvalid;
}
arePathsEqual(path1, path2) {
let newPathLength = path1.length + 1;
let oldPathLength = path2.length;
if (this.isControlPressed && newPathLength === 2 && oldPathLength === 1) return true;
if (newPathLength !== oldPathLength) return false;
var equal = true;
path1.forEach(function (item, index) {
if (path2[index] !== item) {
equal = false;
}
});
return equal;
}
reAssignSortProperty() {
this.gridApi.forEachNode((node) => {
node.data.properties.sort = node.childIndex + 1
})
};
addRows(data) {
let selectedGroupNode = data.selectedGroup;
let groupIndex = selectedGroupNode ? this.rowData.indexOf(selectedGroupNode.data) + 1 : this.rowData.length;
data.selectedRows.forEach((selectedRow, index) => {
let newRowData = _.cloneDeep(selectedRow.data);
newRowData.orgHierarchy = selectedGroupNode ? _.cloneDeep(selectedGroupNode.data.properties.orgHierarchy) : [];
newRowData.orgHierarchy.push(newRowData.name);
newRowData = { properties: newRowData };
newRowData.properties.parent = selectedGroupNode ? selectedGroupNode.data.properties.id : 0;
if (this.isCheckpointGroup(newRowData)) {
if (!this.checkNodeExistsInParent(newRowData, selectedGroupNode)) {
this.newGroupsCounter--;
newRowData.properties.checkpointGroupId = newRowData.properties.id;
newRowData.properties.id = this.newGroupsCounter;
newRowData.subGroups = [];
if (selectedGroupNode && selectedGroupNode.data) selectedGroupNode.data.subGroups.push(newRowData);
this.rowData.splice(groupIndex + index + (selectedGroupNode ? selectedGroupNode.allChildrenCount : 0), 0, newRowData)
}
} else {
if (!this.checkNodeExistsInTemplate(newRowData)) {
newRowData.properties.templateCheckpointGroupCheckpointId = -1;
if (selectedGroupNode && selectedGroupNode.data) selectedGroupNode.data.properties.checkpointList.push(newRowData);
this.rowData.splice(groupIndex + index + (selectedGroupNode ? selectedGroupNode.allChildrenCount : 0), 0, newRowData)
}
}
});
if (selectedGroupNode) data.selectedGroup.expanded = true;
this.updateRowData(this.rowData);
}
deleteRow(row) {
if (row.properties.checkpointList && row.properties.checkpointList.length > 0) {
row.properties.checkpointList.forEach(checkpoint => {
this.deleteRow(checkpoint);
});
}
if (row.subGroups && row.subGroups.length > 0) {
row.subGroups.forEach(subGroup => {
this.deleteRow(subGroup);
});
}
this.deleteByUpdatedParentId(row);
this.updateRowData(this.rowData);
}
deleteByUpdatedParentId(row) {
this.rowData = this.rowData.filter(existingRow => this.getRowNodeWithUpdatedId(existingRow) !== this.getRowNodeWithUpdatedId(row));
}
adjustColumnsToFitWindow() {
if (this.gridApi) this.gridApi.sizeColumnsToFit();
}
onFilterTextBoxChanged() {
this.gridApi.setQuickFilter(this.quickFilterInput.nativeElement.value);
}
exportCsv() {
let params = {
fileName: 'Rows',
sheetName: 'Rows',
columnSeparator: ';'
};
this.gridApi.exportDataAsCsv(params);
}
print() {
this.setPrinterFriendly();
this.printPending = true;
// if (this.gridApi.isAnimationFrameQueueEmpty()) {
this.onAnimationQueueEmpty({ api: this.gridApi });
// }
}
onAnimationQueueEmpty(event) {
if (this.printPending) {
this.printPending = false;
this.printTable();
}
}
printTable() {
// let rest = <any>document.querySelector("ion-content :not(#grid)");
// rest.style.display = 'none';
print();
}
setPrinterFriendly() {
let eGridDiv = <any>document.getElementById("grid");
let preferredWidth = this.gridApi.getPreferredWidth();
preferredWidth += 2;
eGridDiv.style.width = preferredWidth + "px";
eGridDiv.style.height = "";
this.gridApi.setGridAutoHeight(true);
}
}
html:
<ion-content padding>
<ion-item class="generic-tools-wrapper">
<div>
<input type="text" #quickFilterInput placeholder="Filter..." (input)="onFilterTextBoxChanged()" />
<button ion-button small type="button" icon-only (click)="exportCsv()">
<ion-icon name="list-box"></ion-icon>
</button>
<button ion-button small type="button" icon-only (click)="print()">
<ion-icon name="print"></ion-icon>
</button>
<button ion-button small [color]="isControlPressed ? 'danger': 'primary'" type="button" icon-only (click)="isControlPressed = !isControlPressed">
<ion-icon name="return-right"></ion-icon>
</button>
</div>
</ion-item>
<ion-label> <strong>Attention!</strong> The grouping option is <strong>{{isControlPressed ? 'deactivated':
'activated'}}</strong>,
elements
dragged over a group will be put <strong>{{isControlPressed ? 'next to': 'inside'}} that group</strong>. Press
<strong>Control</strong> to switch this option. </ion-label>
<div style="height: 90%;box-sizing: border-box; ">
<ag-grid-angular id="grid" style="width: 100%; height: 100%;" class="ag-theme-material" [rowData]="rowData"
[columnDefs]="columnDefs" [gridOptions]="gridOptions" (gridReady)="onGridReady($event)" (rowDragMove)="onRowDragMove($event)"
(rowDragEnd)="onRowDragEnd($event)" (rowDragLeave)="onRowDragLeave($event)" (viewportChanged)="adjustColumnsToFitWindow()">
</ag-grid-angular>
</div>
</ion-content>