561943 - Nodeprovider: Features Prio 1
Added parameter 'navigator.restoreTreeState'
Change-Id: I0430813c5f2dd1c53d865e0b3aae85aa6f3dbf16
Signed-off-by: Matthias Koller <m.koller@peak-solution.de>
diff --git a/README.md b/README.md
index 49dc779..53e285c 100644
--- a/README.md
+++ b/README.md
@@ -447,7 +447,11 @@
### System scope
System scoped preference are applied globally.
-
+* Restore tree state
+ The navigator is able to save the state of the tree in the Browsers local storage and reopen the tree in the saved state after page reload or logout.
+
+ The parameter `navigator.restoreTreeState` with value `true` will save the state of the tree.
+
* Node provider
The navigation tree structure can be defined via a node provider. The default node provider is set in `nucleus/webclient/src/main/webapp/src/app/navigator/defaultnodeprovider.json`.
It is recommended not to change the default node provider. Instead new node providers can be added as preferences.
diff --git a/nucleus/webclient/src/main/webapp/src/app/navigator/mdm-navigator.component.html b/nucleus/webclient/src/main/webapp/src/app/navigator/mdm-navigator.component.html
index 87dbf69..edca911 100644
--- a/nucleus/webclient/src/main/webapp/src/app/navigator/mdm-navigator.component.html
+++ b/nucleus/webclient/src/main/webapp/src/app/navigator/mdm-navigator.component.html
@@ -20,9 +20,8 @@
selectionMode="multiple"
[(selection)]="selectedNodes"
(onNodeExpand)="onNodeExpand($event)"
+ (onNodeCollapse)="onNodeCollapse($event)"
(onNodeSelect)="onNodeSelect($event)"
- (onNodeExpand)="saveNavigatorStateInLocalStorage($event)"
- (onNodeCollapse)="saveNavigatorStateInLocalStorage($event)"
[contextMenu]="cm"
>
<ng-template let-node pTemplate="default">
diff --git a/nucleus/webclient/src/main/webapp/src/app/navigator/mdm-navigator.component.ts b/nucleus/webclient/src/main/webapp/src/app/navigator/mdm-navigator.component.ts
index f029c98..5732364 100644
--- a/nucleus/webclient/src/main/webapp/src/app/navigator/mdm-navigator.component.ts
+++ b/nucleus/webclient/src/main/webapp/src/app/navigator/mdm-navigator.component.ts
@@ -19,7 +19,6 @@
import { map, tap, startWith, filter, flatMap } from 'rxjs/operators';
import { MDMItem } from '../core/mdm-item';
-import { Node } from '../navigator/node';
import { NodeService } from '../navigator/node.service';
import { QueryService } from '../tableview/query.service';
import { NodeproviderService, MDMTreeNode } from './nodeprovider.service';
@@ -31,7 +30,8 @@
import { TranslateService } from '@ngx-translate/core';
import { streamTranslate, TRANSLATE } from '../core/mdm-core.module';
import { ActivatedRoute, Router } from '@angular/router';
-import { forkJoin, combineLatest, Subscription } from 'rxjs';
+import { combineLatest, Observable, Subscription } from 'rxjs';
+import { PreferenceService } from '@core/preference.service';
@Component({
selector: 'mdm-navigator',
@@ -48,6 +48,7 @@
selectedNodes: TreeNode[] = [];
nodes: TreeNode[] = [];
+ restoreTreeState: Observable<boolean>;
lastClickTime = 0;
nodeProviderSubscription: Subscription;
@@ -71,6 +72,7 @@
private navigatorService: NavigatorService,
private notificationService: MDMNotificationService,
private translateService: TranslateService,
+ private preferenceService: PreferenceService,
private router: Router,
private route: ActivatedRoute) {
}
@@ -87,6 +89,10 @@
(msg: string) => this.AlrtItemTypeNotSupported = msg);
+ this.restoreTreeState = this.preferenceService.getPreference("navigator.restoreTreeState").pipe(
+ map(prefs => prefs.map(p => p.value).some(p => p === 'true'))
+ );
+
// make sure navigate parameter is handled after nodes are loaded
this.loadRootNodes().subscribe(
treeNodes => {
@@ -120,41 +126,38 @@
}
restoreLastNavigatorState() {
- const rootNodesOfTreeBefore = this.getStoredNavigatorState();
+ this.restoreTreeState.subscribe(restoreState => {
+ if (restoreState) {
+ const navigatorState = this.getStoredNavigatorState();
- if (rootNodesOfTreeBefore != null) {
- this.nodeProviderSubscription.unsubscribe();
- this.nodeproviderService.setActiveNodeprovider(rootNodesOfTreeBefore.nodeprovider_id);
- this.loadRootNodes().subscribe(rootNodes => {
- this.nodes = rootNodes;
- this.restoreTreeNodes(this.nodes, rootNodesOfTreeBefore.nodes);
- this.subscribeToNodeProviderChanges();
- });
+ if (navigatorState != null) {
+ this.nodeProviderSubscription.unsubscribe();
+ this.nodeproviderService.setActiveNodeprovider(navigatorState.nodeprovider_id);
+ this.loadRootNodes().subscribe(rootNodes => {
+ this.nodes = rootNodes;
+ this.restoreTreeNodes(this.nodes, navigatorState.nodes);
+ this.subscribeToNodeProviderChanges();
+ });
+ }
+ }
+ });
+ }
+
+ restoreTreeNodes(treeNodes: TreeNode[], labelsToExpand: Map<string, any>) {
+ for (let nodeToRestore of Array.from(labelsToExpand.entries())) {
+ const nodeToExpand = this.findTreeNodeByLabel(treeNodes, nodeToRestore[0]);
+ this.loadNodeItemAndExpand(nodeToExpand, nodeToRestore[1]);
}
}
- restoreTreeNodes(nodesToRestore: TreeNode[], lastNodes: TreeNode[]) {
- for (let nodeToRestore of nodesToRestore) {
- const lastNode = this.findTreeNode(lastNodes, nodeToRestore.data);
- this.loadNodeItemAndExpand(nodeToRestore, lastNode);
- }
- }
-
- loadNodeItemAndExpand(nodeOfCurrentTree: TreeNode, nodeOfTreeBefore: TreeNode) {
- if (nodeOfTreeBefore != null && !nodeOfCurrentTree.leaf && nodeOfTreeBefore.expanded) {
- return this.getChildren(nodeOfCurrentTree.data).subscribe(
+ loadNodeItemAndExpand(nodeToExpand: TreeNode, labelsToExpand: Map<string, any>) {
+ if (nodeToExpand) {
+ return this.getChildren(nodeToExpand.data).subscribe(
nodes => {
- nodeOfCurrentTree.children = nodes;
+ nodeToExpand.children = nodes;
+ nodeToExpand.expanded = true;
- const lastChilds = nodeOfTreeBefore.children;
- nodeOfCurrentTree.expanded = true;
-
- for (const child of nodeOfCurrentTree.children) {
- const lastNode = this.findTreeNode(lastChilds, child.data);
- this.loadNodeItemAndExpand(child, lastNode);
- }
-
-
+ this.restoreTreeNodes(nodeToExpand.children, labelsToExpand);
},
error => this.notificationService.notifyError(
this.translateService.instant('navigator.mdm-navigator.err-cannot-load-nodes'), error)
@@ -162,10 +165,10 @@
}
}
- findTreeNode(nodes: TreeNode[], mdmNode: MDMTreeNode) {
+ findTreeNodeByLabel(nodes: TreeNode[], nodeLabel: string) {
for (const node of nodes) {
const data = node.data;
- if (data != null && data.source === mdmNode.source && data.type === mdmNode.type && data.id === mdmNode.id) {
+ if (data != null && data.label === nodeLabel) {
return node;
}
}
@@ -236,7 +239,9 @@
if (event.originalEvent.timeStamp - this.lastClickTime < 300) {
if (!event.node.expanded && !event.node.children) {
- this.loadNode(event.node as TreeNode);
+ this.onNodeExpand(event);
+ } else {
+ this.onNodeCollapse(event);
}
event.node.expanded = !event.node.expanded;
}
@@ -245,29 +250,37 @@
onNodeExpand(event) {
if (event.node) {
- this.loadNode(event.node as TreeNode);
+ this.loadChildren(event.node as TreeNode);
+ this.saveNavigatorStateInLocalStorage();
}
}
- loadNode(node: TreeNode) {
- if (node && node.children == undefined) {
- return this.loadNodeItem(node);
+ onNodeCollapse(event) {
+ if (event.node) {
+ this.unloadChildren(event.node as TreeNode);
+ this.saveNavigatorStateInLocalStorage();
}
}
- loadNodeItem(node: TreeNode) {
- if (!node.leaf) {
- return this.getChildren(node.data as MDMTreeNode).pipe(
+ loadChildren(parentNode: TreeNode) {
+ if (parentNode && parentNode.children == undefined && !parentNode.leaf) {
+ return this.getChildren(parentNode.data as MDMTreeNode).pipe(
startWith([this.loadingNode]),
- tap(nodes => (nodes && nodes.length === 0) ? node.leaf = true : node.leaf = false))
+ tap(nodes => (nodes && nodes.length === 0) ? parentNode.leaf = true : parentNode.leaf = false))
.subscribe(
- nodes => node.children = nodes,
+ nodes => parentNode.children = nodes,
error => this.notificationService.notifyError(
this.translateService.instant('navigator.mdm-navigator.err-cannot-load-nodes'), error)
);
}
}
+ unloadChildren(parentNode: TreeNode) {
+ if (parentNode) {
+ parentNode.children = undefined;
+ }
+ }
+
reloadTree() {
this.loadRootNodes()
.subscribe(
@@ -313,7 +326,7 @@
refresh() {
for (const node of this.selectedNodes) {
- this.loadNodeItem(node);
+ this.loadChildren(node);
}
}
@@ -321,110 +334,73 @@
this.restoreLastNavigatorState();
}
- saveNavigatorStateInLocalStorage(event) {
- this.nodeproviderService.getActiveNodeprovider().subscribe(np => {
- let jsonVal = JSON.stringify({ nodeprovider_id: np, nodes: this.nodes }, this.replacer);
- console.log('storedRootNodes', jsonVal);
- localStorage.setItem('storedRootNodes', jsonVal);
+ saveNavigatorStateInLocalStorage() {
+ this.restoreTreeState.subscribe(restoreState => {
+ if (restoreState) {
+ this.nodeproviderService.getActiveNodeprovider().subscribe(np => {
+ let jsonVal = JSON.stringify({ nodeprovider_id: np, nodes: this.getOpenNodes(this.nodes) }, this.replacer);
+ localStorage.setItem('storedRootNodes', jsonVal);
+ });
+ }
});
}
- getStoredNavigatorState() : { nodeprovider_id: string, nodes: TreeNode[] }{
- return JSON.parse(localStorage.getItem('storedRootNodes'));
+ getStoredNavigatorState() : { nodeprovider_id: string, nodes: Map<string, any> }{
+ return JSON.parse(localStorage.getItem('storedRootNodes'), this.reviver);
}
replacer(key, value) {
- if (key == 'parent') {
- return undefined;
+ const originalObject = this[key];
+ if(originalObject instanceof Map) {
+ return {
+ dataType: 'Map',
+ value: Array.from(originalObject.entries()), // or with spread: value: [...originalObject]
+ };
} else {
return value;
}
+ }
+ reviver(key, value) {
+ if(typeof value === 'object' && value !== null) {
+ if (value.dataType === 'Map') {
+ return new Map(value.value);
+ }
+ }
+ return value;
+ }
+
+ getOpenNodes(treeNodes: TreeNode[]) : Map<string, any> {
+ let m = new Map<string, any>();
+
+ treeNodes.filter(n => n.children).forEach(n => m.set(n.label, this.getOpenNodes(n.children)));
+ return m;
}
openInTree(items: MDMItem[]) {
this.selectedNodes = [];
items.forEach(item => {
- this.nodeproviderService.getTreePath(item).subscribe(treepath =>
- this.expandTreePath(treepath));
- /*let pathTypes = this.nodeproviderService.getPathTypes(item.type);
- if (pathTypes.length === 0) {
- alert(this.AlrtItemTypeNotSupported);
- } else {
- let env = this.nodes.find(e => item.source === e.data.source);
- env.expanded = true;
- this.openChildrenRecursive(item, env, pathTypes, 1);
- }*/
+ this.nodeproviderService.getTreePath(item).subscribe(treepath => this.expandTreePath(treepath));
});
}
-expandTreePath(treePath: MDMTreeNode[]) {
- console.log("expand", treePath);
- let current = this.nodes;
- for (let mdmTreeNode of treePath.reverse()) {
- console.log("current", current, "mdmTreeNode", mdmTreeNode);
- current = this.expandChild(current, mdmTreeNode);
- }
- console.log("current", current);
- this.onNodeSelect(current);
-}
-expandChild(children: TreeNode[], treePath: MDMTreeNode) {
- let s = children.filter(node => node.data.source === treePath.source && node.data.id === treePath.id)[0];
- s.expanded = true;
- return s.children;
-}
-
-/*
- openChildrenRecursive(item: MDMItem, current: TreeNode, pathTypes: string[], iii: number) {
- if (current.children) {
- this.expander(item, current, pathTypes, iii);
- } else {
- this.getChildren(current).pipe(
- startWith([this.loadingNode]),
- tap(n => current.children = n))
- .subscribe(
- () => this.expander(item, current, pathTypes, iii),
- error => this.notificationService.notifyError(
- this.translateService.instant('navigator.mdm-navigator.err-cannot-open-node'), error)
- );
+ expandTreePath(treePath: MDMTreeNode[]) {
+ console.log("expand", treePath);
+ let current = this.nodes;
+ for (let mdmTreeNode of treePath.reverse()) {
+ console.log("current", current, "mdmTreeNode", mdmTreeNode);
+ current = this.expandChild(current, mdmTreeNode);
}
+ console.log("current", current);
+ this.onNodeSelect(current);
}
- expander(item: MDMItem, current: TreeNode, pathTypes: string[], iii: number) {
- let expandList: string[] = [];
- this.nodeService.searchNodes('filter=' + item.type + '.Id eq "' + item.id + '"',
- item.source, pathTypes[iii])
- .subscribe(
- nodes => {
- expandList = nodes.map(n => n.id);
- current.children.filter(node => expandList.findIndex(
- i => node.data ? i === node.data.id : false) > -1
- )
- .forEach(node => {
- if (++iii < pathTypes.length) {
- node.expanded = true;
- this.openChildrenRecursive(item, node, pathTypes, iii);
- this.scrollToSelectionPrimeNgDataTable(node);
- } else {
- this.selectedNodes.push(node);
- let length = this.selectedNodes.length;
- if (length === 1) {
- this.nodeService.getNodeFromItem(this.selectedNodes[length - 1].data)
- .subscribe(
- n => this.navigatorService.setSelectedNode(n),
- error => this.notificationService.notifyError(
- this.translateService.instant('navigator.mdm-navigator.err-cannot-open-node'), error)
- );
- }
- this.scrollToSelectionPrimeNgDataTable(node);
- }
- });
- },
- error => this.notificationService.notifyError(
- this.translateService.instant('navigator.mdm-navigator.err-cannot-open-node'), error)
- );
+ expandChild(children: TreeNode[], treePath: MDMTreeNode) {
+ let s = children.filter(node => node.data.source === treePath.source && node.data.id === treePath.id)[0];
+ s.expanded = true;
+ return s.children;
}
-*/
+
/**
* PrimeNG does not support scroll to view. This methods implements a
* workaround by using HTML element IDs.