From fa43214d2f6e4d9fda585aaf5ffd56b6903e4c75 Mon Sep 17 00:00:00 2001 From: Bill Erickson Date: Wed, 18 Jul 2018 17:42:39 -0400 Subject: [PATCH] LP#1775466 Tree widget Signed-off-by: Bill Erickson --- .../src/eg2/src/app/share/tree/tree.component.css | 19 ++++ .../src/eg2/src/app/share/tree/tree.component.html | 20 ++++ .../src/eg2/src/app/share/tree/tree.component.ts | 60 +++++++++++ Open-ILS/src/eg2/src/app/share/tree/tree.module.ts | 20 ++++ Open-ILS/src/eg2/src/app/share/tree/tree.ts | 117 +++++++++++++++++++++ 5 files changed, 236 insertions(+) create mode 100644 Open-ILS/src/eg2/src/app/share/tree/tree.component.css create mode 100644 Open-ILS/src/eg2/src/app/share/tree/tree.component.html create mode 100644 Open-ILS/src/eg2/src/app/share/tree/tree.component.ts create mode 100644 Open-ILS/src/eg2/src/app/share/tree/tree.module.ts create mode 100644 Open-ILS/src/eg2/src/app/share/tree/tree.ts diff --git a/Open-ILS/src/eg2/src/app/share/tree/tree.component.css b/Open-ILS/src/eg2/src/app/share/tree/tree.component.css new file mode 100644 index 0000000000..0d29dd73a4 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/share/tree/tree.component.css @@ -0,0 +1,19 @@ + +.eg-tree-node-expandy .material-icons { + font-size: 16px; +} + +.eg-tree-node { + padding: 2px; +} + +.eg-tree-node.active { + background-color: rgba(0,0,0,.03); + border: 1px solid rgba(0,0,0,.125); + font-style: italic; +} + +.eg-tree-node-nochild { + border-left: 2px dashed rgba(0,0,0,.125); +} + diff --git a/Open-ILS/src/eg2/src/app/share/tree/tree.component.html b/Open-ILS/src/eg2/src/app/share/tree/tree.component.html new file mode 100644 index 0000000000..80125b3ec2 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/share/tree/tree.component.html @@ -0,0 +1,20 @@ + + +
+
+
+
+ expand_more + expand_less +
+
+   +
+
+ +
+
diff --git a/Open-ILS/src/eg2/src/app/share/tree/tree.component.ts b/Open-ILS/src/eg2/src/app/share/tree/tree.component.ts new file mode 100644 index 0000000000..d933957000 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/share/tree/tree.component.ts @@ -0,0 +1,60 @@ +import {Component, OnInit, Input, Output, EventEmitter} from '@angular/core'; +import {Tree, TreeNode} from './tree'; + +/* +Tree Widget: + + + + +---- + +constructor() { + + const rootNode = new TreeNode({ + id: 1, + label: 'Root', + children: [ + new TreeNode({id: 2, label: 'Child'}), + new TreeNode({id: 3, label: 'Child2'}) + ] + ]}); + + this.myTree = new Tree(rootNode); +} + +nodeClicked(node: TreeNode) { + console.log('someone clicked on ' + node.label); +} +*/ + +@Component({ + selector: 'eg-tree', + templateUrl: 'tree.component.html', + styleUrls: ['tree.component.css'] +}) +export class TreeComponent implements OnInit { + + @Input() tree: Tree; + @Output() nodeClicked: EventEmitter; + + constructor() { + this.nodeClicked = new EventEmitter(); + } + + ngOnInit() {} + + displayNodes(): TreeNode[] { + return this.tree.nodeList(true); + } + + handleNodeClick(node: TreeNode) { + this.tree.activateNode(node); + this.nodeClicked.emit(node); + } +} + + + diff --git a/Open-ILS/src/eg2/src/app/share/tree/tree.module.ts b/Open-ILS/src/eg2/src/app/share/tree/tree.module.ts new file mode 100644 index 0000000000..3894fcdd25 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/share/tree/tree.module.ts @@ -0,0 +1,20 @@ +import {NgModule} from '@angular/core'; +import {EgCommonModule} from '@eg/common.module'; +import {TreeComponent} from './tree.component'; + +@NgModule({ + declarations: [ + TreeComponent + ], + imports: [ + EgCommonModule + ], + exports: [ + TreeComponent + ], + providers: [ + ] +}) + +export class TreeModule {} + diff --git a/Open-ILS/src/eg2/src/app/share/tree/tree.ts b/Open-ILS/src/eg2/src/app/share/tree/tree.ts new file mode 100644 index 0000000000..f230780d28 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/share/tree/tree.ts @@ -0,0 +1,117 @@ + +export class TreeNode { + id: any; + label: string; + expanded: boolean; + children: TreeNode[]; + depth: number; + active: boolean; + + constructor(values: {[key: string]: any}) { + this.children = []; + this.expanded = false; + this.depth = 0; + this.active = false; + if (values) { + if ('id' in values) { this.id = values.id; } + if ('label' in values) { this.label = values.label; } + if ('children' in values) { this.children = values.children; } + if ('expanded' in values) { this.expanded = values.expanded; } + } + } + + toggleExpand() { + this.expanded = !this.expanded; + } +} + +export class Tree { + + rootNode: TreeNode; + idMap: {[id: string]: TreeNode}; + + constructor(rootNode?: TreeNode) { + this.rootNode = rootNode; + this.idMap = {}; + } + + // Returns a depth-first list of tree nodes + // Tweaks node attributes along the way to match the shape of the tree. + nodeList(filterHidden?: boolean): TreeNode[] { + + const nodes = []; + + const recurseTree = + (node: TreeNode, depth: number, hidden: boolean) => { + if (!node) { return; } + + node.depth = depth++; + this.idMap[node.id + ''] = node; + + if (hidden) { + // it could be confusing for a hidden node to be active. + node.active = false; + } + + if (hidden && filterHidden) { + // Avoid adding hidden child nodes to the list. + } else { + nodes.push(node); + } + + node.children.forEach(n => recurseTree(n, depth, !node.expanded)); + } + + recurseTree(this.rootNode, 0, false); + return nodes; + } + + findNode(id: any): TreeNode { + if (this.idMap[id + '']) { + return this.idMap[id + '']; + } else { + // nodeList re-indexes all the nodes. + this.nodeList(); + return this.idMap[id + '']; + } + } + + findParentNode(node: TreeNode) { + const list = this.nodeList(); + for (let idx = 0; idx < list.length; idx++) { + const pnode = list[idx]; + if (pnode.children.filter(c => c.id === node.id).length) { + return pnode; + } + } + return null; + } + + removeNode(node: TreeNode) { + if (!node) { return; } + const pnode = this.findParentNode(node); + if (pnode) { + pnode.children = pnode.children.filter(n => n.id !== node.id); + } else { + this.rootNode = null; + } + } + + expandAll() { + this.nodeList().forEach(node => node.expanded = true); + } + + collapseAll() { + this.nodeList().forEach(node => node.expanded = false); + } + + activeNode(): TreeNode { + return this.nodeList().filter(node => node.active)[0]; + } + + activateNode(node: TreeNode) { + this.nodeList().forEach(n => n.active = false); + node.active = true; + } +} + -- 2.11.0