@myrmidon/paged-data-browsers 5.0.2 → 5.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +101 -14
- package/fesm2022/myrmidon-paged-data-browsers.mjs +831 -66
- package/fesm2022/myrmidon-paged-data-browsers.mjs.map +1 -1
- package/index.d.ts +255 -10
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Paged Data Browsers
|
|
2
2
|
|
|
3
|
+
- [Paged Data Browsers](#paged-data-browsers)
|
|
4
|
+
- [Paged List](#paged-list)
|
|
5
|
+
- [Paged Tree](#paged-tree)
|
|
6
|
+
- [Nodes](#nodes)
|
|
7
|
+
- [TreeNode](#treenode)
|
|
8
|
+
- [PagedTreeNode](#pagedtreenode)
|
|
9
|
+
- [Services](#services)
|
|
10
|
+
- [PagedTreeStoreService](#pagedtreestoreservice)
|
|
11
|
+
- [EditablePagedTreeStoreService](#editablepagedtreestoreservice)
|
|
12
|
+
- [PagedTreeStore](#pagedtreestore)
|
|
13
|
+
- [Node Component](#node-component)
|
|
14
|
+
|
|
3
15
|
This library provides simple components to display filtered and paged data from some service.
|
|
4
16
|
|
|
5
17
|
There are currently two components, one for displaying a flat list of data, and another to display hierarchically structured data, combining a tree view with paging.
|
|
@@ -21,47 +33,122 @@ So you need:
|
|
|
21
33
|
|
|
22
34
|
## Paged Tree
|
|
23
35
|
|
|
36
|
+
The paged tree is a tree where each node can page and filter its direct children.
|
|
37
|
+
|
|
24
38
|
### Nodes
|
|
25
39
|
|
|
40
|
+
#### TreeNode
|
|
41
|
+
|
|
26
42
|
A paged tree is based on **nodes** of type `TreeNode` or its derived types. This basic tree node contains:
|
|
27
43
|
|
|
28
44
|
- `id`: a unique numeric ID.
|
|
29
45
|
- `parentId`: the ID of the parent, or undefined if it's a root-level node.
|
|
30
|
-
- `y`: Y is the node depth, starting from 1. As we often need a single root node (for paging its children), unless we have a small number of root nodes, when your data source does not return a single root node you have the option of using a mock root. In this case, the mock root Y level will be 0 and all the other nodes will descend from it. Anyway you can also have many root level nodes, whose parent ID is undefined; but in this case all these nodes will appear at once without any paging, because paging is governed by the parent node.
|
|
46
|
+
- `y`: Y is the node depth, starting from 1. As we often need a single root node (for paging its children), unless we have a small number of root nodes, when your data source does not return a single root node you have the option of using a mock root. In this case, the mock root's Y level will be 0 and all the other nodes will descend from it. Anyway you can also have many root level nodes, whose parent ID is undefined; but in this case all these nodes will appear at once without any paging, because paging is governed by the parent node.
|
|
31
47
|
- `x`: X is the ordinal number of the node among its siblings. The first child of a given node has X=1, the second has X=2, and so forth.
|
|
32
48
|
- `label`: a human-friendly label for the node.
|
|
33
49
|
- `tag`: a tag string for virtually categorizing or grouping the node in some way.
|
|
34
|
-
- `hasChildren`: true if the node has children, false if it has no children; undefined if this has not yet been determined.
|
|
50
|
+
- `hasChildren`: true if the node has children, false if it has no children; undefined if this has not yet been determined. This is initially undefined, until the service fetching data has been used to retrieve the children of a node. After that, if no nodes were returned, `hasChildren` is `false` and this will prevent further roundtrips to the server.
|
|
51
|
+
|
|
52
|
+
#### PagedTreeNode
|
|
35
53
|
|
|
36
|
-
A **paged tree node** type derives from `TreeNode
|
|
54
|
+
A **paged tree node** type (`PagedTreeNode<F>`) derives from this basic `TreeNode`, adding two essential components for filtering and paging its children:
|
|
55
|
+
|
|
56
|
+
- a _filter_ for its children. Node filters derive from `TreeNodeFilter`, which just refers to `TreeNode`'s properties: so, the base filter just has a tag and a parent ID. Typically you derive your own filter from this type.
|
|
57
|
+
|
|
58
|
+
>⚠️ It is very important to take into account the parent ID property of the base filter when implementing the paging service.
|
|
37
59
|
|
|
38
|
-
- a _filter_ for its children. The basic `TreeNodeFilter` just refers to `TreeNode` properties, so it just has tag and parent ID. You can derive your own filter from this type.
|
|
39
60
|
- _paging information_ for its children. This is of type `PagingInfo` which has page number, page items count, and total items count.
|
|
40
61
|
|
|
41
|
-
The base type for a paged tree node is thus `PagedTreeNode<F>`, where `F` is the type of the filter used for filtering nodes; this adds:
|
|
62
|
+
The base type for a paged tree node is thus `PagedTreeNode<F>`, where `F` is the type of the filter used for filtering nodes; this adds properties:
|
|
42
63
|
|
|
43
64
|
- `paging`: paging information. This is required.
|
|
44
|
-
- `expanded`: for expanded/collapsed state.
|
|
45
|
-
- `filter`: filter object.
|
|
65
|
+
- `expanded`: for expanded/collapsed state. Initially this is `undefined`.
|
|
66
|
+
- `filter`: filter object. If not set, there will be no filtering and thus all the children will be included.
|
|
46
67
|
|
|
47
68
|
### Services
|
|
48
69
|
|
|
49
|
-
|
|
70
|
+
#### PagedTreeStoreService
|
|
50
71
|
|
|
51
|
-
|
|
72
|
+
Typically, data for the tree is dynamically loaded. It can also be loaded all at once; in both cases, data will always be provided by a service implementing the same interface. This is the paged tree store's service, which fetches data from your data source on behalf of the paged tree store.
|
|
52
73
|
|
|
53
|
-
|
|
74
|
+
So, your data must be provided by this service which implements interface `PagedTreeStoreService<F>`, where `F` is the tree node filter type. The interface requires you to implement a single function, `getNodes`, which returns a specific page of nodes, applying the specified filter:
|
|
75
|
+
|
|
76
|
+
```ts
|
|
77
|
+
getNodes(
|
|
78
|
+
filter: F,
|
|
79
|
+
pageNumber: number,
|
|
80
|
+
pageSize: number,
|
|
81
|
+
hasMockRoot?: boolean
|
|
82
|
+
): Observable<DataPage<TreeNode>>;
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
>This service deals with `TreeNode`'s (or their derivations), returning a specific page of them. It is the service which is directly in contact with your data source, and its only purpose is to return a page of nodes. It is the store which extends these nodes with paging and filtering data using the `PagedTreeNode` type. Note that if you want a single root mock node, your implementation of this service must provide it (with Y=0).
|
|
86
|
+
|
|
87
|
+
#### EditablePagedTreeStoreService
|
|
88
|
+
|
|
89
|
+
For editing scenarios, you can use `EditablePagedTreeStoreService<F>` which extends the base service interface with editing capabilities. This service maintains changes in memory until they are explicitly saved, allowing for:
|
|
90
|
+
|
|
91
|
+
- **Change tracking**: All modifications (add, remove, update) are tracked internally
|
|
92
|
+
- **Optimistic updates**: The service's `getNodes` method returns data including unsaved changes
|
|
93
|
+
- **Batch saving**: All changes can be committed to the data source at once
|
|
94
|
+
- **ID mapping**: Temporary IDs for new nodes are mapped to permanent IDs after saving
|
|
95
|
+
|
|
96
|
+
The interface adds these methods:
|
|
97
|
+
|
|
98
|
+
```ts
|
|
99
|
+
// Save all pending changes to the data source
|
|
100
|
+
saveChanges(): Observable<Map<number, number>>;
|
|
101
|
+
|
|
102
|
+
// Check if there are any unsaved changes
|
|
103
|
+
hasChanges(): boolean;
|
|
104
|
+
|
|
105
|
+
// Clear all pending changes without saving
|
|
106
|
+
clearChanges(): void;
|
|
107
|
+
|
|
108
|
+
// Get all pending change operations
|
|
109
|
+
getChanges(): ChangeOperation[];
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
A base implementation `EditablePagedTreeStoreServiceBase<F>` is provided that handles all the change tracking logic. You only need to implement:
|
|
113
|
+
|
|
114
|
+
- `fetchNodes()`: Get nodes from your actual data source
|
|
115
|
+
- `persistChanges()`: Save changes to your data source
|
|
116
|
+
|
|
117
|
+
This design ensures full backward compatibility - existing readonly implementations continue to work unchanged.
|
|
118
|
+
|
|
119
|
+
#### PagedTreeStore
|
|
120
|
+
|
|
121
|
+
The tree logic is implemented by the `PagedTreeStore<E,F>`, where:
|
|
122
|
+
|
|
123
|
+
- `E` is the element (node) type (a `PagedTreeNode<F>` or any derived type);
|
|
124
|
+
- `F` is the filter type (a `TreeNodeFilter` or any derived type).
|
|
54
125
|
|
|
55
126
|
The store is used to load and manage a flat list of nodes. Every tree node in the list is extended with page number, page count and total items, plus expansion-related metadata. Users can expand and collapse nodes, browse through pages of children, and filter them.
|
|
56
127
|
|
|
128
|
+
When using an `EditablePagedTreeStoreService`, the store also supports editing operations:
|
|
129
|
+
|
|
130
|
+
- `addChild(parentId, child, first)`: Add a child node to the specified parent
|
|
131
|
+
- `addSibling(anchorId, sibling, before)`: Add a sibling node relative to an anchor node
|
|
132
|
+
- `removeNode(nodeId)`: Remove a node and optionally its descendants
|
|
133
|
+
- `replaceNode(oldNodeId, newNode, keepDescendants)`: Replace a node with new data
|
|
134
|
+
- `saveChanges()`: Persist all changes to the data source
|
|
135
|
+
- `hasUnsavedChanges()`: Check if there are pending changes
|
|
136
|
+
- `clearUnsavedChanges()`: Discard all pending changes
|
|
137
|
+
|
|
138
|
+
All editing operations automatically handle:
|
|
139
|
+
- Position recalculation (x coordinates)
|
|
140
|
+
- Parent `hasChildren` status updates
|
|
141
|
+
- Cache invalidation
|
|
142
|
+
- UI updates for expanded nodes
|
|
143
|
+
|
|
57
144
|
The essential store **data** are:
|
|
58
145
|
|
|
59
|
-
- the flat _list of paged nodes_ (exposed in `nodes$`). This is populated by using an instance of `PagedTreeStoreService<F
|
|
146
|
+
- the flat _list of paged nodes_ (exposed in `nodes$`). This flat list is populated by using an instance of the paged tree store's service (`PagedTreeStoreService<F>`), injected in the store constructor together with its options (of type `PagedTreeStoreOptions`). Among other things, the options specify whether there must be a single mock root node, and the default page size. Once retrieved, pages can be fetched from an internal LRU cache (which is cleared when calling `reset`).
|
|
60
147
|
- a list of tree _tags_ (exposed in `tags$`).
|
|
61
148
|
- a _global filter_ (exposed in `filter$`). This gets combined with (overridden by) node-specific filters, when specified. You can set it with `setFilter`. To set the filter for the children of a specific node use `setNodeFilter`.
|
|
62
149
|
- the _page size_ (`pageSize`), a get/set property, initially set by the options injected in the store constructor. Setting this property resets the store (like calling `reset`).
|
|
63
150
|
|
|
64
|
-
To initialize the store, you call `reset`, which loads root nodes (via its service's `
|
|
151
|
+
To initialize the store, you call `reset`, which loads root nodes (via its service's `getNodes`) and their direct children. Users then expand or collapse nodes, change page, and set the global or node filters as desired.
|
|
65
152
|
|
|
66
153
|
The main **methods** are:
|
|
67
154
|
|
|
@@ -69,7 +156,7 @@ The main **methods** are:
|
|
|
69
156
|
- `clear()`: clear the whole store, emptying the cache and removing all the nodes.
|
|
70
157
|
- `clearCache()`: clears the pages cache.
|
|
71
158
|
- `hasCachedPage(pageNumber, filter)`: checks whether the specified page is cached.
|
|
72
|
-
- `
|
|
159
|
+
- `isEmpty()`: true if the list is empty.
|
|
73
160
|
- `getNodes()`: gets all the nodes in the list.
|
|
74
161
|
- `getRootNode()`: returns the root node, i.e. the first node in the list (unless this is empty).
|
|
75
162
|
- `getChildren(id)`: gets the children of the specified node.
|
|
@@ -83,7 +170,7 @@ The main **methods** are:
|
|
|
83
170
|
- `setFilter(filter)`: sets the global filter, resetting the store.
|
|
84
171
|
- `setNodeFilter(id, filter)`: sets the node-filter for the specified node, resetting its children page number to 1.
|
|
85
172
|
|
|
86
|
-
The store is a plain class. If you want to persist it in the app, you can wrap it in a service and inject the service into your component. If you don't need this, just implement your data service to provide nodes via `getNodes`. Otherwise, you can wrap the store like e.g. here using a singleton for a single page of nodes in the demo app:
|
|
173
|
+
The store is a plain TypeScript class. If you want to **persist** it in the app, you can wrap it in a service and inject the service into your component. If you don't need this, just implement your data service to provide nodes via `getNodes`. Otherwise, you can wrap the store like e.g. here using a singleton for a single page of nodes in the demo app:
|
|
87
174
|
|
|
88
175
|
```ts
|
|
89
176
|
@Injectable({
|