@onehat/data 1.21.20 → 1.21.21
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/cypress/e2e/Property/Mixed.cy.js +105 -0
- package/cypress/e2e/Property/Property.cy.js +7 -1
- package/cypress/e2e/Repository/Ajax.cy.js +15 -0
- package/cypress/support/e2e.js +1 -0
- package/cypress/support/index.js +20 -0
- package/cypress.config.js +18 -16
- package/package.json +16 -18
- package/src/Entity/Entity.js +32 -21
- package/src/OneHatData.js +7 -5
- package/src/Property/Currency.js +1 -1
- package/src/Property/Json.js +1 -1
- package/src/Property/Mixed.js +458 -0
- package/src/Property/Property.js +21 -0
- package/src/Property/index.js +2 -0
- package/src/Reader/index.js +2 -2
- package/src/Repository/Ajax.js +241 -28
- package/src/Repository/OneBuild.js +73 -434
- package/src/Repository/Repository.js +41 -94
- package/src/Repository/Tree.js +452 -0
- package/src/Repository/index.js +2 -0
- package/src/Schema/Schema.js +6 -9
- package/src/Writer/index.js +2 -2
- package/src/Repository/OneBuild2.js +0 -953
|
@@ -190,6 +190,11 @@ export default class Repository extends EventEmitter {
|
|
|
190
190
|
*/
|
|
191
191
|
this.page = 1;
|
|
192
192
|
|
|
193
|
+
/**
|
|
194
|
+
* @member {number} previousPage - State: Previous page number in pagination
|
|
195
|
+
*/
|
|
196
|
+
this.previousPage = null;
|
|
197
|
+
|
|
193
198
|
/**
|
|
194
199
|
* Getter for
|
|
195
200
|
* @member {number} pageTotal - State: Total number of entities on the current page
|
|
@@ -216,6 +221,7 @@ export default class Repository extends EventEmitter {
|
|
|
216
221
|
|
|
217
222
|
/**
|
|
218
223
|
* @member {number} total - Total number of entities in remote storage that pass filters
|
|
224
|
+
* Example: "Showing 21-30 of 45" This would be 45
|
|
219
225
|
*/
|
|
220
226
|
this.total = 0;
|
|
221
227
|
|
|
@@ -255,11 +261,6 @@ export default class Repository extends EventEmitter {
|
|
|
255
261
|
*/
|
|
256
262
|
this.lastLoaded = null;
|
|
257
263
|
|
|
258
|
-
/**
|
|
259
|
-
* @member {boolean} areRootNodesLoaded - State: whether or not root nodes have been loaded at least once
|
|
260
|
-
*/
|
|
261
|
-
this.areRootNodesLoaded = false;
|
|
262
|
-
|
|
263
264
|
/**
|
|
264
265
|
* @member {boolean} isSaving - State: whether or not entities are currently being saved
|
|
265
266
|
*/
|
|
@@ -349,6 +350,7 @@ export default class Repository extends EventEmitter {
|
|
|
349
350
|
|
|
350
351
|
this._createMethods();
|
|
351
352
|
this._createStatics();
|
|
353
|
+
this._createListeners();
|
|
352
354
|
|
|
353
355
|
const init = this.schema.repository.init || this.originalConfig.init; // The latter is mainly for lfr repositories
|
|
354
356
|
if (init) {
|
|
@@ -396,6 +398,37 @@ export default class Repository extends EventEmitter {
|
|
|
396
398
|
}
|
|
397
399
|
}
|
|
398
400
|
|
|
401
|
+
/**
|
|
402
|
+
* Creates the initial listeners for this Repository, based on originalConfig.
|
|
403
|
+
* @private
|
|
404
|
+
*/
|
|
405
|
+
_createListeners() {
|
|
406
|
+
if (this.isDestroyed) {
|
|
407
|
+
this.throwError('this._createListeners is no longer valid. Repository has been destroyed.');
|
|
408
|
+
return;
|
|
409
|
+
}
|
|
410
|
+
const listeners = this.originalConfig.listeners;
|
|
411
|
+
if (!_.isEmpty(listeners)) {
|
|
412
|
+
const oThis = this;
|
|
413
|
+
_.each(listeners, ({ event, handler }) => {
|
|
414
|
+
if (!event || !handler) {
|
|
415
|
+
oThis.throwError('Invalid listener definition. Must have event and handler.');
|
|
416
|
+
return;
|
|
417
|
+
}
|
|
418
|
+
oThis.on(event, handler);
|
|
419
|
+
});
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
getModel() {
|
|
424
|
+
if (this.model) {
|
|
425
|
+
return this.model;
|
|
426
|
+
}
|
|
427
|
+
if (!this.isUnique) {
|
|
428
|
+
return this.name;
|
|
429
|
+
}
|
|
430
|
+
return this.name.match(/^([^-]*)-(.*)/)[1]; // converts 'ModelName-22f9915c-79f5-4e86-a25b-9446c7b85b63' to 'ModelName'
|
|
431
|
+
}
|
|
399
432
|
|
|
400
433
|
// __ __
|
|
401
434
|
// / / ____ ____ _____/ /
|
|
@@ -912,6 +945,8 @@ export default class Repository extends EventEmitter {
|
|
|
912
945
|
return false;
|
|
913
946
|
}
|
|
914
947
|
|
|
948
|
+
this.previousPage = this.page;
|
|
949
|
+
|
|
915
950
|
// Reset to page 1 (don't use setPage(), so we can skip _onChangePagination, which we'll do later)
|
|
916
951
|
this.page = 1;
|
|
917
952
|
this.emit('changePage');
|
|
@@ -947,6 +982,7 @@ export default class Repository extends EventEmitter {
|
|
|
947
982
|
return false;
|
|
948
983
|
}
|
|
949
984
|
|
|
985
|
+
this.previousPage = this.page;
|
|
950
986
|
this.page = page;
|
|
951
987
|
this.emit('changePage');
|
|
952
988
|
if (this._onChangePagination) {
|
|
@@ -2127,95 +2163,6 @@ export default class Repository extends EventEmitter {
|
|
|
2127
2163
|
}
|
|
2128
2164
|
|
|
2129
2165
|
|
|
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
2166
|
|
|
2220
2167
|
// __ ____ _ ___ __ _
|
|
2221
2168
|
// / / / / /_(_) (_) /_(_)__ _____
|
|
@@ -0,0 +1,452 @@
|
|
|
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.markLoaded();
|
|
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
|
+
|
|
191
|
+
// Set the current entities
|
|
192
|
+
const children = [];
|
|
193
|
+
_.each(root, (data) => {
|
|
194
|
+
if (data.id === treeNode.id) {
|
|
195
|
+
// This is the node we're loading, so update it directly
|
|
196
|
+
treeNode.loadOriginalData(data);
|
|
197
|
+
return null;
|
|
198
|
+
}
|
|
199
|
+
const entity = Repository._createEntity(this.schema, data, this, true);
|
|
200
|
+
this._relayEntityEvents(entity);
|
|
201
|
+
children.push(entity);
|
|
202
|
+
});
|
|
203
|
+
if (children.length) {
|
|
204
|
+
this.entities = this.entities.concat(children);
|
|
205
|
+
this.assembleTreeNodes();
|
|
206
|
+
this._setPaginationVars();
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
this.markLoaded();
|
|
210
|
+
|
|
211
|
+
this.rehash();
|
|
212
|
+
// this.emit('changeData', this.entities);
|
|
213
|
+
this.emit('load', this);
|
|
214
|
+
|
|
215
|
+
return treeNode;
|
|
216
|
+
})
|
|
217
|
+
.finally(() => {
|
|
218
|
+
this.markLoading(false);
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* alias for backward compatibility
|
|
224
|
+
*/
|
|
225
|
+
loadChildNodes(treeNode, depth = 1) {
|
|
226
|
+
return this.loadNode(treeNode, depth);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Override the AjaxRepository to we can reload a treeNode if needed
|
|
231
|
+
*/
|
|
232
|
+
reloadEntity(entity, callback = null) {
|
|
233
|
+
if (!entity.isTree) {
|
|
234
|
+
return super.reloadEntity(entity, callback);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
return this.loadNode(entity, 1);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Searches all nodes for the supplied text.
|
|
242
|
+
* This basically takes the search query and returns whatever the server sends
|
|
243
|
+
*/
|
|
244
|
+
searchNodes(q) {
|
|
245
|
+
this.ensureTree();
|
|
246
|
+
if (this.isDestroyed) {
|
|
247
|
+
this.throwError('this.searchNodes is no longer valid. Repository has been destroyed.');
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
if (!this.isOnline) {
|
|
251
|
+
this.throwError('Offline');
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
const
|
|
256
|
+
data = _.merge({ q, }, this._baseParams, this._params),
|
|
257
|
+
url = this.rootNodeType + '/' + this.api.searchNodes;
|
|
258
|
+
|
|
259
|
+
if (this.debugMode) {
|
|
260
|
+
console.log('searchNodes', data);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
return this._send('POST', url, data)
|
|
264
|
+
.then((result) => {
|
|
265
|
+
if (this.debugMode) {
|
|
266
|
+
console.log('Response for searchNodes', result);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
if (this.isDestroyed) {
|
|
270
|
+
// If this repository gets destroyed before it has a chance
|
|
271
|
+
// to process the Ajax request, just ignore the response.
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
const {
|
|
276
|
+
root,
|
|
277
|
+
success,
|
|
278
|
+
total,
|
|
279
|
+
message
|
|
280
|
+
} = this._processServerResponse(result);
|
|
281
|
+
|
|
282
|
+
if (!success) {
|
|
283
|
+
this.throwError(message);
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
return root;
|
|
288
|
+
})
|
|
289
|
+
.finally(() => {
|
|
290
|
+
this.markLoading(false);
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Moves the supplied treeNode to a new position on the tree
|
|
296
|
+
* @returns id of common ancestor node
|
|
297
|
+
*/
|
|
298
|
+
moveTreeNode(treeNode, newParentId) {
|
|
299
|
+
this.ensureTree();
|
|
300
|
+
if (this.isDestroyed) {
|
|
301
|
+
this.throwError('this.moveTreeNode is no longer valid. Repository has been destroyed.');
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
if (!this.isOnline) {
|
|
305
|
+
this.throwError('Offline');
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
const
|
|
310
|
+
oldParentId = treeNode.parent?.id,
|
|
311
|
+
data = _.merge({ nodeId: treeNode.id, parentId: newParentId, }, this._baseParams, this._params),
|
|
312
|
+
url = this.rootNodeType + '/' + this.api.moveNode;
|
|
313
|
+
|
|
314
|
+
// NOTE: The rootNodeType controller needs to know about all the possible nodeTypes,
|
|
315
|
+
// so any particular node can move all around the tree.
|
|
316
|
+
|
|
317
|
+
if (this.debugMode) {
|
|
318
|
+
console.log('moveTreeNode', data);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
return this._send('POST', url, data)
|
|
322
|
+
.then((result) => {
|
|
323
|
+
if (this.debugMode) {
|
|
324
|
+
console.log('Response for searchNodes', result);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
if (this.isDestroyed) {
|
|
328
|
+
// If this repository gets destroyed before it has a chance
|
|
329
|
+
// to process the Ajax request, just ignore the response.
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
const {
|
|
334
|
+
root: {
|
|
335
|
+
commonAncestorId,
|
|
336
|
+
oldParent,
|
|
337
|
+
newParent,
|
|
338
|
+
node,
|
|
339
|
+
},
|
|
340
|
+
success,
|
|
341
|
+
total,
|
|
342
|
+
message
|
|
343
|
+
} = this._processServerResponse(result);
|
|
344
|
+
|
|
345
|
+
if (!success) {
|
|
346
|
+
this.throwError(message);
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// move it from oldParent.children to newParent.children
|
|
351
|
+
const
|
|
352
|
+
oldParentRecord = this.getById(oldParentId),
|
|
353
|
+
newParentRecord = this.getById(newParentId);
|
|
354
|
+
|
|
355
|
+
oldParentRecord?.loadOriginalData(oldParent);
|
|
356
|
+
newParentRecord.loadOriginalData(newParent);
|
|
357
|
+
treeNode.loadOriginalData(node);
|
|
358
|
+
|
|
359
|
+
this.assembleTreeNodes();
|
|
360
|
+
|
|
361
|
+
return commonAncestorId;
|
|
362
|
+
})
|
|
363
|
+
.finally(() => {
|
|
364
|
+
this.markLoading(false);
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* Gets the root TreeNodes
|
|
370
|
+
*/
|
|
371
|
+
getRootNodes() {
|
|
372
|
+
this.ensureTree();
|
|
373
|
+
if (this.isDestroyed) {
|
|
374
|
+
this.throwError('this.loadRootNodes is no longer valid. Repository has been destroyed.');
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// Look through all entities and pull out the root nodes.
|
|
379
|
+
// Subclasses of Repository will override this method to get root nodes from server
|
|
380
|
+
const entities = _.filter(this.getEntities(), (entity) => {
|
|
381
|
+
return entity.isRoot;
|
|
382
|
+
})
|
|
383
|
+
return entities;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* Populates the TreeNodes with .parent and .children references
|
|
388
|
+
*/
|
|
389
|
+
assembleTreeNodes() {
|
|
390
|
+
this.ensureTree();
|
|
391
|
+
if (this.isDestroyed) {
|
|
392
|
+
this.throwError('this.assembleTreeNodes is no longer valid. Repository has been destroyed.');
|
|
393
|
+
return;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
const treeNodes = this.getEntities();
|
|
397
|
+
|
|
398
|
+
// Reset all parent/child relationships
|
|
399
|
+
_.each(treeNodes, (treeNode) => {
|
|
400
|
+
treeNode.parent = null;
|
|
401
|
+
treeNode.children = [];
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
// Rebuild all parent/child relationships
|
|
405
|
+
const oThis = this;
|
|
406
|
+
_.each(treeNodes, (treeNode) => {
|
|
407
|
+
const parent = oThis.getById(treeNode.parentId);
|
|
408
|
+
if (parent) {
|
|
409
|
+
treeNode.parent = parent;
|
|
410
|
+
parent.children.push(treeNode);
|
|
411
|
+
}
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
/**
|
|
416
|
+
* Removes the treeNode and all of its children from repository
|
|
417
|
+
* without deleting anything on the server
|
|
418
|
+
*/
|
|
419
|
+
removeTreeNode(treeNode) {
|
|
420
|
+
if (!_.isEmpty(treeNode.children)) {
|
|
421
|
+
const children = treeNode.children;
|
|
422
|
+
treeNode.parent = null;
|
|
423
|
+
treeNode.children = [];
|
|
424
|
+
|
|
425
|
+
const oThis = this;
|
|
426
|
+
_.each(children, (child) => {
|
|
427
|
+
oThis.removeTreeNode(child);
|
|
428
|
+
});
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
this.removeEntity(treeNode);
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
/**
|
|
435
|
+
* Helper to make sure this Repository is a tree
|
|
436
|
+
* @private
|
|
437
|
+
*/
|
|
438
|
+
async ensureTree() {
|
|
439
|
+
if (!this.isTree) {
|
|
440
|
+
this.throwError('This Repository is not a tree!');
|
|
441
|
+
return false;
|
|
442
|
+
}
|
|
443
|
+
return true;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
|
|
449
|
+
TreeRepository.className = 'Tree';
|
|
450
|
+
TreeRepository.type = 'tree';
|
|
451
|
+
|
|
452
|
+
export default TreeRepository;
|
package/src/Repository/index.js
CHANGED
|
@@ -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;
|
package/src/Schema/Schema.js
CHANGED
|
@@ -75,24 +75,19 @@ export default class Schema extends EventEmitter {
|
|
|
75
75
|
* @member {string} parentIdProperty - name of parent_id Property (e.g. 'categories__parent_id' for Adjacency Lists, and parent_id for Closure Tables)
|
|
76
76
|
* For trees only
|
|
77
77
|
*/
|
|
78
|
-
parentIdProperty:
|
|
78
|
+
parentIdProperty: 'parentId',
|
|
79
79
|
|
|
80
80
|
/**
|
|
81
81
|
* @member {string} depthIdProperty - name of depth Property (e.g. 'categories__depth' for Adjacency Lists, and depth for Closure Tables)
|
|
82
82
|
* For trees only
|
|
83
83
|
*/
|
|
84
|
-
depthProperty:
|
|
84
|
+
depthProperty: 'depth',
|
|
85
85
|
|
|
86
86
|
/**
|
|
87
87
|
* @member {string} hasChildrenProperty - name of hasChildren Property (e.g. 'categories__has_children' for Adjacency Lists, and has_children for Closure Tables)
|
|
88
88
|
* For trees only
|
|
89
89
|
*/
|
|
90
|
-
hasChildrenProperty:
|
|
91
|
-
|
|
92
|
-
/**
|
|
93
|
-
* @member {boolean} isTree - Whether this model has hierarchical tree data
|
|
94
|
-
*/
|
|
95
|
-
isTree: false,
|
|
90
|
+
hasChildrenProperty: 'hasChildren',
|
|
96
91
|
|
|
97
92
|
/**
|
|
98
93
|
* @member {boolean} isAdjacencyList - Whether this tree is an Adjacency List
|
|
@@ -291,7 +286,9 @@ export default class Schema extends EventEmitter {
|
|
|
291
286
|
if (!propertyType) {
|
|
292
287
|
propertyType = PropertyTypes['string'];
|
|
293
288
|
}
|
|
294
|
-
|
|
289
|
+
// Pass the property definition to getStaticDefaults for types like 'mixed'
|
|
290
|
+
// that need access to the configuration (e.g., the types array)
|
|
291
|
+
const staticDefaults = propertyType.getStaticDefaults(property);
|
|
295
292
|
defaultValue = staticDefaults.defaultValue;
|
|
296
293
|
}
|
|
297
294
|
if (_.isFunction(defaultValue)) {
|
package/src/Writer/index.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/** @module Writer */
|
|
2
2
|
|
|
3
3
|
import JsonWriter from './JsonWriter.js';
|
|
4
|
-
import XmlWriter from './XmlWriter.js';
|
|
4
|
+
// import XmlWriter from './XmlWriter.js';
|
|
5
5
|
|
|
6
6
|
const WriterTypes = {
|
|
7
7
|
[JsonWriter.type]: JsonWriter,
|
|
8
|
-
[XmlWriter.type]: XmlWriter,
|
|
8
|
+
// [XmlWriter.type]: XmlWriter,
|
|
9
9
|
};
|
|
10
10
|
export default WriterTypes;
|