pict-section-recordset 1.0.24 → 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 (98) hide show
  1. package/example_applications/simple_entity/Simple-RecordSet-Application.js +170 -110
  2. package/package.json +7 -7
  3. package/source/application/Pict-Application-RecordSet.js +8 -0
  4. package/source/providers/RecordSet-Link-Manager.js +2 -2
  5. package/source/providers/RecordSet-RecordProvider-Base.js +9 -2
  6. package/source/providers/RecordSet-RecordProvider-MeadowEndpoints.js +116 -14
  7. package/source/services/RecordsSet-MetaController.js +41 -19
  8. package/source/templates/Pict-Template-FilterInstanceViews.js +222 -0
  9. package/source/templates/Pict-Template-FilterView.js +4 -2
  10. package/source/views/RecordSet-Filters.js +449 -0
  11. package/source/views/dashboard/RecordSet-Dashboard.js +65 -8
  12. package/source/views/filters/RecordSet-Filter-Base-Range.js +38 -0
  13. package/source/views/filters/RecordSet-Filter-Base.js +88 -0
  14. package/source/views/filters/RecordSet-Filter-DateMatch.js +56 -0
  15. package/source/views/filters/RecordSet-Filter-DateRange.js +75 -0
  16. package/source/views/filters/RecordSet-Filter-ExternalJoinDateMatch.js +55 -0
  17. package/source/views/filters/RecordSet-Filter-ExternalJoinDateRange.js +57 -0
  18. package/source/views/filters/RecordSet-Filter-ExternalJoinNumericMatch.js +54 -0
  19. package/source/views/filters/RecordSet-Filter-ExternalJoinNumericRange.js +57 -0
  20. package/source/views/filters/RecordSet-Filter-ExternalJoinStringMatch.js +45 -0
  21. package/source/views/filters/RecordSet-Filter-ExternalJoinStringRange.js +46 -0
  22. package/source/views/filters/RecordSet-Filter-InternalJoinDateMatch.js +55 -0
  23. package/source/views/filters/RecordSet-Filter-InternalJoinDateRange.js +57 -0
  24. package/source/views/filters/RecordSet-Filter-InternalJoinNumericMatch.js +55 -0
  25. package/source/views/filters/RecordSet-Filter-InternalJoinNumericRange.js +57 -0
  26. package/source/views/filters/RecordSet-Filter-InternalJoinStringMatch.js +45 -0
  27. package/source/views/filters/RecordSet-Filter-InternalJoinStringRange.js +46 -0
  28. package/source/views/filters/RecordSet-Filter-NumericMatch.js +55 -0
  29. package/source/views/filters/RecordSet-Filter-NumericRange.js +57 -0
  30. package/source/views/filters/RecordSet-Filter-StringMatch.js +45 -0
  31. package/source/views/filters/RecordSet-Filter-StringRange.js +46 -0
  32. package/source/views/filters/index.js +27 -0
  33. package/source/views/list/RecordSet-List.js +44 -6
  34. package/test/PictSectionRecordSet-RecordProvider-Meadow_tests.js +11 -0
  35. package/types/application/Pict-Application-RecordSet.d.ts.map +1 -1
  36. package/types/providers/RecordSet-DynamicRecordsetSolver.d.ts +6 -6
  37. package/types/providers/RecordSet-DynamicRecordsetSolver.d.ts.map +1 -1
  38. package/types/providers/RecordSet-Link-Manager.d.ts +3 -3
  39. package/types/providers/RecordSet-RecordProvider-Base.d.ts +9 -0
  40. package/types/providers/RecordSet-RecordProvider-Base.d.ts.map +1 -1
  41. package/types/providers/RecordSet-RecordProvider-MeadowEndpoints.d.ts +30 -4
  42. package/types/providers/RecordSet-RecordProvider-MeadowEndpoints.d.ts.map +1 -1
  43. package/types/services/RecordsSet-MetaController.d.ts +2 -2
  44. package/types/services/RecordsSet-MetaController.d.ts.map +1 -1
  45. package/types/templates/Pict-Template-FilterInstanceViews.d.ts +19 -0
  46. package/types/templates/Pict-Template-FilterInstanceViews.d.ts.map +1 -0
  47. package/types/templates/Pict-Template-FilterView.d.ts.map +1 -1
  48. package/types/views/RecordSet-Filters.d.ts +84 -0
  49. package/types/views/RecordSet-Filters.d.ts.map +1 -0
  50. package/types/views/dashboard/RecordSet-Dashboard.d.ts +4 -2
  51. package/types/views/dashboard/RecordSet-Dashboard.d.ts.map +1 -1
  52. package/types/views/filters/RecordSet-Filter-Base-Range.d.ts +5 -0
  53. package/types/views/filters/RecordSet-Filter-Base-Range.d.ts.map +1 -0
  54. package/types/views/filters/RecordSet-Filter-Base.d.ts +29 -0
  55. package/types/views/filters/RecordSet-Filter-Base.d.ts.map +1 -0
  56. package/types/views/filters/RecordSet-Filter-DateMatch.d.ts +9 -0
  57. package/types/views/filters/RecordSet-Filter-DateMatch.d.ts.map +1 -0
  58. package/types/views/filters/RecordSet-Filter-DateRange.d.ts +9 -0
  59. package/types/views/filters/RecordSet-Filter-DateRange.d.ts.map +1 -0
  60. package/types/views/filters/RecordSet-Filter-ExternalJoinDateMatch.d.ts +9 -0
  61. package/types/views/filters/RecordSet-Filter-ExternalJoinDateMatch.d.ts.map +1 -0
  62. package/types/views/filters/RecordSet-Filter-ExternalJoinDateRange.d.ts +9 -0
  63. package/types/views/filters/RecordSet-Filter-ExternalJoinDateRange.d.ts.map +1 -0
  64. package/types/views/filters/RecordSet-Filter-ExternalJoinNumericMatch.d.ts +9 -0
  65. package/types/views/filters/RecordSet-Filter-ExternalJoinNumericMatch.d.ts.map +1 -0
  66. package/types/views/filters/RecordSet-Filter-ExternalJoinNumericRange.d.ts +9 -0
  67. package/types/views/filters/RecordSet-Filter-ExternalJoinNumericRange.d.ts.map +1 -0
  68. package/types/views/filters/RecordSet-Filter-ExternalJoinStringMatch.d.ts +9 -0
  69. package/types/views/filters/RecordSet-Filter-ExternalJoinStringMatch.d.ts.map +1 -0
  70. package/types/views/filters/RecordSet-Filter-ExternalJoinStringRange.d.ts +9 -0
  71. package/types/views/filters/RecordSet-Filter-ExternalJoinStringRange.d.ts.map +1 -0
  72. package/types/views/filters/RecordSet-Filter-InternalJoinDateMatch.d.ts +9 -0
  73. package/types/views/filters/RecordSet-Filter-InternalJoinDateMatch.d.ts.map +1 -0
  74. package/types/views/filters/RecordSet-Filter-InternalJoinDateRange.d.ts +9 -0
  75. package/types/views/filters/RecordSet-Filter-InternalJoinDateRange.d.ts.map +1 -0
  76. package/types/views/filters/RecordSet-Filter-InternalJoinNumericMatch.d.ts +9 -0
  77. package/types/views/filters/RecordSet-Filter-InternalJoinNumericMatch.d.ts.map +1 -0
  78. package/types/views/filters/RecordSet-Filter-InternalJoinNumericRange.d.ts +9 -0
  79. package/types/views/filters/RecordSet-Filter-InternalJoinNumericRange.d.ts.map +1 -0
  80. package/types/views/filters/RecordSet-Filter-InternalJoinStringMatch.d.ts +9 -0
  81. package/types/views/filters/RecordSet-Filter-InternalJoinStringMatch.d.ts.map +1 -0
  82. package/types/views/filters/RecordSet-Filter-InternalJoinStringRange.d.ts +9 -0
  83. package/types/views/filters/RecordSet-Filter-InternalJoinStringRange.d.ts.map +1 -0
  84. package/types/views/filters/RecordSet-Filter-NumericMatch.d.ts +9 -0
  85. package/types/views/filters/RecordSet-Filter-NumericMatch.d.ts.map +1 -0
  86. package/types/views/filters/RecordSet-Filter-NumericRange.d.ts +9 -0
  87. package/types/views/filters/RecordSet-Filter-NumericRange.d.ts.map +1 -0
  88. package/types/views/filters/RecordSet-Filter-StringMatch.d.ts +9 -0
  89. package/types/views/filters/RecordSet-Filter-StringMatch.d.ts.map +1 -0
  90. package/types/views/filters/RecordSet-Filter-StringRange.d.ts +9 -0
  91. package/types/views/filters/RecordSet-Filter-StringRange.d.ts.map +1 -0
  92. package/types/views/filters/index.d.ts +20 -0
  93. package/types/views/filters/index.d.ts.map +1 -0
  94. package/types/views/list/RecordSet-List.d.ts +12 -2
  95. package/types/views/list/RecordSet-List.d.ts.map +1 -1
  96. package/source/views/RecordSet-Filter.js +0 -159
  97. package/types/views/RecordSet-Filter.d.ts +0 -39
  98. package/types/views/RecordSet-Filter.d.ts.map +0 -1
@@ -0,0 +1,449 @@
1
+ const libPictView = require('pict-view');
2
+
3
+ /** @type {Record<string, any>} */
4
+ const _DEFAULT_CONFIGURATION_SUBSET_Filter =
5
+ {
6
+ ViewIdentifier: 'PRSP-SUBSET-Filters',
7
+
8
+ DefaultRenderable: 'PRSP_Renderable_Filters',
9
+ DefaultDestinationAddress: '#PRSP_Filters_Container',
10
+ DefaultTemplateRecordAddress: false,
11
+
12
+ // If this is set to true, when the App initializes this will.
13
+ // While the App initializes, initialize will be called.
14
+ AutoInitialize: false,
15
+ AutoInitializeOrdinal: 0,
16
+
17
+ // If this is set to true, when the App autorenders (on load) this will.
18
+ // After the App initializes, render will be called.
19
+ AutoRender: false,
20
+ AutoRenderOrdinal: 0,
21
+
22
+ AutoSolveWithApp: false,
23
+ AutoSolveOrdinal: 0,
24
+
25
+ CSS: false,
26
+ CSSPriority: 500,
27
+
28
+ Templates:
29
+ [
30
+ {
31
+ Hash: 'PRSP-SUBSET-Filters-Template',
32
+ Template: /*html*/`
33
+ <!-- DefaultPackage pict view template: [PRSP-SUBSET-Filters-Template] -->
34
+ <section id="PRSP_Filters_Container">
35
+ <form id="PRSP_Filter_Form" onsubmit="_Pict.views['PRSP-Filters'].handleSearch(event, '{~D:Record.RecordSet~}', '{~D:Record.ViewContext~}'); return false;">
36
+ {~T:PRSP-SUBSET-Filters-Template-Input-Fieldset~}
37
+ <div id="PRSP_Filter_Instances">
38
+ {~FIV:Record~}
39
+ </div>
40
+ {~T:PRSP-SUBSET-Filters-Template-Button-Fieldset~}
41
+ </form>
42
+ </section>
43
+ <!-- DefaultPackage end view template: [PRSP-SUBSET-Filters-Template] -->
44
+ `
45
+ },
46
+ {
47
+ Hash: 'PRSP-SUBSET-Filters-Template-Input-Fieldset',
48
+ Template: /*html*/`
49
+ <!-- DefaultPackage pict view template: [PRSP-SUBSET-Filters-Template-Input-Fieldset] -->
50
+ <fieldset>
51
+ <label for="filter">Filter:</label>
52
+ <input type="text" name="filter">
53
+ </fieldset>
54
+ <!-- DefaultPackage end view template: [PRSP-SUBSET-Filters-Template-Input-Fieldset] -->
55
+ `
56
+ },
57
+ {
58
+ Hash: 'PRSP-SUBSET-Filters-Template-Button-Fieldset',
59
+ Template: /*html*/`
60
+ <!-- DefaultPackage pict view template: [PRSP-SUBSET-Filters-Template-Button-Fieldset] -->
61
+ <fieldset>
62
+ <button type="button" id="PRSP_Filter_Button_Reset" onclick="_Pict.views['PRSP-Filters'].handleReset(event, '{~D:Record.RecordSet~}', '{~D:Record.ViewContext~}')">Reset</button>
63
+ <button type="submit" id="PRSP_Filter_Button_Apply">Apply</button>
64
+ </fieldset>
65
+ <!-- DefaultPackage end view template: [PRSP-SUBSET-Filters-Template-Button-Fieldset] -->
66
+ `
67
+ },
68
+ ],
69
+
70
+ Renderables:
71
+ [
72
+ {
73
+ RenderableHash: 'PRSP_Renderable_Filters',
74
+ TemplateHash: 'PRSP-SUBSET-Filters-Template',
75
+ DestinationAddress: '#PRSP_Filters_Container',
76
+ RenderMethod: 'replace'
77
+ },
78
+ ],
79
+
80
+ Manifests: {},
81
+ };
82
+
83
+ //FIXME: export this from PSF?
84
+ const libPictViewDynamicForm = require('pict-section-form/source/views/Pict-View-DynamicForm.js');
85
+
86
+ class ViewRecordSetSUBSETFilters extends libPictView
87
+ {
88
+ constructor(pFable, pOptions, pServiceHash)
89
+ {
90
+ let tmpOptions = Object.assign({}, _DEFAULT_CONFIGURATION_SUBSET_Filter, pOptions);
91
+ super(pFable, tmpOptions, pServiceHash);
92
+ /** @type {import('fable') & import('pict') & { PictSectionRecordSet: import('../Pict-Section-RecordSet.js') }} */
93
+ this.pict;
94
+
95
+ const tmpDynamicInputViewSection = (
96
+ {
97
+ "Hash": "PSRSDynamicInputs",
98
+ "Name": "Custom Dynamic Inputs",
99
+ "ViewHash": "PSRSFilterProxyView",
100
+
101
+ "AutoMarshalDataOnSolve": true,
102
+ "IncludeInMetatemplateSectionGeneration": false,
103
+
104
+ "Manifests":
105
+ {
106
+ "Section":
107
+ {
108
+ "Scope": "PSRSDynamic",
109
+ "Sections":
110
+ [
111
+ {
112
+ "Hash": "PSRSDynamicInputs",
113
+ "Name": "Dynamic Inputs"
114
+ }
115
+ ],
116
+ "Descriptors":
117
+ {
118
+ "PSRS.DynamicInputPlaceholder":
119
+ {
120
+ "Name": "DynamicInputPlaceholder",
121
+ "Hash": "DynamicInputPlaceholder",
122
+ "DataType": "String",
123
+ "Macro":
124
+ {
125
+ "HTMLSelector": ""
126
+ },
127
+ "PictForm":
128
+ {
129
+ "Section": "PSRSDynamicInputs"
130
+ }
131
+ }
132
+ }
133
+ }
134
+ }
135
+ });
136
+ if (!this.pict.views[tmpDynamicInputViewSection.ViewHash])
137
+ {
138
+ const tmpViewConfiguration = Object.assign({}, tmpDynamicInputViewSection);
139
+ this.pict.addView(tmpViewConfiguration.ViewHash, tmpViewConfiguration, libPictViewDynamicForm);
140
+ this.pict.views[tmpDynamicInputViewSection.ViewHash].viewMarshalDestination = 'Bundle';
141
+ }
142
+ this.chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
143
+
144
+ // Use a lookup table to find the index.
145
+ this.lookup = typeof Uint8Array === 'undefined' ? [] : new Uint8Array(256);
146
+ for (let i = 0; i < this.chars.length; i++)
147
+ {
148
+ this.lookup[this.chars.charCodeAt(i)] = i;
149
+ }
150
+ }
151
+
152
+ //NOTE: two methods below copied from the pict-section-form metacontroller
153
+ //TODO: consider subclassing the dynamic view to somehow mark these as filter views so we can only operate on those views
154
+
155
+ /**
156
+ * Marshals data from the view to the model, usually AppData (or configured data store).
157
+ *
158
+ * @returns {any} The result of the superclass's onMarshalFromView method.
159
+ */
160
+ onMarshalFromView()
161
+ {
162
+ let tmpViewList = Object.keys(this.fable.views);
163
+ for (let i = 0; i < tmpViewList.length; i++)
164
+ {
165
+ if (this.fable.views[tmpViewList[i]].isPictSectionForm)
166
+ {
167
+ this.fable.views[tmpViewList[i]].marshalFromView();
168
+ }
169
+ }
170
+ return super.onMarshalFromView();
171
+ }
172
+
173
+ /**
174
+ * Marshals the data to the view from the model, usually AppData (or configured data store).
175
+ *
176
+ * @returns {any} The result of the super.onMarshalToView() method.
177
+ */
178
+ onMarshalToView()
179
+ {
180
+ let tmpViewList = Object.keys(this.fable.views);
181
+ for (let i = 0; i < tmpViewList.length; i++)
182
+ {
183
+ if (this.fable.views[tmpViewList[i]].isPictSectionForm)
184
+ {
185
+ this.fable.views[tmpViewList[i]].marshalToView();
186
+ }
187
+ }
188
+ return super.onMarshalToView();
189
+ }
190
+
191
+ /**
192
+ * @param {Event} pEvent - The DOM event that triggered the search
193
+ * @param {string} pRecordSet - The record set being filtered
194
+ * @param {string} pViewContext - The view context for the filter (ex. List, Dashboard)
195
+ */
196
+ handleSearch(pEvent, pRecordSet, pViewContext)
197
+ {
198
+ pEvent.preventDefault(); // don't submit the form
199
+ pEvent.stopPropagation();
200
+ //FIXME: store this filter string in the bundle so we can re-apply it on re-render
201
+ const tmpSearchString = this.pict.ContentAssignment.readContent(`input[name="filter"]`);
202
+ this.performSearch(pRecordSet, pViewContext, tmpSearchString ? String(tmpSearchString) : '');
203
+ }
204
+
205
+ /**
206
+ * @param {string} pRecordSet - The record set being filtered
207
+ * @param {string} pViewContext - The view context for the filter (ex. List, Dashboard)
208
+ * @param {string} [pFilterString] - The filter string to apply, defaults to a single space if not provided
209
+ */
210
+ performSearch(pRecordSet, pViewContext, pFilterString)
211
+ {
212
+ const tmpPictRouter = this.pict.providers.PictRouter;
213
+ const tmpProviderConfiguration = this.pict.PictSectionRecordSet.recordSetProviderConfigurations[pRecordSet];
214
+ let filterExpr = '';
215
+ if (pFilterString)
216
+ {
217
+ /** @type {Array<string>} */
218
+ const searchFields = tmpProviderConfiguration?.SearchFields ?? [ 'Name' ];
219
+ filterExpr = searchFields.map((filterField) => `FBVOR~${filterField}~LK~${encodeURIComponent(`%${pFilterString}%`)}`).join('~');
220
+ }
221
+ let tmpURLTemplate = tmpProviderConfiguration[`RecordSetFilterURLTemplate-${pViewContext}`] || tmpProviderConfiguration[`RecordSetFilterURLTemplate-Default`];
222
+ if (!tmpURLTemplate)
223
+ {
224
+ if (pViewContext === 'Dashboard' || pViewContext === 'List')
225
+ {
226
+ tmpURLTemplate = `/PSRS/${pRecordSet}/${pViewContext}/FilteredTo/{~D:Record.FilterString~}`;
227
+ }
228
+ }
229
+ let tmpURL;
230
+ if (tmpURLTemplate)
231
+ {
232
+ tmpURL = this.pict.parseTemplate(tmpURLTemplate,
233
+ {
234
+ RecordSet: pRecordSet,
235
+ FilterString: filterExpr,
236
+ });
237
+ }
238
+ else
239
+ {
240
+ tmpURL = `/PSRS/${pRecordSet}/List/FilteredTo/${filterExpr}`;
241
+ }
242
+ if (tmpURL.endsWith('FilteredTo/') || tmpURL.includes('FilteredTo//'))
243
+ {
244
+ tmpURL = tmpURL.replace(/\/FilteredTo\//, '');
245
+ }
246
+ this.serializeFilterExperience(this.pict.Bundle._ActiveFilterState[pRecordSet]?.FilterClauses).then((pFilterExperienceSerialized) =>
247
+ {
248
+ if (pFilterExperienceSerialized)
249
+ {
250
+ tmpURL += `/FilterExperience/${encodeURIComponent(pFilterExperienceSerialized)}`;
251
+ }
252
+ //FIXME: this doesn't force a re-render if other filters have changes, but aren't in the URL - so we either need to put them in the URL, or force a re-render based on the filter states
253
+ tmpPictRouter.router.navigate(tmpURL);
254
+ });
255
+ }
256
+
257
+ /**
258
+ * @param {Event} pEvent - The DOM event that triggered the search
259
+ * @param {string} pRecordSet - The record set being filtered
260
+ * @param {string} pViewContext - The view context for the filter (ex. List, Dashboard)
261
+ */
262
+ handleReset(pEvent, pRecordSet, pViewContext)
263
+ {
264
+ pEvent.preventDefault();
265
+ this.pict.ContentAssignment.assignContent('input[name="filter"]', '');
266
+ const tmpFilterExperienceClauses = this.pict.Bundle._ActiveFilterState[pRecordSet]?.FilterClauses;
267
+ if (Array.isArray(tmpFilterExperienceClauses))
268
+ {
269
+ for (const tmpClause of tmpFilterExperienceClauses)
270
+ {
271
+ delete tmpClause.Value;
272
+ delete tmpClause.Values;
273
+ }
274
+ }
275
+ this.performSearch(pRecordSet, pViewContext);
276
+ }
277
+
278
+ /**
279
+ * Lifecycle hook that triggers after the view is rendered.
280
+ *
281
+ * @param {import('pict-view').Renderable} pRenderable - The renderable that was rendered.
282
+ * @param {string} pRenderDestinationAddress - The address where the renderable was rendered.
283
+ * @param {any} pRecord - The record (data) that was used by the renderable.
284
+ * @param {string} pContent - The content that was rendered.
285
+ */
286
+ onAfterRender(pRenderable, pRenderDestinationAddress, pRecord, pContent)
287
+ {
288
+ //FIXME: since this is rendering to the DOM indirectly, can't marshal right after render; need to fix this better, if this even works
289
+ setTimeout(() =>
290
+ {
291
+ this.onMarshalToView();
292
+ }, 1);
293
+ return super.onAfterRender(pRenderable, pRenderDestinationAddress, pRecord, pContent);
294
+ }
295
+
296
+ /**
297
+ * Lifecycle hook that triggers after the view is rendered (async flow).
298
+ *
299
+ * @param {import('pict-view').ErrorCallback} fCallback - The callback to call when the async operation is complete.
300
+ */
301
+ onAfterRenderAsync(fCallback)
302
+ {
303
+ return super.onAfterRenderAsync((pError) =>
304
+ {
305
+ //FIXME: since this is rendering to the DOM indirectly, can't marshal right after render; need to fix this better, if this even works
306
+ setTimeout(() =>
307
+ {
308
+ this.onMarshalToView();
309
+ }, 1);
310
+ fCallback(pError);
311
+ });
312
+ }
313
+
314
+ async serializeFilterExperience(pExperience)
315
+ {
316
+ if (!pExperience || typeof pExperience !== 'object')
317
+ {
318
+ return '';
319
+ }
320
+ return this.encode(await this.compress(JSON.stringify(pExperience)));
321
+ }
322
+
323
+ /**
324
+ * @param {string} pExperience - The serialized filter experience as a string.
325
+ *
326
+ * @return {Promise<Record<string, any>>} - The serialized filter experience as a string.
327
+ */
328
+ async deserializeFilterExperience(pExperience)
329
+ {
330
+ if (!pExperience || typeof pExperience !== 'string')
331
+ {
332
+ //TODO: if the default filters expand, how we wanna handle that?
333
+ return null;
334
+ }
335
+ return JSON.parse(await this.decompress(new Uint8Array(this.decode(pExperience))));
336
+ }
337
+
338
+ /**
339
+ * @param {string} string - The string to compress.
340
+ * @param {CompressionFormat} [encoding='gzip'] - The encoding to use for compression, defaults to 'gzip'.
341
+ *
342
+ * @return {Promise<ArrayBuffer>} - The compressed byte array.
343
+ */
344
+ async compress(string, encoding = 'gzip')
345
+ {
346
+ const byteArray = new TextEncoder().encode(string);
347
+ const cs = new CompressionStream(encoding);
348
+ const writer = cs.writable.getWriter();
349
+ writer.write(byteArray);
350
+ writer.close();
351
+ return new Response(cs.readable).arrayBuffer();
352
+ }
353
+
354
+ /**
355
+ * @param {Uint8Array} byteArray - The byte array to decompress.
356
+ * @param {CompressionFormat} [encoding='gzip'] - The encoding to use for compression, defaults to 'gzip'.
357
+ */
358
+ async decompress(byteArray, encoding = 'gzip')
359
+ {
360
+ const cs = new DecompressionStream(encoding);
361
+ const writer = cs.writable.getWriter();
362
+ writer.write(byteArray);
363
+ writer.close();
364
+ return new Response(cs.readable).arrayBuffer().then((arrayBuffer) =>
365
+ {
366
+ return new TextDecoder().decode(arrayBuffer);
367
+ });
368
+ }
369
+
370
+ /**
371
+ * @param {ArrayBuffer} arraybuffer - The ArrayBuffer to encode to Base64.
372
+ *
373
+ * @return {string} - The Base64 encoded string.
374
+ */
375
+ encode(arraybuffer)
376
+ {
377
+ let bytes = new Uint8Array(arraybuffer),
378
+ i,
379
+ len = bytes.length,
380
+ base64 = '';
381
+
382
+ for (i = 0; i < len; i += 3)
383
+ {
384
+ base64 += this.chars[bytes[i] >> 2];
385
+ base64 += this.chars[((bytes[i] & 3) << 4) | (bytes[i + 1] >> 4)];
386
+ base64 += this.chars[((bytes[i + 1] & 15) << 2) | (bytes[i + 2] >> 6)];
387
+ base64 += this.chars[bytes[i + 2] & 63];
388
+ }
389
+
390
+ if (len % 3 === 2)
391
+ {
392
+ base64 = base64.substring(0, base64.length - 1) + '=';
393
+ }
394
+ else if (len % 3 === 1)
395
+ {
396
+ base64 = base64.substring(0, base64.length - 2) + '==';
397
+ }
398
+
399
+ return base64;
400
+ };
401
+
402
+ /**
403
+ * @param {string} base64 - The Base64 encoded string to decode to an ArrayBuffer.
404
+ *
405
+ * @return {ArrayBuffer} - The decoded ArrayBuffer.
406
+ */
407
+ decode(base64)
408
+ {
409
+ let bufferLength = base64.length * 0.75,
410
+ len = base64.length,
411
+ i,
412
+ p = 0,
413
+ encoded1,
414
+ encoded2,
415
+ encoded3,
416
+ encoded4;
417
+
418
+ if (base64[base64.length - 1] === '=')
419
+ {
420
+ bufferLength--;
421
+ if (base64[base64.length - 2] === '=')
422
+ {
423
+ bufferLength--;
424
+ }
425
+ }
426
+
427
+ const arraybuffer = new ArrayBuffer(bufferLength),
428
+ bytes = new Uint8Array(arraybuffer);
429
+
430
+ for (i = 0; i < len; i += 4)
431
+ {
432
+ encoded1 = this.lookup[base64.charCodeAt(i)];
433
+ encoded2 = this.lookup[base64.charCodeAt(i + 1)];
434
+ encoded3 = this.lookup[base64.charCodeAt(i + 2)];
435
+ encoded4 = this.lookup[base64.charCodeAt(i + 3)];
436
+
437
+ bytes[p++] = (encoded1 << 2) | (encoded2 >> 4);
438
+ bytes[p++] = ((encoded2 & 15) << 4) | (encoded3 >> 2);
439
+ bytes[p++] = ((encoded3 & 3) << 6) | (encoded4 & 63);
440
+ }
441
+
442
+ return arraybuffer;
443
+ };
444
+ }
445
+
446
+ module.exports = ViewRecordSetSUBSETFilters;
447
+
448
+ module.exports.default_configuration = _DEFAULT_CONFIGURATION_SUBSET_Filter;
449
+
@@ -2,7 +2,7 @@ const libPictRecordSetRecordView = require('../RecordSet-RecordBaseView.js');
2
2
 
3
3
  const viewHeaderDashboard = require('./RecordSet-Dashboard-HeaderDashboard.js');
4
4
  const viewTitle = require('./RecordSet-Dashboard-Title.js');
5
- const viewFilters = require('../RecordSet-Filter.js');
5
+ const viewFilters = require('../RecordSet-Filters.js');
6
6
  const viewPaginationTop = require('./RecordSet-Dashboard-PaginationTop.js');
7
7
  const viewRecordList = require('./RecordSet-Dashboard-RecordList.js');
8
8
  const viewRecordListHeader = require('./RecordSet-Dashboard-RecordListHeader.js');
@@ -119,15 +119,16 @@ class viewRecordSetDashboard extends libPictRecordSetRecordView
119
119
  const tmpProviderHash = `RSP-Provider-${pRoutePayload.data.RecordSet}`;
120
120
 
121
121
  const tmpFilterString = pRoutePayload.data.FilterString || '';
122
+ const tmpSerializedFilterExperience = pRoutePayload.data.FilterExperience || '';
122
123
 
123
124
  const tmpOffset = pRoutePayload.data.Offset ? pRoutePayload.data.Offset : 0;
124
125
  const tmpPageSize = pRoutePayload.data.PageSize ? pRoutePayload.data.PageSize : 100;
125
126
 
126
127
  if (pRoutePayload.data.DashboardHash)
127
128
  {
128
- return this.renderSpecificDashboard(pRoutePayload.data.DashboardHash, tmpProviderConfiguration, tmpProviderHash, tmpFilterString, tmpOffset, tmpPageSize);
129
+ return this.renderSpecificDashboard(pRoutePayload.data.DashboardHash, tmpProviderConfiguration, tmpProviderHash, tmpFilterString, tmpSerializedFilterExperience, tmpOffset, tmpPageSize);
129
130
  }
130
- return this.renderDashboard(tmpProviderConfiguration, tmpProviderHash, tmpFilterString, tmpOffset, tmpPageSize);
131
+ return this.renderDashboard(tmpProviderConfiguration, tmpProviderHash, tmpFilterString, tmpSerializedFilterExperience, tmpOffset, tmpPageSize);
131
132
  }
132
133
 
133
134
  /**
@@ -135,14 +136,22 @@ class viewRecordSetDashboard extends libPictRecordSetRecordView
135
136
  */
136
137
  addRoutes(pPictRouter)
137
138
  {
139
+ pPictRouter.router.on('/PSRS/:RecordSet/Dashboard/FilterExperience/:FilterExperience', this.handleRecordSetDashboardRoute.bind(this));
140
+ pPictRouter.router.on('/PSRS/:RecordSet/Dashboard/FilteredTo/:FilterString/FilterExperience/:FilterExperience', this.handleRecordSetDashboardRoute.bind(this));
141
+ pPictRouter.router.on('/PSRS/:RecordSet/Dashboard/FilteredTo/:FilterString/:Offset/:PageSize/FilterExperience/:FilterExperience', this.handleRecordSetDashboardRoute.bind(this));
138
142
  pPictRouter.router.on('/PSRS/:RecordSet/Dashboard/FilteredTo/:FilterString/:Offset/:PageSize', this.handleRecordSetDashboardRoute.bind(this));
139
143
  pPictRouter.router.on('/PSRS/:RecordSet/Dashboard/FilteredTo/:FilterString', this.handleRecordSetDashboardRoute.bind(this));
144
+ pPictRouter.router.on('/PSRS/:RecordSet/Dashboard/:Offset/:PageSize/FilterExperience/:FilterExperience', this.handleRecordSetDashboardRoute.bind(this));
140
145
  pPictRouter.router.on('/PSRS/:RecordSet/Dashboard/:Offset/:PageSize', this.handleRecordSetDashboardRoute.bind(this));
141
146
  pPictRouter.router.on('/PSRS/:RecordSet/Dashboard/:Offset', this.handleRecordSetDashboardRoute.bind(this));
142
147
  pPictRouter.router.on('/PSRS/:RecordSet/Dashboard', this.handleRecordSetDashboardRoute.bind(this));
143
148
 
149
+ pPictRouter.router.on('/PSRS/:RecordSet/SpecificDashboard/:DashboardHash/FilterExperience/:FilterExperience', this.handleRecordSetDashboardRoute.bind(this));
150
+ pPictRouter.router.on('/PSRS/:RecordSet/SpecificDashboard/:DashboardHash/FilteredTo/:FilterString/FilterExperience/:FilterExperience', this.handleRecordSetDashboardRoute.bind(this));
151
+ pPictRouter.router.on('/PSRS/:RecordSet/SpecificDashboard/:DashboardHash/FilteredTo/:FilterString/:Offset/:PageSize/FilterExperience/:FilterExperience', this.handleRecordSetDashboardRoute.bind(this));
144
152
  pPictRouter.router.on('/PSRS/:RecordSet/SpecificDashboard/:DashboardHash/FilteredTo/:FilterString/:Offset/:PageSize', this.handleRecordSetDashboardRoute.bind(this));
145
153
  pPictRouter.router.on('/PSRS/:RecordSet/SpecificDashboard/:DashboardHash/FilteredTo/:FilterString', this.handleRecordSetDashboardRoute.bind(this));
154
+ pPictRouter.router.on('/PSRS/:RecordSet/SpecificDashboard/:DashboardHash/:Offset/:PageSize/FilterExperience/:FilterExperience', this.handleRecordSetDashboardRoute.bind(this));
146
155
  pPictRouter.router.on('/PSRS/:RecordSet/SpecificDashboard/:DashboardHash/:Offset/:PageSize', this.handleRecordSetDashboardRoute.bind(this));
147
156
  pPictRouter.router.on('/PSRS/:RecordSet/SpecificDashboard/:DashboardHash/:Offset', this.handleRecordSetDashboardRoute.bind(this));
148
157
  pPictRouter.router.on('/PSRS/:RecordSet/SpecificDashboard/:DashboardHash', this.handleRecordSetDashboardRoute.bind(this));
@@ -201,12 +210,13 @@ class viewRecordSetDashboard extends libPictRecordSetRecordView
201
210
  * @param {Record<string, any>} pRecordSetConfiguration
202
211
  * @param {string} pProviderHash
203
212
  * @param {string} pFilterString
213
+ * @param {string} pSerializedFilterExperience
204
214
  * @param {number} pOffset
205
215
  * @param {number} pPageSize
206
216
  *
207
217
  * @return {Promise<void>}
208
218
  */
209
- async renderSpecificDashboard(pDashboardHash, pRecordSetConfiguration, pProviderHash, pFilterString, pOffset, pPageSize)
219
+ async renderSpecificDashboard(pDashboardHash, pRecordSetConfiguration, pProviderHash, pFilterString, pSerializedFilterExperience, pOffset, pPageSize)
210
220
  {
211
221
  if (!pRecordSetConfiguration)
212
222
  {
@@ -231,6 +241,17 @@ class viewRecordSetDashboard extends libPictRecordSetRecordView
231
241
  tmpTitle = this.pict.parseTemplate(tmpManifestDefinition.TitleTemplate, pRecordSetConfiguration);
232
242
  }
233
243
 
244
+ const tmpEncodedFilterExperience = pSerializedFilterExperience && encodeURIComponent(pSerializedFilterExperience);
245
+ if (tmpEncodedFilterExperience)
246
+ {
247
+ // shove filter xp into the active filters for this recordset
248
+ const tmpExperienceFromURL = await this.pict.views['PRSP-Filters'].deserializeFilterExperience(pSerializedFilterExperience);
249
+ if (tmpExperienceFromURL)
250
+ {
251
+ this.pict.manifest.setValueByHash(this.pict.Bundle, `_ActiveFilterState[${pRecordSetConfiguration.RecordSet}].FilterClauses`, tmpExperienceFromURL);
252
+ }
253
+ }
254
+
234
255
  let tmpRecordDashboardData =
235
256
  {
236
257
  "Title": tmpTitle,
@@ -262,7 +283,7 @@ class viewRecordSetDashboard extends libPictRecordSetRecordView
262
283
  tmpRecordDashboardData.GUIDAddress = `GUID${this.pict.providers[pProviderHash].options.Entity}`;
263
284
 
264
285
  // Get the "page end record number" for the current page (e.g. for messaging like Record 700 to 800 of 75,000)
265
- const tmpOffset = typeof(tmpRecordDashboardData.Offset) === 'number' ? tmpRecordDashboardData.Offset : parseInt(tmpRecordDashboardData.Offset);
286
+ const tmpOffset = Number(tmpRecordDashboardData.Offset);
266
287
  tmpRecordDashboardData.PageEnd = tmpOffset + tmpRecordDashboardData.Records.Records.length;
267
288
 
268
289
  // Compute the number of pages total
@@ -296,6 +317,10 @@ class viewRecordSetDashboard extends libPictRecordSetRecordView
296
317
  URL: `#/PSRS/${tmpRecordDashboardData.RecordSet}/SpecificDashboard/${pDashboardHash}/${i * tmpRecordDashboardData.PageSize}/${tmpRecordDashboardData.PageSize}`
297
318
  });
298
319
  }
320
+ if (tmpEncodedFilterExperience)
321
+ {
322
+ tmpRecordDashboardData.PageLinks[tmpRecordDashboardData.PageLinks.length - 1].URL += `/FilterExperience/${tmpEncodedFilterExperience}`;
323
+ }
299
324
  }
300
325
 
301
326
  //FIXME: short-term workaround to not blow up the tempplate rendering with way too many links
@@ -322,6 +347,10 @@ class viewRecordSetDashboard extends libPictRecordSetRecordView
322
347
  URL: `#/PSRS/${tmpRecordDashboardData.RecordSet}/SpecificDashboard/${pDashboardHash}/0/${tmpRecordDashboardData.PageSize}`
323
348
  });
324
349
  }
350
+ if (tmpEncodedFilterExperience)
351
+ {
352
+ tmpRecordDashboardData.PageLinksLimited[tmpRecordDashboardData.PageLinksLimited.length - 1].URL += `/FilterExperience/${tmpEncodedFilterExperience}`;
353
+ }
325
354
  }
326
355
  if (linkRangeEnd < tmpRecordDashboardData.PageLinks.length)
327
356
  {
@@ -343,6 +372,10 @@ class viewRecordSetDashboard extends libPictRecordSetRecordView
343
372
  URL: `#/PSRS/${tmpRecordDashboardData.RecordSet}/SpecificDashboard/${pDashboardHash}/${(tmpRecordDashboardData.PageCount - 1) * tmpRecordDashboardData.PageSize}/${tmpRecordDashboardData.PageSize}`
344
373
  });
345
374
  }
375
+ if (tmpEncodedFilterExperience)
376
+ {
377
+ tmpRecordDashboardData.PageLinksLimited[tmpRecordDashboardData.PageLinksLimited.length - 1].URL += `/FilterExperience/${tmpEncodedFilterExperience}`;
378
+ }
346
379
  }
347
380
 
348
381
  tmpRecordDashboardData.PageLinkBookmarks.Previous = tmpRecordDashboardData.PageLinkBookmarks.Current - 1;
@@ -458,12 +491,13 @@ class viewRecordSetDashboard extends libPictRecordSetRecordView
458
491
  * @param {Record<string, any>} pRecordSetConfiguration
459
492
  * @param {string} pProviderHash
460
493
  * @param {string} pFilterString
494
+ * @param {string} pSerializedFilterExperience
461
495
  * @param {number} pOffset
462
496
  * @param {number} pPageSize
463
497
  *
464
498
  * @return {Promise<void>}
465
499
  */
466
- async renderDashboard(pRecordSetConfiguration, pProviderHash, pFilterString, pOffset, pPageSize)
500
+ async renderDashboard(pRecordSetConfiguration, pProviderHash, pFilterString, pSerializedFilterExperience, pOffset, pPageSize)
467
501
  {
468
502
  if (!pRecordSetConfiguration)
469
503
  {
@@ -486,7 +520,18 @@ class viewRecordSetDashboard extends libPictRecordSetRecordView
486
520
  }
487
521
  else
488
522
  {
489
- return this.renderSpecificDashboard(tmpManifestHash, pRecordSetConfiguration, pProviderHash, pFilterString, pOffset, pPageSize);
523
+ return this.renderSpecificDashboard(tmpManifestHash, pRecordSetConfiguration, pProviderHash, pFilterString, pSerializedFilterExperience, pOffset, pPageSize);
524
+ }
525
+ }
526
+
527
+ const tmpEncodedFilterExperience = pSerializedFilterExperience && encodeURIComponent(pSerializedFilterExperience);
528
+ if (tmpEncodedFilterExperience)
529
+ {
530
+ // shove filter xp into the active filters for this recordset
531
+ const tmpExperienceFromURL = await this.pict.views['PRSP-Filters'].deserializeFilterExperience(pSerializedFilterExperience);
532
+ if (tmpExperienceFromURL)
533
+ {
534
+ this.pict.manifest.setValueByHash(this.pict.Bundle, `_ActiveFilterState[${pRecordSetConfiguration.RecordSet}].FilterClauses`, tmpExperienceFromURL);
490
535
  }
491
536
  }
492
537
 
@@ -525,7 +570,7 @@ class viewRecordSetDashboard extends libPictRecordSetRecordView
525
570
  tmpRecordDashboardData.GUIDAddress = `GUID${this.pict.providers[pProviderHash].options.Entity}`;
526
571
 
527
572
  // Get the "page end record number" for the current page (e.g. for messaging like Record 700 to 800 of 75,000)
528
- const tmpOffset = typeof(tmpRecordDashboardData.Offset) === 'number' ? tmpRecordDashboardData.Offset : parseInt(tmpRecordDashboardData.Offset);
573
+ const tmpOffset = Number(tmpRecordDashboardData.Offset);
529
574
  tmpRecordDashboardData.PageEnd = tmpOffset + tmpRecordDashboardData.Records.Records.length;
530
575
 
531
576
  // Compute the number of pages total
@@ -559,6 +604,10 @@ class viewRecordSetDashboard extends libPictRecordSetRecordView
559
604
  URL: `#/PSRS/${tmpRecordDashboardData.RecordSet}/Dashboard/${i * tmpRecordDashboardData.PageSize}/${tmpRecordDashboardData.PageSize}`
560
605
  });
561
606
  }
607
+ if (tmpEncodedFilterExperience)
608
+ {
609
+ tmpRecordDashboardData.PageLinks[tmpRecordDashboardData.PageLinks.length - 1].URL += `/FilterExperience/${tmpEncodedFilterExperience}`;
610
+ }
562
611
  }
563
612
 
564
613
  //FIXME: short-term workaround to not blow up the tempplate rendering with way too many links
@@ -585,6 +634,10 @@ class viewRecordSetDashboard extends libPictRecordSetRecordView
585
634
  URL: `#/PSRS/${tmpRecordDashboardData.RecordSet}/Dashboard/${0}/${tmpRecordDashboardData.PageSize}`
586
635
  });
587
636
  }
637
+ if (tmpEncodedFilterExperience)
638
+ {
639
+ tmpRecordDashboardData.PageLinksLimited[tmpRecordDashboardData.PageLinksLimited.length - 1].URL += `/FilterExperience/${tmpEncodedFilterExperience}`;
640
+ }
588
641
  }
589
642
  if (linkRangeEnd < tmpRecordDashboardData.PageLinks.length)
590
643
  {
@@ -606,6 +659,10 @@ class viewRecordSetDashboard extends libPictRecordSetRecordView
606
659
  URL: `#/PSRS/${tmpRecordDashboardData.RecordSet}/Dashboard/${(tmpRecordDashboardData.PageCount - 1) * tmpRecordDashboardData.PageSize}/${tmpRecordDashboardData.PageSize}`
607
660
  });
608
661
  }
662
+ if (tmpEncodedFilterExperience)
663
+ {
664
+ tmpRecordDashboardData.PageLinksLimited[tmpRecordDashboardData.PageLinksLimited.length - 1].URL += `/FilterExperience/${tmpEncodedFilterExperience}`;
665
+ }
609
666
  }
610
667
 
611
668
  tmpRecordDashboardData.PageLinkBookmarks.Previous = tmpRecordDashboardData.PageLinkBookmarks.Current - 1;
@@ -0,0 +1,38 @@
1
+
2
+ const ViewRecordSetSUBSETFilterBase = require('./RecordSet-Filter-Base');
3
+
4
+ class ViewRecordSetSUBSETFilterBaseRange extends ViewRecordSetSUBSETFilterBase
5
+ {
6
+ constructor(pFable, pOptions, pServiceHash)
7
+ {
8
+ super(pFable, pOptions, pServiceHash);
9
+ }
10
+
11
+ /**
12
+ * @param {Record<string, any>} pRecord
13
+ */
14
+ prepareRecord(pRecord)
15
+ {
16
+ super.prepareRecord(pRecord);
17
+
18
+ pRecord.StartClauseAddress = pRecord.ClauseValuesAddress + '.Start';
19
+ pRecord.EndClauseAddress = pRecord.ClauseValuesAddress + '.End';
20
+
21
+ pRecord.StartClauseDescriptor =
22
+ {
23
+ Address: pRecord.StartClauseAddress,
24
+ //TODO: figure out a nice pattern for extracting a name for the field from the filter - and allow the filter author to provide the label here
25
+ Name: pRecord.MinimumLabel || `Minimum ${pRecord.ExternalFilterByColumn || pRecord.ExternalFilterByColumns?.[0] || pRecord.FilterByColumn || pRecord.FilterByColumns?.[0] || 'Value'}`,
26
+ DataType: 'String',
27
+ };
28
+
29
+ pRecord.EndClauseDescriptor =
30
+ {
31
+ Address: pRecord.EndClauseAddress,
32
+ Name: pRecord.MaximumLabel || `Maximum ${pRecord.ExternalFilterByColumn || pRecord.ExternalFilterByColumns?.[0] || pRecord.FilterByColumn || pRecord.FilterByColumns?.[0] || 'Value'}`,
33
+ DataType: 'String',
34
+ };
35
+ }
36
+ }
37
+
38
+ module.exports = ViewRecordSetSUBSETFilterBaseRange;