pict-section-recordset 1.0.23 → 1.0.26

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.
Files changed (100) hide show
  1. package/eslint.config.mjs +10 -0
  2. package/example_applications/simple_entity/Simple-RecordSet-Application.js +170 -110
  3. package/package.json +11 -11
  4. package/source/application/Pict-Application-RecordSet.js +8 -0
  5. package/source/providers/RecordSet-DynamicRecordsetSolver.js +1 -0
  6. package/source/providers/RecordSet-Link-Manager.js +2 -2
  7. package/source/providers/RecordSet-RecordProvider-Base.js +9 -2
  8. package/source/providers/RecordSet-RecordProvider-MeadowEndpoints.js +118 -16
  9. package/source/services/RecordsSet-MetaController.js +41 -19
  10. package/source/templates/Pict-Template-FilterInstanceViews.js +222 -0
  11. package/source/templates/Pict-Template-FilterView.js +4 -2
  12. package/source/views/RecordSet-Filters.js +449 -0
  13. package/source/views/dashboard/RecordSet-Dashboard.js +66 -9
  14. package/source/views/filters/RecordSet-Filter-Base-Range.js +38 -0
  15. package/source/views/filters/RecordSet-Filter-Base.js +88 -0
  16. package/source/views/filters/RecordSet-Filter-DateMatch.js +56 -0
  17. package/source/views/filters/RecordSet-Filter-DateRange.js +75 -0
  18. package/source/views/filters/RecordSet-Filter-ExternalJoinDateMatch.js +55 -0
  19. package/source/views/filters/RecordSet-Filter-ExternalJoinDateRange.js +57 -0
  20. package/source/views/filters/RecordSet-Filter-ExternalJoinNumericMatch.js +54 -0
  21. package/source/views/filters/RecordSet-Filter-ExternalJoinNumericRange.js +57 -0
  22. package/source/views/filters/RecordSet-Filter-ExternalJoinStringMatch.js +45 -0
  23. package/source/views/filters/RecordSet-Filter-ExternalJoinStringRange.js +46 -0
  24. package/source/views/filters/RecordSet-Filter-InternalJoinDateMatch.js +55 -0
  25. package/source/views/filters/RecordSet-Filter-InternalJoinDateRange.js +57 -0
  26. package/source/views/filters/RecordSet-Filter-InternalJoinNumericMatch.js +55 -0
  27. package/source/views/filters/RecordSet-Filter-InternalJoinNumericRange.js +57 -0
  28. package/source/views/filters/RecordSet-Filter-InternalJoinStringMatch.js +45 -0
  29. package/source/views/filters/RecordSet-Filter-InternalJoinStringRange.js +46 -0
  30. package/source/views/filters/RecordSet-Filter-NumericMatch.js +55 -0
  31. package/source/views/filters/RecordSet-Filter-NumericRange.js +57 -0
  32. package/source/views/filters/RecordSet-Filter-StringMatch.js +45 -0
  33. package/source/views/filters/RecordSet-Filter-StringRange.js +46 -0
  34. package/source/views/filters/index.js +27 -0
  35. package/source/views/list/RecordSet-List.js +44 -6
  36. package/test/PictSectionRecordSet-RecordProvider-Meadow_tests.js +11 -0
  37. package/types/application/Pict-Application-RecordSet.d.ts.map +1 -1
  38. package/types/providers/RecordSet-DynamicRecordsetSolver.d.ts +10 -8
  39. package/types/providers/RecordSet-DynamicRecordsetSolver.d.ts.map +1 -1
  40. package/types/providers/RecordSet-Link-Manager.d.ts +3 -3
  41. package/types/providers/RecordSet-RecordProvider-Base.d.ts +9 -0
  42. package/types/providers/RecordSet-RecordProvider-Base.d.ts.map +1 -1
  43. package/types/providers/RecordSet-RecordProvider-MeadowEndpoints.d.ts +30 -4
  44. package/types/providers/RecordSet-RecordProvider-MeadowEndpoints.d.ts.map +1 -1
  45. package/types/services/RecordsSet-MetaController.d.ts +2 -2
  46. package/types/services/RecordsSet-MetaController.d.ts.map +1 -1
  47. package/types/templates/Pict-Template-FilterInstanceViews.d.ts +19 -0
  48. package/types/templates/Pict-Template-FilterInstanceViews.d.ts.map +1 -0
  49. package/types/templates/Pict-Template-FilterView.d.ts.map +1 -1
  50. package/types/views/RecordSet-Filters.d.ts +84 -0
  51. package/types/views/RecordSet-Filters.d.ts.map +1 -0
  52. package/types/views/dashboard/RecordSet-Dashboard.d.ts +4 -2
  53. package/types/views/dashboard/RecordSet-Dashboard.d.ts.map +1 -1
  54. package/types/views/filters/RecordSet-Filter-Base-Range.d.ts +5 -0
  55. package/types/views/filters/RecordSet-Filter-Base-Range.d.ts.map +1 -0
  56. package/types/views/filters/RecordSet-Filter-Base.d.ts +29 -0
  57. package/types/views/filters/RecordSet-Filter-Base.d.ts.map +1 -0
  58. package/types/views/filters/RecordSet-Filter-DateMatch.d.ts +9 -0
  59. package/types/views/filters/RecordSet-Filter-DateMatch.d.ts.map +1 -0
  60. package/types/views/filters/RecordSet-Filter-DateRange.d.ts +9 -0
  61. package/types/views/filters/RecordSet-Filter-DateRange.d.ts.map +1 -0
  62. package/types/views/filters/RecordSet-Filter-ExternalJoinDateMatch.d.ts +9 -0
  63. package/types/views/filters/RecordSet-Filter-ExternalJoinDateMatch.d.ts.map +1 -0
  64. package/types/views/filters/RecordSet-Filter-ExternalJoinDateRange.d.ts +9 -0
  65. package/types/views/filters/RecordSet-Filter-ExternalJoinDateRange.d.ts.map +1 -0
  66. package/types/views/filters/RecordSet-Filter-ExternalJoinNumericMatch.d.ts +9 -0
  67. package/types/views/filters/RecordSet-Filter-ExternalJoinNumericMatch.d.ts.map +1 -0
  68. package/types/views/filters/RecordSet-Filter-ExternalJoinNumericRange.d.ts +9 -0
  69. package/types/views/filters/RecordSet-Filter-ExternalJoinNumericRange.d.ts.map +1 -0
  70. package/types/views/filters/RecordSet-Filter-ExternalJoinStringMatch.d.ts +9 -0
  71. package/types/views/filters/RecordSet-Filter-ExternalJoinStringMatch.d.ts.map +1 -0
  72. package/types/views/filters/RecordSet-Filter-ExternalJoinStringRange.d.ts +9 -0
  73. package/types/views/filters/RecordSet-Filter-ExternalJoinStringRange.d.ts.map +1 -0
  74. package/types/views/filters/RecordSet-Filter-InternalJoinDateMatch.d.ts +9 -0
  75. package/types/views/filters/RecordSet-Filter-InternalJoinDateMatch.d.ts.map +1 -0
  76. package/types/views/filters/RecordSet-Filter-InternalJoinDateRange.d.ts +9 -0
  77. package/types/views/filters/RecordSet-Filter-InternalJoinDateRange.d.ts.map +1 -0
  78. package/types/views/filters/RecordSet-Filter-InternalJoinNumericMatch.d.ts +9 -0
  79. package/types/views/filters/RecordSet-Filter-InternalJoinNumericMatch.d.ts.map +1 -0
  80. package/types/views/filters/RecordSet-Filter-InternalJoinNumericRange.d.ts +9 -0
  81. package/types/views/filters/RecordSet-Filter-InternalJoinNumericRange.d.ts.map +1 -0
  82. package/types/views/filters/RecordSet-Filter-InternalJoinStringMatch.d.ts +9 -0
  83. package/types/views/filters/RecordSet-Filter-InternalJoinStringMatch.d.ts.map +1 -0
  84. package/types/views/filters/RecordSet-Filter-InternalJoinStringRange.d.ts +9 -0
  85. package/types/views/filters/RecordSet-Filter-InternalJoinStringRange.d.ts.map +1 -0
  86. package/types/views/filters/RecordSet-Filter-NumericMatch.d.ts +9 -0
  87. package/types/views/filters/RecordSet-Filter-NumericMatch.d.ts.map +1 -0
  88. package/types/views/filters/RecordSet-Filter-NumericRange.d.ts +9 -0
  89. package/types/views/filters/RecordSet-Filter-NumericRange.d.ts.map +1 -0
  90. package/types/views/filters/RecordSet-Filter-StringMatch.d.ts +9 -0
  91. package/types/views/filters/RecordSet-Filter-StringMatch.d.ts.map +1 -0
  92. package/types/views/filters/RecordSet-Filter-StringRange.d.ts +9 -0
  93. package/types/views/filters/RecordSet-Filter-StringRange.d.ts.map +1 -0
  94. package/types/views/filters/index.d.ts +20 -0
  95. package/types/views/filters/index.d.ts.map +1 -0
  96. package/types/views/list/RecordSet-List.d.ts +12 -2
  97. package/types/views/list/RecordSet-List.d.ts.map +1 -1
  98. package/source/views/RecordSet-Filter.js +0 -159
  99. package/types/views/RecordSet-Filter.d.ts +0 -39
  100. package/types/views/RecordSet-Filter.d.ts.map +0 -1
@@ -14,7 +14,7 @@ class MeadowEndpointsRecordSetProvider extends libRecordSetProviderBase
14
14
  {
15
15
  /**
16
16
  * Creates an instance of RecordSetProvider.
17
- * @param {import('fable')} pFable - The Fable object.
17
+ * @param {import('pict')} pFable - The Fable object.
18
18
  * @param {Record<string, any>} [pOptions] - Custom options for the provider.
19
19
  * @param {string} [pServiceHash] - The service hash.
20
20
  */
@@ -24,8 +24,6 @@ class MeadowEndpointsRecordSetProvider extends libRecordSetProviderBase
24
24
 
25
25
  /** @type {Record<string, any>} */
26
26
  this.options;
27
- /** @type {import('fable')} */
28
- this.fable;
29
27
  /** @type {import('pict') & {
30
28
  * log: any,
31
29
  * services:
@@ -37,6 +35,7 @@ class MeadowEndpointsRecordSetProvider extends libRecordSetProviderBase
37
35
  * PictSectionRecordSet: InstanceType<import('../Pict-Section-RecordSet.js')>
38
36
  * }} */
39
37
  this.pict;
38
+ this.fable = this.pict;
40
39
  /** @type {string} */
41
40
  this.Hash;
42
41
  /** @type {string} */
@@ -44,6 +43,10 @@ class MeadowEndpointsRecordSetProvider extends libRecordSetProviderBase
44
43
  //TODO: make this typedef better
45
44
  /** @type {Record<string, any>} */
46
45
  this._Schema = { };
46
+ /** @type {Record<string, Record<string, any>>} */
47
+ this._Experiences = { };
48
+ /** @type {Record<string, Record<string, any>>} */
49
+ this._FiltersByField = { };
47
50
  }
48
51
 
49
52
  /** @return {import('pict/types/source/Pict-Meadow-EntityProvider.js')} */
@@ -59,11 +62,6 @@ class MeadowEndpointsRecordSetProvider extends libRecordSetProviderBase
59
62
  return this._EntityProvider;
60
63
  }
61
64
 
62
- /**
63
- * @typedef {(error?: Error, result?: T) => void} RecordSetCallback
64
- * @template T = Record<string, any>
65
- */
66
-
67
65
  /**
68
66
  * Get a record by its ID or GUID.
69
67
  *
@@ -129,6 +127,25 @@ class MeadowEndpointsRecordSetProvider extends libRecordSetProviderBase
129
127
  });
130
128
  }
131
129
 
130
+ _prepareFilterState(pEntity, pOptions, pFilterExperienceResultAddress = 'Records')
131
+ {
132
+ const tmpClauses = [].concat(this.pict.Bundle._ActiveFilterState?.[pOptions.Entity || this.options.Entity]?.FilterClauses || []);
133
+ const tmpExperience = Object.assign({}, this.pict.Bundle._ActiveFilterState?.[pOptions.Entity || this.options.Entity]?.Experience || {});
134
+ if (!tmpExperience.ResultDestinationAddress)
135
+ {
136
+ tmpExperience.ResultDestinationAddress = `Bundle._ActiveFilterState[\`${this.options.RecordSet}\`].${pFilterExperienceResultAddress}`;
137
+ }
138
+ if (!tmpExperience.Entity)
139
+ {
140
+ tmpExperience.Entity = pEntity;
141
+ }
142
+ if (pOptions.FilterString && typeof pOptions.FilterString === 'string' && pOptions.FilterString.trim().length > 0)
143
+ {
144
+ tmpClauses.push({ Type: 'RawFilter', Value: pOptions.FilterString });
145
+ }
146
+ return [ tmpClauses, tmpExperience ];
147
+ }
148
+
132
149
  /**
133
150
  * Read records from the provider.
134
151
  *
@@ -148,15 +165,16 @@ class MeadowEndpointsRecordSetProvider extends libRecordSetProviderBase
148
165
  }
149
166
  return new Promise((resolve, reject) =>
150
167
  {
151
- // using a space here, otherwise you get a `//` in the URL which breaks some stuff
152
- this.entityProvider.getEntitySetPage(tmpEntity, pOptions.FilterString ? pOptions.FilterString : ' ', pOptions.Offset || 0, pOptions.PageSize || 250, (pError, pResult) =>
168
+ const [ tmpClauses, tmpExperience ] = this._prepareFilterState(tmpEntity, pOptions);
169
+ this.pict.providers.FilterManager.loadRecordPageByFilter(tmpClauses, tmpExperience, pOptions.Offset || 0, pOptions.PageSize || 250, (pError) =>
153
170
  {
154
171
  if (pError)
155
172
  {
156
173
  return reject(pError);
157
174
  }
158
- resolve({ Records: pResult, Facets: { } });
175
+ resolve({ Records: this.pict.resolveStateFromAddress(tmpExperience.ResultDestinationAddress), Facets: { } });
159
176
  });
177
+ // using a space here, otherwise you get a `//` in the URL which breaks some stuff
160
178
  });
161
179
  }
162
180
 
@@ -192,13 +210,14 @@ class MeadowEndpointsRecordSetProvider extends libRecordSetProviderBase
192
210
  //TODO: lite support / other variants?
193
211
  return new Promise((resolve, reject) =>
194
212
  {
195
- this.entityProvider.getEntitySetRecordCount(tmpEntity, pOptions.FilterString, (pError, pCount) =>
213
+ const [ tmpClauses, tmpExperience ] = this._prepareFilterState(tmpEntity, pOptions, 'Count');
214
+ this.pict.providers.FilterManager.countRecordsByFilter(tmpClauses, tmpExperience, (pError) =>
196
215
  {
197
216
  if (pError)
198
217
  {
199
218
  return reject(pError);
200
219
  }
201
- resolve({ Count: pCount });
220
+ resolve({ Count: this.pict.resolveStateFromAddress(tmpExperience.ResultDestinationAddress) });
202
221
  });
203
222
  });
204
223
  }
@@ -331,6 +350,89 @@ class MeadowEndpointsRecordSetProvider extends libRecordSetProviderBase
331
350
  * @param {(error?: Error) => void} fCallback - The callback function.
332
351
  */
333
352
  onInitializeAsync(fCallback)
353
+ {
354
+ super.onInitializeAsync((pError) =>
355
+ {
356
+ if (this.options.FilterExperiences && typeof this.options.FilterExperiences === 'object' && !Array.isArray(this.options.FilterExperiences))
357
+ {
358
+ this.pict.Bundle._ActiveFilterState = this.pict.Bundle._ActiveFilterState || {};
359
+ this.pict.Bundle._ActiveFilterState[this.options.RecordSet] = this.pict.Bundle._ActiveFilterState[this.options.RecordSet] || {};
360
+ const tmpEntityFilterState = this.pict.Bundle._ActiveFilterState[this.options.RecordSet];
361
+ tmpEntityFilterState.Experience = tmpEntityFilterState.Experience || {};
362
+ for (const tmpKey of Object.keys(this.options.FilterExperiences))
363
+ {
364
+ const tmpExperience = this.options.FilterExperiences[tmpKey];
365
+ if (!tmpExperience.FilterCriteriaHash && !Array.isArray(tmpExperience.FilterClauses))
366
+ {
367
+ this.log.warn(`Filter experience ${tmpKey} has invalid Criteria`, { Experience: tmpExperience });
368
+ continue;
369
+ }
370
+ if (tmpExperience.FilterCriteriaHash)
371
+ {
372
+ // load from hash
373
+ const tmpClauses = this.pict.providers.FilterManager.getFilterCriteria(tmpExperience.FilterCriteriaHash);
374
+ if (!tmpClauses)
375
+ {
376
+ this.pict.log.warn(`Filter experience ${tmpKey} filter criteria hash ${tmpExperience.FilterCriteriaHash} not found`, { Experience: tmpExperience });
377
+ continue;
378
+ }
379
+ //TODO: handle Ordinal / generate if missing
380
+ this._Experiences[tmpKey] = JSON.parse(JSON.stringify(tmpExperience));
381
+ this._Experiences[tmpKey].FilterClauses = JSON.parse(JSON.stringify(tmpClauses));
382
+ for (const tmpClause of this._Experiences[tmpKey].FilterClauses)
383
+ {
384
+ tmpClause.FilterCriteriaHash = tmpExperience.FilterCriteriaHash;
385
+ if (!tmpClause.FilterByColumn && !tmpClause.CoreConnectionColumn && !tmpClause.JoinInternalConnectionColumn)
386
+ {
387
+ this.log.warn(`Filter experience ${tmpKey} clause does not have filter by column configured`, { Clause: tmpClause });
388
+ continue;
389
+ }
390
+ if (tmpClause.FilterDefinitionHash)
391
+ {
392
+ const tmpFilter = this.pict.providers.FilterManager.getFilter(tmpClause.FilterDefinitionHash);
393
+ if (!tmpFilter)
394
+ {
395
+ this.pict.log.warn(`Filter experience ${tmpKey} filter criteria hash ${tmpClause.FilterDefinitionHash} not found`, { Experience: tmpExperience });
396
+ continue;
397
+ }
398
+ Object.assign(tmpClause, tmpFilter); //TODO: is there a risk of leakage here?
399
+ if (tmpClause.FilterByColumn && typeof tmpClause.Type === 'string')
400
+ {
401
+ if (tmpClause.Type.startsWith('InternalJoin'))
402
+ {
403
+ tmpClause.JoinInternalConnectionColumn = tmpClause.FilterByColumn;
404
+ }
405
+ else if (tmpClause.Type.startsWith('ExternalJoin'))
406
+ {
407
+ tmpClause.CoreConnectionColumn = tmpClause.FilterByColumn;
408
+ }
409
+ }
410
+ }
411
+ }
412
+ if (tmpExperience.Default && !tmpEntityFilterState.FilterClauses)
413
+ {
414
+ tmpEntityFilterState.FilterClauses = JSON.parse(JSON.stringify(this._Experiences[tmpKey].FilterClauses));
415
+ }
416
+ continue;
417
+ }
418
+ this._Experiences[tmpKey] = JSON.parse(JSON.stringify(tmpExperience));
419
+ if (tmpExperience.Default && !tmpEntityFilterState.FilterClauses)
420
+ {
421
+ tmpEntityFilterState.FilterClauses = JSON.parse(JSON.stringify(tmpExperience.FilterClauses));
422
+ }
423
+ }
424
+ }
425
+ this.initializeEntitySchema(() =>
426
+ {
427
+ return fCallback(pError);
428
+ });
429
+ });
430
+ }
431
+
432
+ /**
433
+ * @param {(error?: Error) => void} fCallback - The callback function.
434
+ */
435
+ initializeEntitySchema(fCallback)
334
436
  {
335
437
  this.fable.log.info('Initializing RecordSetProvider-MeadowEndpoints');
336
438
  const checkSession = this.pict.services.PictSectionRecordSet ? this.pict.services.PictSectionRecordSet.checkSession.bind(this.pict.services.PictSectionRecordSet) : async () => true;
@@ -361,7 +463,7 @@ class MeadowEndpointsRecordSetProvider extends libRecordSetProviderBase
361
463
  {
362
464
  if (!this._Schema)
363
465
  {
364
- await new Promise((resolve, reject) => this.onInitializeAsync((pError) =>
466
+ await new Promise((resolve, reject) => this.initializeEntitySchema((pError) =>
365
467
  {
366
468
  if (pError)
367
469
  {
@@ -390,8 +492,8 @@ class MeadowEndpointsRecordSetProvider extends libRecordSetProviderBase
390
492
  this.pict.log.error('RecordDecorationConfiguration is not an array', { RecordDecorationConfiguration: this.options.RecordDecorationConfiguration });
391
493
  return;
392
494
  }
393
- this.pict.AppData[this.Hash] = { CoreEntityRecordSubset: pRecords };
394
- const config = [{ Type: 'SetStateAddress', StateAddress: `AppData[${this.Hash}]` }].concat(this.options.RecordDecorationConfiguration);
495
+ this.pict.Bundle[this.Hash] = { CoreEntityRecordSubset: pRecords };
496
+ const config = [{ Type: 'SetStateAddress', StateAddress: `Bundle[${this.Hash}]` }].concat(this.options.RecordDecorationConfiguration);
395
497
 
396
498
  try
397
499
  {
@@ -1,18 +1,18 @@
1
1
  const libFableServiceProviderBase = require('fable-serviceproviderbase');
2
2
 
3
- const viewDefinitionRecordSetErrorNotFound = require('../views/error/RecordSet-Error-NotFound.json');
4
- const viewRecordSetList = require('../views/list/RecordSet-List.js');
5
- const viewRecordSetEdit = require('../views/edit/RecordSet-Edit.js');
6
- const viewRecordSetRead = require('../views/read/RecordSet-Read.js');
7
- const viewRecordSetDashboard = require('../views/dashboard/RecordSet-Dashboard.js');
3
+ const ViewDefinitionRecordSetErrorNotFound = require('../views/error/RecordSet-Error-NotFound.json');
4
+ const ViewRecordSetList = require('../views/list/RecordSet-List.js');
5
+ const ViewRecordSetEdit = require('../views/edit/RecordSet-Edit.js');
6
+ const ViewRecordSetRead = require('../views/read/RecordSet-Read.js');
7
+ const ViewRecordSetDashboard = require('../views/dashboard/RecordSet-Dashboard.js');
8
8
 
9
9
  //_Pict.addProvider('BooksProvider', { Entity: 'Book', URLPrefix: 'http://www.datadebase.com:8086/1.0/' }, require('../source/providers/RecordSet-RecordProvider-MeadowEndpoints.js'));
10
- const providerBase = require('../providers/RecordSet-RecordProvider-Base.js');
11
- const providerMeadowEndpoints = require('../providers/RecordSet-RecordProvider-MeadowEndpoints.js');
10
+ const ProviderBase = require('../providers/RecordSet-RecordProvider-Base.js');
11
+ const ProviderMeadowEndpoints = require('../providers/RecordSet-RecordProvider-MeadowEndpoints.js');
12
12
 
13
- const providerLinkManager = require('../providers/RecordSet-Link-Manager.js');
13
+ const ProviderLinkManager = require('../providers/RecordSet-Link-Manager.js');
14
14
 
15
- const providerRouter = require('../providers/RecordSet-Router.js');
15
+ const ProviderRouter = require('../providers/RecordSet-Router.js');
16
16
 
17
17
  /** @type {Record<string, any>} */
18
18
  const _DEFAULT_CONFIGURATION =
@@ -20,6 +20,8 @@ const _DEFAULT_CONFIGURATION =
20
20
  DefaultMeadowURLPrefix: '/1.0/'
21
21
  };
22
22
 
23
+ const libFormsTemplateProvider = require('pict-section-form').PictFormTemplateProvider;
24
+
23
25
  class RecordSetMetacontroller extends libFableServiceProviderBase
24
26
  {
25
27
  constructor(pFable, pOptions, pServiceHash)
@@ -61,6 +63,9 @@ class RecordSetMetacontroller extends libFableServiceProviderBase
61
63
  this.manifests = { Default: this.pict.manifest };
62
64
 
63
65
  this.has_initialized = false;
66
+
67
+ //FIXME: this duplicates a behavior in pict-section-form - figure out how we want this coupled dependency to work.
68
+ this.fable.addProviderSingleton('PictFormSectionDefaultTemplateProvider', libFormsTemplateProvider.default_configuration, libFormsTemplateProvider);
64
69
  }
65
70
 
66
71
  /*
@@ -163,7 +168,7 @@ class RecordSetMetacontroller extends libFableServiceProviderBase
163
168
  {
164
169
  providerConfiguration.URLPrefix = '/1.0/';
165
170
  }
166
- tmpProvider = this.recordSetProviders[pRecordSetConfiguration.RecordSet] = this.fable.addProvider(providerConfiguration.Hash, providerConfiguration, providerMeadowEndpoints);
171
+ tmpProvider = this.recordSetProviders[pRecordSetConfiguration.RecordSet] = this.fable.addProvider(providerConfiguration.Hash, providerConfiguration, ProviderMeadowEndpoints);
167
172
  break;
168
173
  default:
169
174
  case 'Custom':
@@ -177,12 +182,12 @@ class RecordSetMetacontroller extends libFableServiceProviderBase
177
182
  }
178
183
  else
179
184
  {
180
- tmpProvider = this.recordSetProviders[providerConfiguration.RecordSet] = this.fable.addProvider(providerConfiguration.Hash, providerConfiguration, providerBase);
185
+ tmpProvider = this.recordSetProviders[providerConfiguration.RecordSet] = this.fable.addProvider(providerConfiguration.Hash, providerConfiguration, ProviderBase);
181
186
  }
182
187
  }
183
188
  else
184
189
  {
185
- tmpProvider = this.recordSetProviders[providerConfiguration.RecordSet] = this.fable.addProvider(providerConfiguration.Hash, providerConfiguration, providerBase);
190
+ tmpProvider = this.recordSetProviders[providerConfiguration.RecordSet] = this.fable.addProvider(providerConfiguration.Hash, providerConfiguration, ProviderBase);
186
191
  }
187
192
  break;
188
193
  }
@@ -302,15 +307,16 @@ class RecordSetMetacontroller extends libFableServiceProviderBase
302
307
  return this;
303
308
  }
304
309
 
305
- this.fable.addProvider('RecordSetLinkManager', {}, providerLinkManager);
310
+ this.fable.addProvider('RecordSetLinkManager', {}, ProviderLinkManager);
306
311
 
307
312
  // Add the subviews internally and externally
308
313
  this.pict.addTemplate(require('../templates/Pict-Template-FilterView.js'));
309
- this.childViews.errorNotFound = this.fable.addView('RSP-RecordSet-Error-NotFound', viewDefinitionRecordSetErrorNotFound);
310
- this.childViews.list = this.fable.addView('RSP-RecordSet-List', this.options, viewRecordSetList);
311
- this.childViews.edit = this.fable.addView('RSP-RecordSet-Edit', this.options, viewRecordSetEdit);
312
- this.childViews.read = this.fable.addView('RSP-RecordSet-Read', this.options, viewRecordSetRead);
313
- this.childViews.dashboard = this.fable.addView('RSP-RecordSet-Dashboard', this.options, viewRecordSetDashboard);
314
+ this.pict.addTemplate(require('../templates/Pict-Template-FilterInstanceViews.js'));
315
+ this.childViews.errorNotFound = this.fable.addView('RSP-RecordSet-Error-NotFound', ViewDefinitionRecordSetErrorNotFound);
316
+ this.childViews.list = this.fable.addView('RSP-RecordSet-List', this.options, ViewRecordSetList);
317
+ this.childViews.edit = this.fable.addView('RSP-RecordSet-Edit', this.options, ViewRecordSetEdit);
318
+ this.childViews.read = this.fable.addView('RSP-RecordSet-Read', this.options, ViewRecordSetRead);
319
+ this.childViews.dashboard = this.fable.addView('RSP-RecordSet-Dashboard', this.options, ViewRecordSetDashboard);
314
320
 
315
321
  // Initialize the subviews
316
322
  this.childViews.list.initialize();
@@ -320,6 +326,22 @@ class RecordSetMetacontroller extends libFableServiceProviderBase
320
326
 
321
327
  // Now initialize the router
322
328
 
329
+ if (this.fable.settings.hasOwnProperty('Filters') && typeof this.fable.settings.Filters === 'object')
330
+ {
331
+ for (const tmpFilterKey of Object.keys(this.fable.settings.Filters))
332
+ {
333
+ this.pict.providers.FilterManager.addFilter(tmpFilterKey, this.fable.settings.Filters[tmpFilterKey]);
334
+ }
335
+ }
336
+
337
+ if (this.fable.settings.hasOwnProperty('FilterCriteria') && typeof this.fable.settings.FilterCriteria === 'object')
338
+ {
339
+ for (const tmpFilterCriterionKey of Object.keys(this.fable.settings.FilterCriteria))
340
+ {
341
+ this.pict.providers.FilterManager.addFilterCriteria(tmpFilterCriterionKey, this.fable.settings.FilterCriteria[tmpFilterCriterionKey]);
342
+ }
343
+ }
344
+
323
345
  if (this.fable.settings.hasOwnProperty('DefaultRecordSetConfigurations'))
324
346
  {
325
347
  this.loadRecordSetConfigurationArray(this.fable.settings.DefaultRecordSetConfigurations);
@@ -346,7 +368,7 @@ class RecordSetMetacontroller extends libFableServiceProviderBase
346
368
  // Load pict-router if it isn't loaded
347
369
  if (!('RecordSetRouter' in this.fable.providers))
348
370
  {
349
- this.fable.addProvider('RecordSetRouter', {}, providerRouter);
371
+ this.fable.addProvider('RecordSetRouter', {}, ProviderRouter);
350
372
  this.fable.providers.RecordSetRouter.initialize();
351
373
  }
352
374
 
@@ -0,0 +1,222 @@
1
+ const libPictTemplate = require('pict-template');
2
+ const libFilterViews = require('../views/filters/index.js');
3
+
4
+ /**
5
+ * Specialized instruction for rendering the filter view and plumbing in required context.
6
+ *
7
+ * Based on the Pict base {~V:...~} template instruction.
8
+ */
9
+ class PictTemplateFilterInstanceViewInstruction extends libPictTemplate
10
+ {
11
+ /**
12
+ * @param {Object} pFable - The Fable Framework instance
13
+ * @param {Object} pOptions - The options for the service
14
+ * @param {String} pServiceHash - The hash of the service
15
+ */
16
+ constructor(pFable, pOptions, pServiceHash)
17
+ {
18
+ super(pFable, pOptions, pServiceHash);
19
+
20
+ /** @type {any} */
21
+ this.log;
22
+
23
+ this.addPattern('{~FIV:', '~}');
24
+ this.addPattern('{~FilterInstanceViews:', '~}');
25
+
26
+
27
+ if (!('__TemplateOutputCache' in this.pict))
28
+ {
29
+ this.pict.__TemplateOutputCache = {};
30
+ }
31
+ }
32
+
33
+ _getViewForFilterClauses(pClauses)
34
+ {
35
+ const tmpViewHash =`PSRS-FilterType-${pClauses.Type}`;
36
+ /** @type {import('../views/filters/RecordSet-Filter-Base.js')} */
37
+ let tmpView = this.pict.views[tmpViewHash];
38
+ if (!tmpView)
39
+ {
40
+ const tmpViewPrototype = libFilterViews[pClauses.Type] || libFilterViews.Base;
41
+ if (!tmpViewPrototype)
42
+ {
43
+ this.pict.log.error(`Pict: Filter Instance Views Template Render: No view prototype found for filter type [${pClauses.Type}]`);
44
+ return null;
45
+ }
46
+ //FIXME: is this safe? will this view get rendered at other times?
47
+ tmpView = this.pict.addView(tmpViewHash, { }, tmpViewPrototype);
48
+ }
49
+ return tmpView;
50
+ }
51
+
52
+ /**
53
+ * Render a template expression, returning a string with the resulting content.
54
+ *
55
+ * @param {string} pTemplateHash - The hash contents of the template (what's between the template start and stop tags)
56
+ * @param {any} pRecord - The json object to be used as the Record for the template render
57
+ * @param {Array<any>} pContextArray - An array of context objects accessible from the template; safe to leave empty
58
+ * @param {any} [pScope] - A sticky scope that can be used to carry state and simplify template
59
+ *
60
+ * @return {string} The rendered template
61
+ */
62
+ render(pTemplateHash, pRecord, pContextArray, pScope)
63
+ {
64
+ const tmpRecordSet = pRecord.RecordSet || '';
65
+ if (!tmpRecordSet)
66
+ {
67
+ this.pict.log.error(`Pict: Filter Instance Views Template Render: No record set specified in record`);
68
+ return '';
69
+ }
70
+ const tmpRecordSetConfiguration = this.pict.PictSectionRecordSet.recordSetProviderConfigurations?.[tmpRecordSet];
71
+ if (!tmpRecordSetConfiguration)
72
+ {
73
+ this.pict.log.error(`Pict: Filter Instance Views Template Render: No record set configuration found for [${tmpRecordSet}]`);
74
+ return '';
75
+ }
76
+ let tmpViewContext = pRecord.ViewContext;
77
+ if (tmpViewContext)
78
+ {
79
+ tmpViewContext = tmpViewContext.trim();
80
+ }
81
+ else
82
+ {
83
+ tmpViewContext = 'Default';
84
+ }
85
+ pRecord = pRecord || {};
86
+ if (!pRecord.RecordSet)
87
+ {
88
+ pRecord.RecordSet = tmpRecordSet;
89
+ }
90
+ if (!pRecord.RecordSetConfiguration)
91
+ {
92
+ pRecord.RecordSetConfiguration = tmpRecordSetConfiguration;
93
+ }
94
+ if (!pRecord.ViewContext)
95
+ {
96
+ pRecord.ViewContext = pRecord.DashboardHash ? `${tmpViewContext}-${pRecord.DashboardHash}` : tmpViewContext;
97
+ }
98
+
99
+ let tmpRenderGUID = this.pict.getUUID();
100
+ let tmpResult = '';
101
+
102
+ const tmpClauses = this.pict.Bundle._ActiveFilterState?.[pRecord.RecordSet]?.FilterClauses || [];
103
+ //FIXME: lookup by hash instead?
104
+ for (let i = 0; i < tmpClauses.length; i++)
105
+ //for (const tmpClauses of this.pict.Bundle._ActiveFilterState?.[pRecord.RecordSet]?.FilterClauses || [])
106
+ {
107
+ const tmpClause = tmpClauses[i];
108
+ /** @type {import('../views/filters/RecordSet-Filter-Base.js')} */
109
+ const tmpView = this._getViewForFilterClauses(tmpClause);
110
+ if (!tmpView)
111
+ {
112
+ continue;
113
+ }
114
+
115
+ const tmpRecord = Object.assign({}, pRecord, tmpClause);
116
+ //tmpRecord.ClauseAddress = `Bundle._ActiveFilterState['${pRecord.RecordSet}'].FilterClauses[${i}]`;
117
+ tmpRecord.ClauseAddress = `_ActiveFilterState[\`${pRecord.RecordSet}\`].FilterClauses[${i}]`;
118
+ tmpView.prepareRecord(tmpRecord);
119
+ tmpView.renderWithScope(tmpClause, '__Virtual', `__TemplateOutputCache.${tmpRenderGUID}`, tmpRecord);
120
+
121
+ tmpResult += this.pict.__TemplateOutputCache[tmpRenderGUID];
122
+ // TODO: Uncomment this when we like how it's working
123
+ //delete this.pict.__TemplateOutputCache[tmpRenderGUID];
124
+
125
+ }
126
+ return tmpResult;
127
+ }
128
+
129
+ /**
130
+ * @param {string} pTemplateHash - The hash contents of the template (what's between the template start and stop tags)
131
+ * @param {any} pRecord - The json object to be used as the Record for the template
132
+ * @param {(pError?: Error, pResult?: string) => void} fCallback - The callback function to call with the result
133
+ * @param {Array<any>} pContextArray - An array of context objects accessible from the template; safe to leave empty
134
+ * @param {any} [pScope] - A sticky scope that can be used to carry state and simplify template
135
+ * @return {void}
136
+ */
137
+ renderAsync(pTemplateHash, pRecord, fCallback, pContextArray, pScope)
138
+ {
139
+ const tmpRecordSet = pRecord.RecordSet || '';
140
+ if (!tmpRecordSet)
141
+ {
142
+ this.pict.log.error(`Pict: Filter Instance Views Template Render: No record set specified in record`);
143
+ return fCallback(null, '');
144
+ }
145
+ const tmpRecordSetConfiguration = this.pict.PictSectionRecordSet.recordSetProviderConfigurations?.[tmpRecordSet];
146
+ if (!tmpRecordSetConfiguration)
147
+ {
148
+ this.pict.log.error(`Pict: Filter Instance Views Template Render: No record set configuration found for [${tmpRecordSet}]`);
149
+ return fCallback(null, '');
150
+ }
151
+ let tmpViewContext = pRecord.ViewContext;
152
+ if (tmpViewContext)
153
+ {
154
+ tmpViewContext = tmpViewContext.trim();
155
+ }
156
+ else
157
+ {
158
+ tmpViewContext = 'Default';
159
+ }
160
+
161
+ pRecord = pRecord || {};
162
+ if (!pRecord.RecordSet)
163
+ {
164
+ pRecord.RecordSet = tmpRecordSet;
165
+ }
166
+ if (!pRecord.RecordSetConfiguration)
167
+ {
168
+ pRecord.RecordSetConfiguration = tmpRecordSetConfiguration;
169
+ }
170
+ if (!pRecord.ViewContext)
171
+ {
172
+ pRecord.ViewContext = pRecord.DashboardHash ? `${tmpViewContext}-${pRecord.DashboardHash}` : tmpViewContext;
173
+ }
174
+ const tmpAnticipate = this.pict.newAnticipate();
175
+ let tmpResult = '';
176
+
177
+ const tmpClauses = this.pict.Bundle._ActiveFilterState?.[pRecord.RecordSet]?.FilterClauses || [];
178
+ //FIXME: lookup by hash instead?
179
+ for (let i = 0; i < tmpClauses.length; i++)
180
+ {
181
+ const tmpClause = tmpClauses[i];
182
+ /** @type {import('../views/filters/RecordSet-Filter-Base.js')} */
183
+ const tmpView = this._getViewForFilterClauses(tmpClause);
184
+ if (!tmpView)
185
+ {
186
+ continue;
187
+ }
188
+
189
+ const tmpRenderGUID = this.pict.getUUID();
190
+ tmpAnticipate.anticipate((fNext) =>
191
+ {
192
+ const tmpRecord = Object.assign({}, pRecord, tmpClause);
193
+ tmpRecord.ClauseAddress = `_ActiveFilterState[${pRecord.RecordSet}].FilterClauses[${i}]`;
194
+ tmpView.prepareRecord(tmpRecord);
195
+
196
+ return tmpView.renderWithScopeAsync(tmpClause, '__Virtual', `__TemplateOutputCache.${tmpRenderGUID}`, tmpRecord,
197
+ (pError, pResult) =>
198
+ {
199
+ if (pError)
200
+ {
201
+ this.log.warn(`Pict: Filter Instance Views Template Render: Error rendering view [${tmpView.Hash}]`, pError);
202
+ //TODO: should we fail the whole render?
203
+ return fNext();
204
+ //return fNext(pError);
205
+ }
206
+
207
+ tmpResult += this.pict.__TemplateOutputCache[tmpRenderGUID];
208
+ // TODO: Uncomment this when we like how it's working
209
+ //delete this.pict.__TemplateOutputCache[tmpRenderGUID];
210
+
211
+ return fNext(null);
212
+ });
213
+ });
214
+ }
215
+ return tmpAnticipate.wait((pError) =>
216
+ {
217
+ return fCallback(pError, tmpResult);
218
+ });
219
+ }
220
+ }
221
+
222
+ module.exports = PictTemplateFilterInstanceViewInstruction;
@@ -35,10 +35,11 @@ class PictTemplateFilterViewInstruction extends libPictTemplate
35
35
  * @param {string} pTemplateHash - The hash contents of the template (what's between the template start and stop tags)
36
36
  * @param {any} pRecord - The json object to be used as the Record for the template render
37
37
  * @param {Array<any>} pContextArray - An array of context objects accessible from the template; safe to leave empty
38
+ * @param {any} [pScope] - A sticky scope that can be used to carry state and simplify template
38
39
  *
39
40
  * @return {string} The rendered template
40
41
  */
41
- render(pTemplateHash, pRecord, pContextArray)
42
+ render(pTemplateHash, pRecord, pContextArray, pScope)
42
43
  {
43
44
  let [ tmpViewHash, tmpViewContext ] = pTemplateHash.split(':');
44
45
  tmpViewHash = tmpViewHash.trim();
@@ -101,9 +102,10 @@ class PictTemplateFilterViewInstruction extends libPictTemplate
101
102
  * @param {any} pRecord - The json object to be used as the Record for the template
102
103
  * @param {(pError?: Error, pResult?: string) => void} fCallback - The callback function to call with the result
103
104
  * @param {Array<any>} pContextArray - An array of context objects accessible from the template; safe to leave empty
105
+ * @param {any} [pScope] - A sticky scope that can be used to carry state and simplify template
104
106
  * @return {void}
105
107
  */
106
- renderAsync(pTemplateHash, pRecord, fCallback, pContextArray)
108
+ renderAsync(pTemplateHash, pRecord, fCallback, pContextArray, pScope)
107
109
  {
108
110
  let [ tmpViewHash, tmpViewContext ] = pTemplateHash.split(':');
109
111
  tmpViewHash = tmpViewHash.trim();