1212 * or submit itself to any jurisdiction.
1313 */
1414
15- import { Observable } from '/js/src/index.js' ;
15+ import { BrowserStorage , Observable , sessionService } from '/js/src/index.js' ;
16+ import { StorageKeysEnum } from '../common/enums/storageKeys.enum.js' ;
1617
1718/**
1819 * This class allows to transforms objects names (A/B/C) into a tree that can have
@@ -27,6 +28,7 @@ export default class ObjectTree extends Observable {
2728 */
2829 constructor ( name , parent ) {
2930 super ( ) ;
31+ this . storage = new BrowserStorage ( StorageKeysEnum . OBJECT_TREE_OPEN_NODES ) ;
3032 this . initTree ( name , parent ) ;
3133 }
3234
@@ -46,12 +48,116 @@ export default class ObjectTree extends Observable {
4648 this . pathString = '' ; // 'A/B'
4749 }
4850
51+ /**
52+ * Load the expanded/collapsed state for this node and its children from localStorage.
53+ * Updates the `open` property for the current node and recursively for all children.
54+ */
55+ loadExpandedNodes ( ) {
56+ if ( ! this . parent ) {
57+ // The main node may not be collapsable or expandable.
58+ // Because of this we also have to load the expanded state of their direct children.
59+ this . children . forEach ( ( child ) => child . loadExpandedNodes ( ) ) ;
60+ }
61+
62+ const session = sessionService . get ( ) ;
63+ const key = session . personid . toString ( ) ;
64+
65+ // We traverse the path to reach the parent object of this node
66+ let parentNode = this . storage . getLocalItem ( key ) ?? { } ;
67+ for ( let i = 0 ; i < this . path . length - 1 ; i ++ ) {
68+ parentNode = parentNode [ this . path [ i ] ] ;
69+ if ( ! parentNode ) {
70+ // Cannot expand marked node because parent path does not exist
71+ return ;
72+ }
73+ }
74+
75+ this . _applyExpandedNodesRecursive ( parentNode , this ) ;
76+ }
77+
78+ /**
79+ * Recursively traverse the stored data and update the tree nodes
80+ * @param {object } data - The current level of the hierarchical expanded nodes object
81+ * @param {ObjectTree } treeNode - The tree node to update
82+ */
83+ _applyExpandedNodesRecursive ( data , treeNode ) {
84+ if ( data [ treeNode . name ] ) {
85+ treeNode . open = true ;
86+ Object . keys ( data [ treeNode . name ] ) . forEach ( ( childName ) => {
87+ const child = treeNode . children . find ( ( child ) => child . name === childName ) ;
88+ if ( child ) {
89+ this . _applyExpandedNodesRecursive ( data [ treeNode . name ] , child ) ;
90+ }
91+ } ) ;
92+ }
93+ } ;
94+
95+ /**
96+ * Persist the current node's expanded/collapsed state in localStorage.
97+ */
98+ storeExpandedNodes ( ) {
99+ if ( ! this . parent ) {
100+ // The main node may not be collapsable or expandable.
101+ // Because of this we have to store the expanded state of their direct children.
102+ this . children . forEach ( ( child ) => child . storeExpandedNodes ( ) ) ;
103+ }
104+
105+ const session = sessionService . get ( ) ;
106+ const key = session . personid . toString ( ) ;
107+ const data = this . storage . getLocalItem ( key ) ?? { } ;
108+
109+ // We traverse the path to reach the parent object of this node
110+ let parentNode = data ;
111+ for ( let i = 0 ; i < this . path . length - 1 ; i ++ ) {
112+ const pathKey = this . path [ i ] ;
113+ if ( ! parentNode [ pathKey ] ) {
114+ if ( ! this . open ) {
115+ // Cannot remove marked node because parent path does not exist
116+ // Due to this the marked node also does not exist (so there is nothing to remove)
117+ return ;
118+ }
119+
120+ // Parent path does not exist, we create it here so we can mark a deeper node
121+ parentNode [ pathKey ] = { } ;
122+ }
123+
124+ parentNode = parentNode [ pathKey ] ;
125+ }
126+
127+ if ( this . open ) {
128+ this . _markExpandedNodesRecursive ( parentNode , this ) ;
129+ this . storage . setLocalItem ( key , data ) ;
130+ } else if ( parentNode [ this . name ] ) {
131+ // Deleting from `parentNode` directly updates the `data` object
132+ delete parentNode [ this . name ] ;
133+ this . storage . setLocalItem ( key , data ) ;
134+ }
135+ }
136+
137+ /**
138+ * Recursively mark a node and all open children in the hierarchical "expanded nodes" object.
139+ * This method updates `data` to reflect the current node's expanded state:
140+ * - If the node has any open children, it creates an object branch and recursively marks those children.
141+ * - If the node has no open children (or is a leaf), it stores a marker value `{}`.
142+ * @param {object } data - The current level in the hierarchical data object where nodes are stored.
143+ * @param {ObjectTree } treeNode - The tree node whose expanded state should be stored.
144+ */
145+ _markExpandedNodesRecursive ( data , treeNode ) {
146+ if ( ! data [ treeNode . name ] ) {
147+ data [ treeNode . name ] = { } ;
148+ }
149+ treeNode . children
150+ . filter ( ( child ) => child . open )
151+ . forEach ( ( child ) => this . _markExpandedNodesRecursive ( data [ treeNode . name ] , child ) ) ;
152+ } ;
153+
49154 /**
50155 * Toggle this node (open/close)
51156 * @returns {undefined }
52157 */
53158 toggle ( ) {
54159 this . open = ! this . open ;
160+ this . storeExpandedNodes ( ) ;
55161 this . notify ( ) ;
56162 }
57163
@@ -60,6 +166,7 @@ export default class ObjectTree extends Observable {
60166 */
61167 closeAll ( ) {
62168 this . _closeAllRecursive ( ) ;
169+ this . storeExpandedNodes ( ) ;
63170 this . notify ( ) ;
64171 }
65172
@@ -85,15 +192,14 @@ export default class ObjectTree extends Observable {
85192 * addChild(o, [], ['A', 'B']) // end inserting, affecting B
86193 * @returns {undefined }
87194 */
88- addChild ( object , path , pathParent ) {
195+ _addChild ( object , path = undefined , pathParent = [ ] ) {
89196 // Fill the path argument through recursive call
90197 if ( ! path ) {
91198 if ( ! object . name ) {
92199 throw new Error ( 'Object name must exist' ) ;
93200 }
94201 path = object . name . split ( '/' ) ;
95- this . addChild ( object , path , [ ] ) ;
96- this . notify ( ) ;
202+ this . _addChild ( object , path ) ;
97203 return ;
98204 }
99205
@@ -122,15 +228,26 @@ export default class ObjectTree extends Observable {
122228 }
123229
124230 // Pass to child
125- subtree . addChild ( object , path , fullPath ) ;
231+ subtree . _addChild ( object , path , fullPath ) ;
126232 }
127233
128234 /**
129- * Add a list of objects by calling `addChild`
235+ * Add a single object as a child node
236+ * @param {object } object - child to be added
237+ */
238+ addOneChild ( object ) {
239+ this . _addChild ( object ) ;
240+ this . loadExpandedNodes ( ) ;
241+ this . notify ( ) ;
242+ }
243+
244+ /**
245+ * Add a list of objects as child nodes
130246 * @param {Array<object> } objects - children to be added
131- * @returns {undefined }
132247 */
133248 addChildren ( objects ) {
134- objects . forEach ( ( object ) => this . addChild ( object ) ) ;
249+ objects . forEach ( ( object ) => this . _addChild ( object ) ) ;
250+ this . loadExpandedNodes ( ) ;
251+ this . notify ( ) ;
135252 }
136253}
0 commit comments