@onehat/data 1.18.8 → 1.18.10

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.
@@ -1139,7 +1139,7 @@ describe('Repository Base', function() {
1139
1139
  parent_id: null,
1140
1140
  depth: 0,
1141
1141
  hasChildren: true,
1142
- isChildrenLoaded: true,
1142
+ areChildrenLoaded: true,
1143
1143
  },
1144
1144
  {
1145
1145
  id: 2,
@@ -1147,21 +1147,21 @@ describe('Repository Base', function() {
1147
1147
  parent_id: 1,
1148
1148
  depth: 1,
1149
1149
  hasChildren: true,
1150
- isChildrenLoaded: true,
1150
+ areChildrenLoaded: true,
1151
1151
  },
1152
1152
  {
1153
1153
  id: 3,
1154
1154
  display: 'Child 2',
1155
1155
  parent_id: 1,
1156
1156
  depth: 1,
1157
- isChildrenLoaded: true,
1157
+ areChildrenLoaded: true,
1158
1158
  },
1159
1159
  {
1160
1160
  id: 4,
1161
1161
  display: 'Grandchild',
1162
1162
  parent_id: 2,
1163
1163
  depth: 2,
1164
- isChildrenLoaded: true,
1164
+ areChildrenLoaded: true,
1165
1165
  },
1166
1166
  ],
1167
1167
  creatRepository = async () => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onehat/data",
3
- "version": "1.18.8",
3
+ "version": "1.18.10",
4
4
  "description": "JS data modeling package with adapters for many storage mediums.",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
@@ -121,21 +121,24 @@ class Entity extends EventEmitter {
121
121
 
122
122
  /**
123
123
  * @member {TreeNode} parent - The parent TreeNode for this TreeNode
124
+ * @public
124
125
  * For trees only
125
126
  */
126
127
  this.parent = null;
127
128
 
128
129
  /**
129
130
  * @member {array} children - Contains any children of this TreeNode
131
+ * @public
130
132
  * For trees only
131
133
  */
132
134
  this.children = this._originalData.children && !_.isEmpty(this._originalData.children) ? this._originalData.children : [];
133
135
 
134
136
  /**
135
- * @member {boolean} isChildrenLoaded - Whether child TreeNodes have loaded for this TreeNode
137
+ * @member {boolean} areChildrenLoaded - Whether child TreeNodes have loaded for this TreeNode
138
+ * @public
136
139
  * For trees only
137
140
  */
138
- this.isChildrenLoaded = this._originalData.isChildrenLoaded || false;
141
+ this.areChildrenLoaded = this._originalData.areChildrenLoaded || false;
139
142
 
140
143
  /**
141
144
  * @member {boolean} isPersisted - Whether this object has been persisted in a storage medium
@@ -374,7 +377,7 @@ class Entity extends EventEmitter {
374
377
  * Assumes (and sets) isTempId === false.
375
378
  * @param {array} originalData - Raw data to load into entity.
376
379
  */
377
- loadOriginalData = (originalData, assembleTreeNodes = true) => {
380
+ loadOriginalData = (originalData) => {
378
381
  if (this.isDestroyed) {
379
382
  throw Error('this.loadOriginalData is no longer valid. Entity has been destroyed.');
380
383
  }
@@ -383,10 +386,6 @@ class Entity extends EventEmitter {
383
386
  this._originalData = originalData || {};
384
387
  this.reset();
385
388
  this.getIdProperty().isTempId = false;
386
-
387
- if (this.isTree && this.repository && assembleTreeNodes) {
388
- this.repository.assembleTreeNodes(); // rebuilds them all
389
- }
390
389
  }
391
390
 
392
391
  /**
@@ -1603,6 +1602,10 @@ class Entity extends EventEmitter {
1603
1602
  throw Error('this.getHasChildren is no longer valid. TreeNode has been destroyed.');
1604
1603
  }
1605
1604
 
1605
+ if (!_.isEmpty(this.children)) {
1606
+ return true; // In case the hasChildrenProperty is stale. i.e. That property came from server, and we now have children here
1607
+ }
1608
+
1606
1609
  return this.getHasChildrenProperty().getSubmitValue();
1607
1610
  }
1608
1611
 
@@ -1639,7 +1642,7 @@ class Entity extends EventEmitter {
1639
1642
  if (this.isDestroyed) {
1640
1643
  throw Error('this.getChildren is no longer valid. TreeNode has been destroyed.');
1641
1644
  }
1642
- if (!this.isChildrenLoaded) {
1645
+ if (!this.areChildrenLoaded) {
1643
1646
  await this.loadChildren();
1644
1647
  }
1645
1648
  return this.children;
@@ -1667,12 +1670,13 @@ class Entity extends EventEmitter {
1667
1670
  if (this.isDestroyed) {
1668
1671
  throw Error('this.loadChildren is no longer valid. TreeNode has been destroyed.');
1669
1672
  }
1670
- if (!this.repository?.loadChildren) {
1671
- throw Error('repository.loadChildren is not defined.');
1673
+ if (!this.repository?.loadChildNodes) {
1674
+ throw Error('repository.loadChildNodes is not defined.');
1672
1675
  }
1673
1676
 
1674
- this.children = await this.repository.loadChildren(this, depth); // populates the children with a reference to this in child.parent
1675
- this.isChildrenLoaded = true;
1677
+ const children = await this.repository.loadChildNodes(this, depth);
1678
+ this.areChildrenLoaded = true;
1679
+ return children;
1676
1680
  }
1677
1681
 
1678
1682
  /**
@@ -385,6 +385,9 @@ class AjaxRepository extends Repository {
385
385
  * @fires beforeLoad,changeData,load,error
386
386
  */
387
387
  load = async (params, callback = null) => {
388
+ if (this.isTree && this.getRootNodes) {
389
+ return this.getRootNodes();
390
+ }
388
391
  if (this.isDestroyed) {
389
392
  this.throwError('this.load is no longer valid. Repository has been destroyed.');
390
393
  return;
@@ -431,10 +434,6 @@ class AjaxRepository extends Repository {
431
434
  this._relayEntityEvents(entity);
432
435
  return entity;
433
436
  });
434
-
435
- if (this.isTree) {
436
- this.assembleTreeNodes();
437
- }
438
437
 
439
438
  // Set the total records that pass filter
440
439
  this.total = total;
@@ -442,6 +441,9 @@ class AjaxRepository extends Repository {
442
441
 
443
442
  this.markLoaded();
444
443
 
444
+ if (this.isTree) {
445
+ this.assembleTreeNodes();
446
+ }
445
447
  this.emit('changeData', this.entities);
446
448
  this.emit('load', this);
447
449
 
@@ -584,6 +586,10 @@ class AjaxRepository extends Repository {
584
586
  if (entity.isRemotePhantomMode) {
585
587
  entity.isRemotePhantom = true;
586
588
  }
589
+
590
+ if (this.isTree) {
591
+ this.assembleTreeNodes();
592
+ }
587
593
  });
588
594
  }
589
595
 
@@ -637,11 +643,12 @@ class AjaxRepository extends Repository {
637
643
  // Reload each entity with new data
638
644
  // TODO: Check this
639
645
  _.each(entities, (entity, ix) => {
640
- entity.loadOriginalData(root[ix], false); // false to not assembleTreeNodes
646
+ entity.loadOriginalData(root[ix]);
641
647
  if (entity.isRemotePhantomMode) {
642
648
  entity.isRemotePhantom = true;
643
649
  }
644
650
  });
651
+
645
652
  if (this.isTree) {
646
653
  this.assembleTreeNodes();
647
654
  }
@@ -690,6 +697,10 @@ class AjaxRepository extends Repository {
690
697
  if (entity.isRemotePhantomMode && entity.isRemotePhantom) {
691
698
  entity.isRemotePhantom = false;
692
699
  }
700
+
701
+ if (this.isTree) {
702
+ this.assembleTreeNodes();
703
+ }
693
704
  });
694
705
  }
695
706
 
@@ -743,11 +754,12 @@ class AjaxRepository extends Repository {
743
754
  // Reload each entity with new data
744
755
  // TODO: Check this
745
756
  _.each(entities, (entity, ix) => {
746
- entity.loadOriginalData(root[ix], false); // false to not assembleTreeNodes
757
+ entity.loadOriginalData(root[ix]);
747
758
  if (entity.isRemotePhantomMode && entity.isRemotePhantom) {
748
759
  entity.isRemotePhantom = false;
749
760
  }
750
761
  });
762
+
751
763
  if (this.isTree) {
752
764
  this.assembleTreeNodes();
753
765
  }
@@ -800,6 +812,10 @@ class AjaxRepository extends Repository {
800
812
  const id = entity.id;
801
813
  this.entities = _.filter(this.entities, (entity) => entity.id !== id);
802
814
  entity.destroy();
815
+
816
+ if (this.isTree) {
817
+ this.assembleTreeNodes();
818
+ }
803
819
  });
804
820
  }
805
821
 
@@ -856,6 +872,10 @@ class AjaxRepository extends Repository {
856
872
  }
857
873
  return !deleteIt;
858
874
  });
875
+
876
+ if (this.isTree) {
877
+ this.assembleTreeNodes();
878
+ }
859
879
  });
860
880
  }
861
881
 
@@ -874,6 +894,10 @@ class AjaxRepository extends Repository {
874
894
  return !match;
875
895
  });
876
896
 
897
+ if (this.isTree) {
898
+ this.assembleTreeNodes();
899
+ }
900
+
877
901
  return true;
878
902
  }
879
903
 
@@ -977,7 +1001,10 @@ class AjaxRepository extends Repository {
977
1001
 
978
1002
  // Do we need to reload?
979
1003
  if (!this.eventsPaused) {
980
- if (this.isRemotePhantomMode && (this._operations.add || this._operations.deletePhantom)) {
1004
+ if (this.isTree) {
1005
+ this.assembleTreeNodes();
1006
+ this.emit('changeData', this.entities);
1007
+ } else if (this.isRemotePhantomMode && (this._operations.add || this._operations.deletePhantom)) {
981
1008
  // Do nothing, as we don't want to immediately reload on add for a remote phantom mode record. It won't appear, and it will cause all kinds of trouble!
982
1009
  if (this._operations.deletePhantom) {
983
1010
  // sweep existing deleted records and remove them
@@ -106,7 +106,7 @@ class OneBuildRepository extends AjaxRepository {
106
106
  }
107
107
 
108
108
  const headers = _.merge({
109
- 'Content-Type': 'application/json',
109
+ // 'Content-Type': 'application/json', // Stops axios from using 'application/x-www-form-urlencoded'
110
110
  Accept: 'application/json',
111
111
  }, this.headers);
112
112
 
@@ -178,7 +178,11 @@ class OneBuildRepository extends AjaxRepository {
178
178
  });
179
179
 
180
180
  if (this.isLoaded && this.isAutoLoad && !this.eventsPaused) {
181
- return this.reload();
181
+ if (this.isTree) {
182
+ return this.getRootNodes(1);
183
+ } else {
184
+ return this.reload();
185
+ }
182
186
  }
183
187
  }
184
188
 
@@ -199,9 +203,15 @@ class OneBuildRepository extends AjaxRepository {
199
203
 
200
204
  if (!this.eventsPaused) {
201
205
  if (this.isLoaded && this.isAutoLoad) {
202
- return this.reload().then(() => {
203
- this.emit('changeSorters');
204
- });
206
+ if (this.isTree) {
207
+ return this.getRootNodes(1).then(() => {
208
+ this.emit('changeSorters');
209
+ });
210
+ } else {
211
+ return this.reload().then(() => {
212
+ this.emit('changeSorters');
213
+ });
214
+ }
205
215
  } else {
206
216
  this.emit('changeSorters');
207
217
  }
@@ -424,9 +434,9 @@ class OneBuildRepository extends AjaxRepository {
424
434
  // /_/ /_/ \___/\___/____/
425
435
 
426
436
  /**
427
- * Gets the root nodes of this tree.
437
+ * Loads the root nodes of this tree.
428
438
  */
429
- getRootNodes = async (getChildren = false, depth, getChildParams) => {
439
+ loadRootNodes = (depth) => {
430
440
  this.ensureTree();
431
441
  if (this.isDestroyed) {
432
442
  this.throwError('this.setRootNode is no longer valid. Repository has been destroyed.');
@@ -436,26 +446,12 @@ class OneBuildRepository extends AjaxRepository {
436
446
  this.throwError('Offline');
437
447
  return;
438
448
  }
449
+
439
450
  this.emit('beforeLoad'); // TODO: canceling beforeLoad will cancel the load operation
440
451
  this.markLoading();
441
452
 
442
-
443
- const data = {
444
- url: this.name + '/getRootNodes',
445
- data: qs.stringify({
446
- getChildren,
447
- depth,
448
- conditions: getChildParams ? getChildParams() : null
449
- }),
450
- method: 'POST',
451
- baseURL: this.api.baseURL,
452
- };
453
-
454
- if (this.debugMode) {
455
- console.log('getRootNodes', data);
456
- }
457
-
458
- return this.axios(data)
453
+ const data = _.merge({ depth }, this._baseParams, this._params);
454
+ return this._send('POST', this.name + '/getNodes', data)
459
455
  .then((result) => {
460
456
  if (this.debugMode) {
461
457
  console.log('Response for getRootNodes', result);
@@ -474,6 +470,11 @@ class OneBuildRepository extends AjaxRepository {
474
470
  message
475
471
  } = this._processServerResponse(result);
476
472
 
473
+ if (!success) {
474
+ this.throwError(message);
475
+ return;
476
+ }
477
+
477
478
  this._destroyEntities();
478
479
 
479
480
  // Set the current entities
@@ -491,10 +492,10 @@ class OneBuildRepository extends AjaxRepository {
491
492
 
492
493
  this.areRootNodesLoaded = true;
493
494
 
494
- this.markLoaded();
495
-
496
- this.emit('changeData', this.entities);
495
+
496
+ // Don't emit events for root nodes...
497
497
  this.emit('load', this);
498
+ // this.emit('changeData', this.entities);
498
499
 
499
500
  return this.getBy((entity) => {
500
501
  return entity.isRoot;
@@ -506,75 +507,125 @@ class OneBuildRepository extends AjaxRepository {
506
507
  }
507
508
 
508
509
  /**
509
- * Searches all nodes for the supplied text.
510
- * This basically takes the search query and returns whatever the server sends
510
+ * Loads (or reloads) the children of the supplied treeNode
511
511
  */
512
- searchTree = async (q) => {
512
+ loadChildNodes = (treeNode, depth = 1) => {
513
513
  this.ensureTree();
514
514
  if (this.isDestroyed) {
515
- this.throwError('this.searchTree is no longer valid. Repository has been destroyed.');
515
+ this.throwError('this.loadChildNodes is no longer valid. Repository has been destroyed.');
516
516
  return;
517
517
  }
518
518
  if (!this.isOnline) {
519
519
  this.throwError('Offline');
520
520
  return;
521
521
  }
522
-
523
- const data = {
524
- url: this.name + '/searchTree',
525
- data: qs.stringify({
526
- q,
527
- }),
528
- method: 'POST',
529
- baseURL: this.api.baseURL,
530
- };
531
522
 
532
- if (this.debugMode) {
533
- console.log('searchTree', data);
523
+ // If children already exist, remove them from the repository
524
+ // This way, we can reload just a portion of the tree
525
+ if (!_.isEmpty(treeNode.children)) {
526
+ const children = treeNode.children;
527
+ treeNode.children = [];
528
+
529
+ _.each(children, (child) => {
530
+ this.removeNode(child);
531
+ });
534
532
  }
533
+
534
+ this.markLoading();
535
535
 
536
- return this.axios(data)
536
+ const data = _.merge({ depth, parentId: treeNode.id, }, this._baseParams, this._params);
537
+ return this._send('POST', this.name + '/getNodes', data)
537
538
  .then((result) => {
538
539
  if (this.debugMode) {
539
- console.log('searchTree response', result);
540
+ console.log('Response for loadChildNodes', result);
540
541
  }
541
542
 
542
- const response = result.data;
543
- if (!response.success) {
544
- this.throwError(response.data);
543
+ if (this.isDestroyed) {
544
+ // If this repository gets destroyed before it has a chance
545
+ // to process the Ajax request, just ignore the response.
545
546
  return;
546
547
  }
547
548
 
548
- return response.data;
549
+ const {
550
+ root,
551
+ success,
552
+ total,
553
+ message
554
+ } = this._processServerResponse(result);
555
+
556
+ if (!success) {
557
+ this.throwError(message);
558
+ return;
559
+ }
560
+
561
+ // Set the current entities
562
+ const children = _.map(root, (data) => {
563
+ const entity = Repository._createEntity(this.schema, data, this, true);
564
+ this._relayEntityEvents(entity);
565
+ return entity;
566
+ });
567
+
568
+ this.entities = this.entities.concat(children);
569
+
570
+ this.assembleTreeNodes();
571
+
572
+ this._setPaginationVars();
573
+
574
+ // this.emit('changeData', this.entities);
575
+ this.emit('load', this);
576
+
577
+ return children;
578
+ })
579
+ .finally(() => {
580
+ this.markLoading(false);
549
581
  });
550
582
  }
583
+
551
584
  /**
552
- * Loads the children of the supplied treeNode
585
+ * Searches all nodes for the supplied text.
586
+ * This basically takes the search query and returns whatever the server sends
553
587
  */
554
- loadChildren = async (treeNode, depth = 1) => {
588
+ searchNodes = (q) => {
555
589
  this.ensureTree();
556
590
  if (this.isDestroyed) {
557
- this.throwError('this.setRootNode is no longer valid. Repository has been destroyed.');
591
+ this.throwError('this.searchNodes is no longer valid. Repository has been destroyed.');
558
592
  return;
559
593
  }
560
-
561
-
562
- // If children already exist, remove them from the repository
563
- // This way, we can reload just a portion of the tree
564
- if (!_.isEmpty(treeNode.children)) {
565
- const children = treeNode.children;
566
- treeNode.children = [];
567
-
568
- _.each(children, (child) => {
569
- this.removeNode(child);
570
- });
594
+ if (!this.isOnline) {
595
+ this.throwError('Offline');
596
+ return;
571
597
  }
572
598
 
599
+ const data = _.merge({ q, }, this._baseParams, this._params);
600
+ return this._send('POST', this.name + '/searchNodes', data)
601
+ .then((result) => {
602
+ if (this.debugMode) {
603
+ console.log('Response for searchNodes', result);
604
+ }
573
605
 
574
- // TODO: load children here
606
+ if (this.isDestroyed) {
607
+ // If this repository gets destroyed before it has a chance
608
+ // to process the Ajax request, just ignore the response.
609
+ return;
610
+ }
575
611
 
612
+ const {
613
+ root,
614
+ success,
615
+ total,
616
+ message
617
+ } = this._processServerResponse(result);
576
618
 
577
-
619
+ if (!success) {
620
+ this.throwError(message);
621
+ return;
622
+ }
623
+
624
+ return root;
625
+ })
626
+ .finally(() => {
627
+ this.markLoading(false);
628
+ });
578
629
  }
579
630
 
580
631
  /**
@@ -282,16 +282,16 @@ export default class Repository extends EventEmitter {
282
282
 
283
283
  // Assign event handlers
284
284
  this.on('entity_change', async (entity) => { // Entity changed its value
285
- if (this.isAutoSave) {
285
+ if (this.isAutoSave && !this.isRemotePhantomMode) {
286
286
  return await this.save(entity);
287
287
  }
288
288
  });
289
289
 
290
290
  // Auto load & sort
291
- if (this.isAutoLoad) {
291
+ if (this.isAutoLoad && !this.isTree) {
292
292
  await this.load();
293
293
  }
294
- if (!this.isSorted && this.isAutoSort && !this.isRemoteSort) { // load may have sorted, in which case this will be skipped.
294
+ if (!this.isSorted && this.isAutoSort && !this.isRemoteSort && !this.isTree) { // load may have sorted, in which case this will be skipped.
295
295
  await this.sort();
296
296
  }
297
297
 
@@ -999,7 +999,17 @@ export default class Repository extends EventEmitter {
999
999
  entity = Repository._createEntity(this.schema, data, this, isPersisted, isDelayedSave, this.isRemotePhantomMode);
1000
1000
  }
1001
1001
  this._relayEntityEvents(entity);
1002
- this.entities.unshift(entity); // Add to *beginning* of entities array, so the phantom record will appear at the beginning of the current page
1002
+ if (this.isTree && data.parentId) {
1003
+ // Trees need new node to be added as first child of parent
1004
+ const ix = this.getIxById(data.parentId) +1;
1005
+ this.entities = [
1006
+ ...this.entities.slice(0, ix),
1007
+ entity,
1008
+ ...this.entities.slice(ix)
1009
+ ];
1010
+ } else {
1011
+ this.entities.unshift(entity); // Add to *beginning* of entities array, so the phantom record will appear at the beginning of the current page
1012
+ }
1003
1013
 
1004
1014
  // Create id if needed
1005
1015
  if (!this.isRemotePhantomMode && entity.isPhantom) {
@@ -1958,6 +1968,7 @@ export default class Repository extends EventEmitter {
1958
1968
  return entities;
1959
1969
  }
1960
1970
 
1971
+
1961
1972
  /**
1962
1973
  * Populates the TreeNodes with .parent and .children references
1963
1974
  */
@@ -1,7 +1,7 @@
1
1
  import moment from 'moment';
2
2
  import momentAlt from 'relative-time-parser'; // Notice this version of moment is imported from 'relative-time-parser', and may be out of sync with our general 'moment' package
3
3
  import accounting from 'accounting-js';
4
- import * as chrono from 'chrono-node'; // Doesn't yet work in React Native ("SyntaxError: Invalid RegExp: Quantifier has nothing to repeat, js engine: hermes") Github ticket: https://github.com/facebook/hermes/blob/main/doc/RegExp.md
4
+ // import * as chrono from 'chrono-node'; // Doesn't yet work in React Native ("SyntaxError: Invalid RegExp: Quantifier has nothing to repeat, js engine: hermes") Github ticket: https://github.com/facebook/hermes/blob/main/doc/RegExp.md
5
5
  import _ from 'lodash';
6
6
 
7
7
  class Parsers {