]> git.evergreen-ils.org Git - working/Evergreen.git/blob - Open-ILS/src/eg2/src/app/share/tree/tree.ts
LP1823393 Tree component collapsed node display fix
[working/Evergreen.git] / Open-ILS / src / eg2 / src / app / share / tree / tree.ts
1
2 export class TreeNode {
3     // Unique identifier
4     id: any;
5
6     // Display label
7     label: string;
8
9     // True if child nodes should be visible
10     expanded: boolean;
11
12     children: TreeNode[];
13
14     // Set by the tree.
15     depth: number;
16
17     // Set by the tree.
18     selected: boolean;
19
20     // Optional link to user-provided stuff.
21     // This field is ignored by the tree.
22     callerData: any;
23
24     constructor(values: {[key: string]: any}) {
25         this.children = [];
26         this.expanded = true;
27         this.depth = 0;
28         this.selected = false;
29
30         if (!values) { return; }
31
32         if ('id' in values) { this.id = values.id; }
33         if ('label' in values) { this.label = values.label; }
34         if ('children' in values) { this.children = values.children; }
35         if ('expanded' in values) { this.expanded = values.expanded; }
36         if ('callerData' in values) { this.callerData = values.callerData; }
37     }
38
39     toggleExpand() {
40         this.expanded = !this.expanded;
41     }
42 }
43
44 export class Tree {
45
46     rootNode: TreeNode;
47     idMap: {[id: string]: TreeNode};
48
49     constructor(rootNode?: TreeNode) {
50         this.rootNode = rootNode;
51         this.idMap = {};
52     }
53
54     // Returns a depth-first list of tree nodes
55     // Tweaks node attributes along the way to match the shape of the tree.
56     nodeList(filterHidden?: boolean): TreeNode[] {
57
58         const nodes = [];
59
60         const recurseTree =
61             (node: TreeNode, depth: number, hidden: boolean) => {
62             if (!node) { return; }
63
64             node.depth = depth++;
65             this.idMap[node.id + ''] = node;
66
67             if (hidden) {
68                 // it could be confusing for a hidden node to be selected.
69                 node.selected = false;
70             }
71
72             if (hidden && filterHidden) {
73                 // Avoid adding hidden child nodes to the list.
74             } else {
75                 nodes.push(node);
76                 node.children.forEach(n => recurseTree(n, depth, !node.expanded));
77             }
78         };
79
80         recurseTree(this.rootNode, 0, false);
81         return nodes;
82     }
83
84     findNode(id: any): TreeNode {
85         if (this.idMap[id + '']) {
86             return this.idMap[id + ''];
87         } else {
88             // nodeList re-indexes all the nodes.
89             this.nodeList();
90             return this.idMap[id + ''];
91         }
92     }
93
94     findParentNode(node: TreeNode) {
95         const list = this.nodeList();
96         for (let idx = 0; idx < list.length; idx++) {
97             const pnode = list[idx];
98             if (pnode.children.filter(c => c.id === node.id).length) {
99                 return pnode;
100             }
101         }
102         return null;
103     }
104
105     removeNode(node: TreeNode) {
106         if (!node) { return; }
107         const pnode = this.findParentNode(node);
108         if (pnode) {
109             pnode.children = pnode.children.filter(n => n.id !== node.id);
110         } else {
111             this.rootNode = null;
112         }
113     }
114
115     expandAll() {
116         this.nodeList().forEach(node => node.expanded = true);
117     }
118
119     collapseAll() {
120         this.nodeList().forEach(node => node.expanded = false);
121     }
122
123     selectedNode(): TreeNode {
124         return this.nodeList().filter(node => node.selected)[0];
125     }
126
127     selectNode(node: TreeNode) {
128         this.nodeList().forEach(n => n.selected = false);
129         node.selected = true;
130     }
131 }
132