@onehat/data 1.21.20 → 1.22.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.
@@ -396,6 +396,12 @@ export default class Repository extends EventEmitter {
396
396
  }
397
397
  }
398
398
 
399
+ getModel() {
400
+ if (!this.isUnique) {
401
+ return this.name;
402
+ }
403
+ return this.name.match(/^([^-]*)-(.*)/)[1]; // converts 'ModelName-22f9915c-79f5-4e86-a25b-9446c7b85b63' to 'ModelName'
404
+ }
399
405
 
400
406
  // __ __
401
407
  // / / ____ ____ _____/ /
@@ -2127,95 +2133,6 @@ export default class Repository extends EventEmitter {
2127
2133
  }
2128
2134
 
2129
2135
 
2130
- // ______
2131
- // /_ __/_______ ___ _____
2132
- // / / / ___/ _ \/ _ \/ ___/
2133
- // / / / / / __/ __(__ )
2134
- // /_/ /_/ \___/\___/____/
2135
-
2136
-
2137
- /**
2138
- * Gets the root TreeNodes
2139
- */
2140
- getRootNodes() {
2141
- this.ensureTree();
2142
- if (this.isDestroyed) {
2143
- this.throwError('this.loadRootNodes is no longer valid. Repository has been destroyed.');
2144
- return;
2145
- }
2146
-
2147
- // Look through all entities and pull out the root nodes.
2148
- // Subclasses of Repository will override this method to get root nodes from server
2149
- const entities = _.filter(this.getEntities(), (entity) => {
2150
- return entity.isRoot;
2151
- })
2152
- return entities;
2153
- }
2154
-
2155
-
2156
- /**
2157
- * Populates the TreeNodes with .parent and .children references
2158
- */
2159
- assembleTreeNodes() {
2160
- this.ensureTree();
2161
- if (this.isDestroyed) {
2162
- this.throwError('this.assembleTreeNodes is no longer valid. Repository has been destroyed.');
2163
- return;
2164
- }
2165
-
2166
- const treeNodes = this.getEntities();
2167
-
2168
- // Reset all parent/child relationships
2169
- _.each(treeNodes, (treeNode) => {
2170
- treeNode.parent = null;
2171
- treeNode.children = [];
2172
- });
2173
-
2174
- // Rebuild all parent/child relationships
2175
- const oThis = this;
2176
- _.each(treeNodes, (treeNode) => {
2177
- const parent = oThis.getById(treeNode.parentId);
2178
- if (parent) {
2179
- treeNode.parent = parent;
2180
- parent.children.push(treeNode);
2181
- }
2182
- });
2183
- }
2184
-
2185
- /**
2186
- * Removes the treeNode and all of its children from repository
2187
- * without deleting anything on the server
2188
- */
2189
- removeTreeNode(treeNode) {
2190
- if (!_.isEmpty(treeNode.children)) {
2191
- const children = treeNode.children;
2192
- treeNode.parent = null;
2193
- treeNode.children = [];
2194
-
2195
- const oThis = this;
2196
- _.each(children, (child) => {
2197
- oThis.removeTreeNode(child);
2198
- });
2199
- }
2200
-
2201
- this.removeEntity(treeNode);
2202
- }
2203
-
2204
- /**
2205
- * Helper to make sure this Repository is a tree
2206
- * @private
2207
- */
2208
- async ensureTree() {
2209
- if (!this.isTree) {
2210
- this.throwError('This Repository is not a tree!');
2211
- return false;
2212
- }
2213
- return true;
2214
- }
2215
-
2216
-
2217
-
2218
-
2219
2136
 
2220
2137
  // __ ____ _ ___ __ _
2221
2138
  // / / / / /_(_) (_) /_(_)__ _____
@@ -0,0 +1,445 @@
1
+ /** @module Repository */
2
+
3
+ import Repository from './Repository.js'; // so we can use static methods
4
+ import OneBuildRepository from './OneBuild.js';
5
+ import _ from 'lodash';
6
+
7
+
8
+ /**
9
+ * This class is used for OneBuild Trees
10
+ * that contain multiple node types.
11
+ *
12
+ * @extends TreeRepository
13
+ */
14
+ class TreeRepository extends OneBuildRepository {
15
+ constructor(config = {}) {
16
+ super(...arguments);
17
+
18
+ const defaults = {
19
+
20
+ isTree: true,
21
+ rootNodeType: this.getModel(), // e.g. 'Fleets'
22
+
23
+ api: {
24
+ getNodes: 'getNodes',
25
+ moveNode: 'moveNode',
26
+ searchNodes: 'searchNodes',
27
+ },
28
+
29
+ editableNodeTypes: [
30
+ this.getModel(),
31
+ ],
32
+
33
+ };
34
+ _.merge(this, defaults, config);
35
+
36
+ }
37
+
38
+ async initialize() {
39
+
40
+ this.registerEvents([
41
+ 'loadRootNodes',
42
+ ]);
43
+
44
+ await super.initialize();
45
+ }
46
+
47
+ getModelFromTreeNode(treeNode) {
48
+ return treeNode.nodeType || this.rootNodeType;
49
+ }
50
+
51
+ /**
52
+ * Loads the root nodes of this tree.
53
+ */
54
+ loadRootNodes(depth) {
55
+ this.ensureTree();
56
+ if (this.isDestroyed) {
57
+ this.throwError('this.setRootNode is no longer valid. Repository has been destroyed.');
58
+ return;
59
+ }
60
+ if (!this.isOnline) {
61
+ this.throwError('Offline');
62
+ return;
63
+ }
64
+
65
+ this.emit('beforeLoad'); // TODO: canceling beforeLoad will cancel the load operation
66
+ this.markLoading();
67
+
68
+ const
69
+ data = _.merge({ depth }, this._baseParams, this._params),
70
+ url = this.rootNodeType + '/' + this.api.getNodes;
71
+
72
+ if (this.debugMode) {
73
+ console.log('loadRootNodes', data);
74
+ }
75
+
76
+ return this._send('POST', url, data)
77
+ .then((result) => {
78
+ if (this.debugMode) {
79
+ console.log('Response for loadRootNodes', result);
80
+ }
81
+
82
+ if (this.isDestroyed) {
83
+ // If this repository gets destroyed before it has a chance
84
+ // to process the Ajax request, just ignore the response.
85
+ return;
86
+ }
87
+
88
+ const {
89
+ root,
90
+ success,
91
+ total,
92
+ message
93
+ } = this._processServerResponse(result);
94
+
95
+ if (!success) {
96
+ this.throwError(message);
97
+ return;
98
+ }
99
+
100
+ this._destroyEntities();
101
+
102
+ // Set the current entities
103
+ const oThis = this;
104
+ this.entities = _.map(root, (data) => {
105
+ const entity = Repository._createEntity(oThis.schema, data, this, true);
106
+ oThis._relayEntityEvents(entity);
107
+ return entity;
108
+ });
109
+
110
+ this.assembleTreeNodes();
111
+
112
+ // Set the total records that pass filter
113
+ this.total = total;
114
+ this._setPaginationVars();
115
+
116
+ this.areRootNodesLoaded = true;
117
+
118
+
119
+ // Don't emit events for root nodes...
120
+ this.rehash();
121
+ this.emit('loadRootNodes', this);
122
+ // this.emit('changeData', this.entities);
123
+
124
+ return this.getBy((entity) => {
125
+ return entity.isRoot;
126
+ });
127
+ })
128
+ .finally(() => {
129
+ this.markLoading(false);
130
+ });
131
+ }
132
+
133
+ /**
134
+ * Loads (or reloads) the supplied treeNode
135
+ */
136
+ loadNode(treeNode, depth = 1) {
137
+ this.ensureTree();
138
+ if (this.isDestroyed) {
139
+ this.throwError('this.loadNode is no longer valid. Repository has been destroyed.');
140
+ return;
141
+ }
142
+ if (!this.isOnline) {
143
+ this.throwError('Offline');
144
+ return;
145
+ }
146
+
147
+ // If children already exist, remove them from the repository
148
+ // This way, we can reload just a portion of the tree
149
+ if (!_.isEmpty(treeNode.children)) {
150
+ _.each(treeNode.children, (child) => {
151
+ treeNode.repository.removeTreeNode(child);
152
+ });
153
+ treeNode.children = [];
154
+ }
155
+
156
+ this.markLoading();
157
+
158
+ const
159
+ data = _.merge({ depth, nodeId: treeNode.id, }, this._baseParams, this._params),
160
+ url = treeNode.getModel() + '/' + this.api.getNodes;
161
+
162
+ if (this.debugMode) {
163
+ console.log('loadNode', data);
164
+ }
165
+
166
+ return this._send('POST', url, data)
167
+ .then((result) => {
168
+ if (this.debugMode) {
169
+ console.log('Response for loadNode', result);
170
+ }
171
+
172
+ if (this.isDestroyed) {
173
+ // If this repository gets destroyed before it has a chance
174
+ // to process the Ajax request, just ignore the response.
175
+ return;
176
+ }
177
+
178
+ const {
179
+ root,
180
+ success,
181
+ total,
182
+ message
183
+ } = this._processServerResponse(result);
184
+
185
+ if (!success) {
186
+ this.throwError(message);
187
+ return;
188
+ }
189
+
190
+ // Set the current entities
191
+ const oThis = this;
192
+ const children = _.map(root, (data) => {
193
+ const entity = Repository._createEntity(oThis.schema, data, this, true);
194
+ oThis._relayEntityEvents(entity);
195
+ return entity;
196
+ });
197
+
198
+ this.entities = this.entities.concat(children);
199
+
200
+ this.assembleTreeNodes();
201
+
202
+ this._setPaginationVars();
203
+
204
+ this.rehash();
205
+ // this.emit('changeData', this.entities);
206
+ this.emit('load', this);
207
+
208
+ return children;
209
+ })
210
+ .finally(() => {
211
+ this.markLoading(false);
212
+ });
213
+ }
214
+
215
+ /**
216
+ * alias for backward compatibility
217
+ */
218
+ loadChildNodes(treeNode, depth = 1) {
219
+ return this.loadNode(treeNode, depth);
220
+ }
221
+
222
+ /**
223
+ * Override the AjaxRepository to we can reload a treeNode if needed
224
+ */
225
+ reloadEntity(entity, callback = null) {
226
+ if (!entity.isTree) {
227
+ return super.reloadEntity(entity, callback);
228
+ }
229
+
230
+ return this.loadNode(entity, 1);
231
+ }
232
+
233
+ /**
234
+ * Searches all nodes for the supplied text.
235
+ * This basically takes the search query and returns whatever the server sends
236
+ */
237
+ searchNodes(q) {
238
+ this.ensureTree();
239
+ if (this.isDestroyed) {
240
+ this.throwError('this.searchNodes is no longer valid. Repository has been destroyed.');
241
+ return;
242
+ }
243
+ if (!this.isOnline) {
244
+ this.throwError('Offline');
245
+ return;
246
+ }
247
+
248
+ const
249
+ data = _.merge({ q, }, this._baseParams, this._params),
250
+ url = this.rootNodeType + '/' + this.api.searchNodes;
251
+
252
+ if (this.debugMode) {
253
+ console.log('searchNodes', data);
254
+ }
255
+
256
+ return this._send('POST', url, data)
257
+ .then((result) => {
258
+ if (this.debugMode) {
259
+ console.log('Response for searchNodes', result);
260
+ }
261
+
262
+ if (this.isDestroyed) {
263
+ // If this repository gets destroyed before it has a chance
264
+ // to process the Ajax request, just ignore the response.
265
+ return;
266
+ }
267
+
268
+ const {
269
+ root,
270
+ success,
271
+ total,
272
+ message
273
+ } = this._processServerResponse(result);
274
+
275
+ if (!success) {
276
+ this.throwError(message);
277
+ return;
278
+ }
279
+
280
+ return root;
281
+ })
282
+ .finally(() => {
283
+ this.markLoading(false);
284
+ });
285
+ }
286
+
287
+ /**
288
+ * Moves the supplied treeNode to a new position on the tree
289
+ * @returns id of common ancestor node
290
+ */
291
+ moveTreeNode(treeNode, newParentId) {
292
+ this.ensureTree();
293
+ if (this.isDestroyed) {
294
+ this.throwError('this.moveTreeNode is no longer valid. Repository has been destroyed.');
295
+ return;
296
+ }
297
+ if (!this.isOnline) {
298
+ this.throwError('Offline');
299
+ return;
300
+ }
301
+
302
+ const
303
+ oldParentId = treeNode.parent?.id,
304
+ data = _.merge({ nodeId: treeNode.id, parentId: newParentId, }, this._baseParams, this._params),
305
+ url = this.rootNodeType + '/' + this.api.moveNode;
306
+
307
+ // NOTE: The rootNodeType controller needs to know about all the possible nodeTypes,
308
+ // so any particular node can move all around the tree.
309
+
310
+ if (this.debugMode) {
311
+ console.log('moveTreeNode', data);
312
+ }
313
+
314
+ return this._send('POST', url, data)
315
+ .then((result) => {
316
+ if (this.debugMode) {
317
+ console.log('Response for searchNodes', result);
318
+ }
319
+
320
+ if (this.isDestroyed) {
321
+ // If this repository gets destroyed before it has a chance
322
+ // to process the Ajax request, just ignore the response.
323
+ return;
324
+ }
325
+
326
+ const {
327
+ root: {
328
+ commonAncestorId,
329
+ oldParent,
330
+ newParent,
331
+ node,
332
+ },
333
+ success,
334
+ total,
335
+ message
336
+ } = this._processServerResponse(result);
337
+
338
+ if (!success) {
339
+ this.throwError(message);
340
+ return;
341
+ }
342
+
343
+ // move it from oldParent.children to newParent.children
344
+ const
345
+ oldParentRecord = this.getById(oldParentId),
346
+ newParentRecord = this.getById(newParentId);
347
+
348
+ oldParentRecord?.loadOriginalData(oldParent);
349
+ newParentRecord.loadOriginalData(newParent);
350
+ treeNode.loadOriginalData(node);
351
+
352
+ this.assembleTreeNodes();
353
+
354
+ return commonAncestorId;
355
+ })
356
+ .finally(() => {
357
+ this.markLoading(false);
358
+ });
359
+ }
360
+
361
+ /**
362
+ * Gets the root TreeNodes
363
+ */
364
+ getRootNodes() {
365
+ this.ensureTree();
366
+ if (this.isDestroyed) {
367
+ this.throwError('this.loadRootNodes is no longer valid. Repository has been destroyed.');
368
+ return;
369
+ }
370
+
371
+ // Look through all entities and pull out the root nodes.
372
+ // Subclasses of Repository will override this method to get root nodes from server
373
+ const entities = _.filter(this.getEntities(), (entity) => {
374
+ return entity.isRoot;
375
+ })
376
+ return entities;
377
+ }
378
+
379
+ /**
380
+ * Populates the TreeNodes with .parent and .children references
381
+ */
382
+ assembleTreeNodes() {
383
+ this.ensureTree();
384
+ if (this.isDestroyed) {
385
+ this.throwError('this.assembleTreeNodes is no longer valid. Repository has been destroyed.');
386
+ return;
387
+ }
388
+
389
+ const treeNodes = this.getEntities();
390
+
391
+ // Reset all parent/child relationships
392
+ _.each(treeNodes, (treeNode) => {
393
+ treeNode.parent = null;
394
+ treeNode.children = [];
395
+ });
396
+
397
+ // Rebuild all parent/child relationships
398
+ const oThis = this;
399
+ _.each(treeNodes, (treeNode) => {
400
+ const parent = oThis.getById(treeNode.parentId);
401
+ if (parent) {
402
+ treeNode.parent = parent;
403
+ parent.children.push(treeNode);
404
+ }
405
+ });
406
+ }
407
+
408
+ /**
409
+ * Removes the treeNode and all of its children from repository
410
+ * without deleting anything on the server
411
+ */
412
+ removeTreeNode(treeNode) {
413
+ if (!_.isEmpty(treeNode.children)) {
414
+ const children = treeNode.children;
415
+ treeNode.parent = null;
416
+ treeNode.children = [];
417
+
418
+ const oThis = this;
419
+ _.each(children, (child) => {
420
+ oThis.removeTreeNode(child);
421
+ });
422
+ }
423
+
424
+ this.removeEntity(treeNode);
425
+ }
426
+
427
+ /**
428
+ * Helper to make sure this Repository is a tree
429
+ * @private
430
+ */
431
+ async ensureTree() {
432
+ if (!this.isTree) {
433
+ this.throwError('This Repository is not a tree!');
434
+ return false;
435
+ }
436
+ return true;
437
+ }
438
+
439
+ }
440
+
441
+
442
+ TreeRepository.className = 'Tree';
443
+ TreeRepository.type = 'tree';
444
+
445
+ export default TreeRepository;
@@ -7,6 +7,7 @@ import MemoryRepository from './Memory.js';
7
7
  import NullRepository from './Null.js';
8
8
  import OneBuildRepository from './OneBuild.js';
9
9
  import RestRepository from './Rest.js';
10
+ import TreeRepository from './Tree.js';
10
11
 
11
12
  const CoreRepositoryTypes = {
12
13
  [AjaxRepository.type]: AjaxRepository,
@@ -16,6 +17,7 @@ const CoreRepositoryTypes = {
16
17
  [NullRepository.type]: NullRepository,
17
18
  [OneBuildRepository.type]: OneBuildRepository,
18
19
  [RestRepository.type]: RestRepository,
20
+ [TreeRepository.type]: TreeRepository,
19
21
  };
20
22
 
21
23
  export default CoreRepositoryTypes;
@@ -89,11 +89,6 @@ export default class Schema extends EventEmitter {
89
89
  */
90
90
  hasChildrenProperty: null,
91
91
 
92
- /**
93
- * @member {boolean} isTree - Whether this model has hierarchical tree data
94
- */
95
- isTree: false,
96
-
97
92
  /**
98
93
  * @member {boolean} isAdjacencyList - Whether this tree is an Adjacency List
99
94
  */