pict-section-recordset 1.0.56 → 1.0.58

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 (37) hide show
  1. package/example_applications/README.md +25 -1
  2. package/package.json +11 -10
  3. package/source/application/Pict-Application-RecordSet.js +6 -0
  4. package/source/providers/Filter-Data-Provider.js +591 -186
  5. package/source/providers/RecordSet-RecordProvider-MeadowEndpoints.js +1 -1
  6. package/source/views/Filter-PersistenceView.js +394 -0
  7. package/source/views/RecordSet-Filters.js +99 -28
  8. package/source/views/dashboard/RecordSet-Dashboard.js +1 -1
  9. package/source/views/list/RecordSet-List.js +1 -1
  10. package/test/PictSectionRecordSet-Basic_tests.js +39 -16
  11. package/test/PictSectionRecordSet-Filter-Data-Provider_tests.js +263 -0
  12. package/test/PictSectionRecordSet-RecordProvider-Meadow_tests.js +5 -3
  13. package/types/application/Pict-Application-RecordSet.d.ts.map +1 -1
  14. package/types/providers/Filter-Data-Provider.d.ts +225 -52
  15. package/types/providers/Filter-Data-Provider.d.ts.map +1 -1
  16. package/types/views/Filter-PersistenceView.d.ts +104 -0
  17. package/types/views/Filter-PersistenceView.d.ts.map +1 -0
  18. package/types/views/RecordSet-Filters.d.ts +43 -10
  19. package/types/views/RecordSet-Filters.d.ts.map +1 -1
  20. package/types/views/filters/RecordSet-Filter-Base-Range.d.ts.map +1 -1
  21. package/types/views/filters/RecordSet-Filter-Base.d.ts.map +1 -1
  22. package/types/views/filters/RecordSet-Filter-DateMatch.d.ts.map +1 -1
  23. package/types/views/filters/RecordSet-Filter-DateRange.d.ts.map +1 -1
  24. package/types/views/filters/RecordSet-Filter-ExternalJoinDateMatch.d.ts.map +1 -1
  25. package/types/views/filters/RecordSet-Filter-ExternalJoinDateRange.d.ts.map +1 -1
  26. package/types/views/filters/RecordSet-Filter-ExternalJoinNumericMatch.d.ts.map +1 -1
  27. package/types/views/filters/RecordSet-Filter-ExternalJoinNumericRange.d.ts.map +1 -1
  28. package/types/views/filters/RecordSet-Filter-ExternalJoinSelectedValue.d.ts.map +1 -1
  29. package/types/views/filters/RecordSet-Filter-ExternalJoinSelectedValueList.d.ts.map +1 -1
  30. package/types/views/filters/RecordSet-Filter-InternalJoinDateMatch.d.ts.map +1 -1
  31. package/types/views/filters/RecordSet-Filter-InternalJoinDateRange.d.ts.map +1 -1
  32. package/types/views/filters/RecordSet-Filter-InternalJoinNumericMatch.d.ts.map +1 -1
  33. package/types/views/filters/RecordSet-Filter-InternalJoinNumericRange.d.ts.map +1 -1
  34. package/types/views/filters/RecordSet-Filter-InternalJoinSelectedValue.d.ts.map +1 -1
  35. package/types/views/filters/RecordSet-Filter-InternalJoinSelectedValueList.d.ts.map +1 -1
  36. package/types/views/filters/RecordSet-Filter-NumericMatch.d.ts.map +1 -1
  37. package/types/views/filters/RecordSet-Filter-NumericRange.d.ts.map +1 -1
@@ -3,11 +3,53 @@ const libPictProvider = require('pict-provider');
3
3
  const _DEFAULT_PROVIDER_CONFIGURATION =
4
4
  {
5
5
  ProviderIdentifier: 'FilterDataProvider',
6
-
7
6
  AutoInitialize: true,
8
7
  AutoInitializeOrdinal: 0,
9
8
  };
10
9
 
10
+ /** Terminology for Filter Data Provider (to avoid confusion):
11
+ * A "Record Set" is a collection of records that can be filtered.
12
+ * A "Filter Experience" is a saved state of filters for a given record set.
13
+ * A "Filter Experience Hash" is a unique identifier for a Filter Experience (display name converted to a hash).
14
+ * A "Filter Experience Encoded URL Param" is the URL-encoded representation of the filter state for a Filter Experience.
15
+ * A "Filter Display Name" is a user-friendly name for a Filter Experience to select/view in the UI.
16
+ * A "Filter Clauses" is a list of all saved filters in the "Filter Experience" for a given Record Set.
17
+ * A "Filter Meta" is the metadata associated with a Filter Experience, including the Filter Clauses and Filter Display Name.
18
+
19
+ * Behavior Summary:
20
+ * - Save Filter Meta to LocalStorage under a key derived from Record Set, View Context, and Filter Experience Hash.
21
+ * - Load Filter Meta from LocalStorage using the same key.
22
+ * - Remove Filter Meta from LocalStorage when requested.
23
+ * - List all Filter Experiences for a given Record Set by scanning LocalStorage keys.
24
+ * - Manage default and last used Filter Experiences for each Record Set and View Context. (last used takes priority over default on load, if the check is enabled)
25
+
26
+ * Storage Key Structure:
27
+ * - Filter_Meta_{RecordSet}_{ViewContext}_{FilterExperienceHash} : stores the Filter Meta JSON.
28
+
29
+ * Object Shape for Filter Meta (filter experience):
30
+ * {
31
+ * RecordSet: string, (auto-filled on save)
32
+ * ViewContext: string, (auto-filled on save)
33
+ * LastModifiedDate: string (ISO date) (auto-filled on save)
34
+ * FilterClauses: Array<{ Label: string, ExactMatch: boolean, Value: string }>,
35
+ * FilterDisplayName: string,
36
+ * FilterExperienceHash: string, (display name converted to hash)
37
+ * FilterExperienceEncodedURLParam: string, (URL-encoded filter state)
38
+
39
+ * Object Shape for Filter Experience Settings:
40
+ * {
41
+ * ExcludedFromSelection: boolean,
42
+ * RememberLastUsedFilterExperience: boolean,
43
+ * LastUsedFilterExperienceHash: string | null,
44
+ * LastUsedFilterExperienceURLParam: string | null,
45
+ * DefaultFilterExperienceHash: string | null,
46
+ * DefaultFilterExperienceURLParam: string | null,
47
+ * FallbackDefaultExperienceURLParam: string | null,
48
+ * }
49
+ */
50
+
51
+ // TODO: would nice to convert the comments above to actual types for better clarity and enforcement, but could use help on how to do that.
52
+
11
53
  class FilterDataProvider extends libPictProvider
12
54
  {
13
55
  /**
@@ -27,309 +69,673 @@ class FilterDataProvider extends libPictProvider
27
69
  {
28
70
  this.storageProvider = window.localStorage;
29
71
  }
30
-
31
- this.filtersMap = { };
32
- this.lastFilterExperienceHashMap = { };
33
72
  }
34
73
 
35
74
  onBeforeInitialize()
36
75
  {
37
- this.loadFilters();
38
-
39
76
  return super.onBeforeInitialize();
40
77
  }
41
78
 
79
+ /** ===== UTILITY for Filter Experience ============= */
80
+
81
+ /**
82
+ * Using the information in the FilterClauses, try to generate a contextual default filter name for the display name of the current experience.
83
+ *
84
+ * @param {object} pFilterExperience - The filter experience to generate the default filter name for
85
+ * @param {string} [pRecordSet] - The current record set
86
+ * @param {string} [pViewContext] - The current view context
87
+ * @return {string} - The generated default filter name
88
+ */
89
+ generateContextualDefaultFilterName(pFilterExperience, pRecordSet, pViewContext)
90
+ {
91
+ // if there is a display name, use that
92
+ if (pFilterExperience && pFilterExperience.FilterDisplayName && pFilterExperience.FilterDisplayName.length > 0)
93
+ {
94
+ return pFilterExperience.FilterDisplayName;
95
+ }
96
+ // otherwise, generate one based on the clauses
97
+ const tmpRecordSet = pFilterExperience?.RecordSet || pRecordSet || '';
98
+ const tmpClauses = pFilterExperience?.FilterClauses || this.pict.Bundle._ActiveFilterState[tmpRecordSet]?.FilterClauses || [];
99
+ if (tmpClauses && tmpClauses.length > 0)
100
+ {
101
+ const clauseSummaries = tmpClauses.map((clause) => {
102
+ return `${clause.Label || clause.FilterByColumn} ${clause.ExactMatch ? 'IS' : 'CONTAINS'} ${clause.Value || '(unset)'}`;
103
+ });
104
+ return `${clauseSummaries.join(' AND ')}`;
105
+ }
106
+ // give up, fall back to default name
107
+ return `New ${pViewContext} Filter`;
108
+ }
109
+
42
110
  /**
43
- * @return {void}
111
+ * Re-render all views affected by a filter change.
112
+ * @param {object} tmpFilterExperience - The filter meta record that was changed/added.
113
+ * @param {string} pRecordSet - The record set to check.
114
+ * @param {string} pViewContext - The current view context
44
115
  */
45
- loadFilters()
116
+ navigateToFilterExperienceRoute(tmpFilterExperience, pRecordSet, pViewContext)
46
117
  {
47
- // load all known recordsets
118
+ // go to the new url with the filter experience encoded param
119
+ if (tmpFilterExperience?.FilterExperienceEncodedURLParam && tmpFilterExperience?.FilterExperienceEncodedURLParam?.length > 0)
120
+ {
121
+ this.fable.providers.RecordSetRouter.pictRouter.navigate(`/PSRS/${pRecordSet}/${pViewContext}/FilterExperience/${tmpFilterExperience.FilterExperienceEncodedURLParam}`);
122
+ }
123
+ else
124
+ {
125
+ console.info('No FilterExperienceEncodedURLParam found for the current filter experience; navigating to base record set URL.');
126
+ this.fable.providers.RecordSetRouter.pictRouter.navigate(`/PSRS/${pRecordSet}/${pViewContext}`);
127
+ }
48
128
  }
49
129
 
50
130
  /**
51
- * List all available Filters (from the Filter Meta data)
52
- *
53
- * @param {string} pRecordSet - the record set to list the filters for
54
- *
55
- * @return Array<Record<string, any>> - a list of Filters as Index/FilterExperienceHash entries
131
+ * Apply the expected filter experience to load on application load for a given record set and view context. Last Used takes priority over Default.
132
+ * This is the main entry point to set the default/latest filter experience on app load for a given record set and view context.
133
+ * @param {string} pRecordSet - The record set to set the default filter experience for
134
+ * @param {string} pViewContext - The current view context
135
+ * @return {boolean} - Returns true when the default filter experience has been set
56
136
  */
57
- listFilters(pRecordSet)
137
+ applyExpectedFilterExperience(pRecordSet, pViewContext)
58
138
  {
59
- this.loadFilterMeta(pRecordSet);
60
- const filtersList = this.filtersMap[pRecordSet] || [];
61
- const tmpFilterList = filtersList.map((pValue, pIndex) => { return { Index: pIndex, RecordSet: pRecordSet, FilterExperienceHash: pValue }; });
62
- return tmpFilterList;
139
+ const applyLastUsed = this.getRememberLastUsedFilterExperience(pRecordSet, pViewContext);
140
+ if (applyLastUsed)
141
+ {
142
+ // first try to set last used filter experience as default on load
143
+ const tmpLastUsedFilterExperience = this.getLastUsedFilterExperience(pRecordSet, pViewContext);
144
+ if (tmpLastUsedFilterExperience)
145
+ {
146
+ this.pict.log.info(`Applying last used filter experience on load for record set: ${pRecordSet} with view context: ${pViewContext}`);
147
+ // if we are already on the last used filter experience by URL, skip loading it again
148
+ if (this.isCurrentFilterExperience(pRecordSet, pViewContext, tmpLastUsedFilterExperience.FilterExperienceHash))
149
+ {
150
+ return true;
151
+ }
152
+ this.loadFilterMeta(pRecordSet, pViewContext, tmpLastUsedFilterExperience.FilterExperienceHash, true);
153
+ return true;
154
+ }
155
+ }
156
+ // if no last used filter experience, fall back to default filter experience on load
157
+ const tmpDefaultFilterExperience = this.getDefaultFilterExperience(pRecordSet, pViewContext);
158
+ if (tmpDefaultFilterExperience)
159
+ {
160
+ this.pict.log.info(`Applying default filter experience on load for record set: ${pRecordSet} with view context: ${pViewContext}`);
161
+ // if we are already on the default filter experience by URL, skip loading it again
162
+ if (this.isCurrentFilterExperience(pRecordSet, pViewContext, tmpDefaultFilterExperience.FilterExperienceHash))
163
+ {
164
+ return true;
165
+ }
166
+ this.loadFilterMeta(pRecordSet, pViewContext, tmpDefaultFilterExperience.FilterExperienceHash, true);
167
+ return true;
168
+ }
169
+ // finally, check for a fallback default experience URL param to load (could be server/customer provided)
170
+ const tmpFallbackDefaultExperienceURLParam = this.getFallbackDefaultFilterExperienceSettings(pRecordSet, pViewContext);
171
+ if (tmpFallbackDefaultExperienceURLParam)
172
+ {
173
+ if (tmpFallbackDefaultExperienceURLParam && tmpFallbackDefaultExperienceURLParam.length > 0)
174
+ {
175
+ this.pict.log.info(`Applying fallback default filter experience URL param on load for record set: ${pRecordSet} with view context: ${pViewContext}`);
176
+ this.fable.providers.RecordSetRouter.pictRouter.navigate(`/PSRS/${pRecordSet}/${pViewContext}/FilterExperience/${tmpFallbackDefaultExperienceURLParam}`);
177
+ return true;
178
+ }
179
+ }
180
+ // no filter experience to apply
181
+ return false;
63
182
  }
64
183
 
184
+ /** ===== CRUD Filter Experiences ============= */
185
+
65
186
  /**
66
- * @param {string} pRecordSet - the record set to get the active filter experience for
67
- *
68
- * @return {Record<string, any>} - the active filter experience for the given record set
187
+ * Initialize the filter experience settings for a given record set and view context if they do not already exist.
188
+ * @param {string} pRecordSet - The record set to initialize the settings for
189
+ * @param {string} pViewContext - The current view context
190
+ * @param {object} [pAdditionalSettings] - Additional settings to initialize with (pass through for future use)
191
+ * @return {string} - The filter experience settings object stringifyed (as if it was just read from storage).
69
192
  */
70
- getActiveFilterExperience(pRecordSet)
193
+ initializeFilterExperienceSettings(pRecordSet, pViewContext, pAdditionalSettings)
71
194
  {
72
- return this.pict.Bundle._ActiveFilterState?.[pRecordSet];
195
+ for (const recordSet in this.storageProvider)
196
+ {
197
+ if (recordSet.startsWith(`Filter_Meta_${pRecordSet}_${pViewContext}_` ) && recordSet.endsWith(`_SETTINGS`))
198
+ {
199
+ return this.storageProvider.getItem(recordSet);
200
+ }
201
+ }
202
+ const defaultSettings = {
203
+ ExcludedFromSelection: true, // this one is excluded from the selection list (it's just settings, not a real filter experience)
204
+ RememberLastUsedFilterExperience: false,
205
+ LastUsedFilterExperienceHash: null,
206
+ LastUsedFilterExperienceURLParam: null,
207
+ DefaultFilterExperienceHash: null,
208
+ DefaultFilterExperienceURLParam: null,
209
+ FallbackDefaultExperienceURLParam: null, // in case the default filter experience is deleted, what URL param to fall back to (could be a server/customer provided fallback)
210
+ };
211
+ // if additional settings were provided, merge them in
212
+ if (pAdditionalSettings && typeof(pAdditionalSettings) === 'object')
213
+ {
214
+ Object.assign(defaultSettings, pAdditionalSettings);
215
+ // ex: set 'FallbackDefaultExperienceURLParam' here for future use
216
+ }
217
+ // save the default settings to storage
218
+ this.storageProvider.setItem(`Filter_Meta_${pRecordSet}_${pViewContext}_SETTINGS`, JSON.stringify(defaultSettings));
219
+ return JSON.stringify(defaultSettings);
73
220
  }
74
221
 
75
222
  /**
76
- * Check if a particular scope is in use.
77
- *
78
- * @param {string} pRecordSet - the record set
79
- * @param {string} pFilterExperienceHash - the manyfest scope to check the existence of
80
- *
81
- * @return {boolean}
223
+ * Set options like Last Used / Default hashes, which are stored in the settings object for a given record set and view context.
224
+ * @param {string} pRecordSet - The record set to get the setting for
225
+ * @param {string} pViewContext - The current view context
226
+ * @param {object} pSettings - The settings to update
227
+ * @return {object} - The filter experience settings object
82
228
  */
83
- checkFilterExists(pRecordSet, pFilterExperienceHash)
229
+ updateFilterExperienceSettingsFromStorage(pRecordSet, pViewContext, pSettings)
84
230
  {
85
- const filterExperience = this.getActiveFilterExperience(pRecordSet);
86
- if (filterExperience && filterExperience.FilterExperienceHash === pFilterExperienceHash)
231
+ let tmpSavedSettings = this.storageProvider.getItem(`Filter_Meta_${pRecordSet}_${pViewContext}_SETTINGS`);
232
+ let tmpSettings = tmpSavedSettings ? JSON.parse(tmpSavedSettings) : {};
233
+ // if we've never saved settings before, initialize them first
234
+ if (!tmpSettings)
87
235
  {
88
- return true;
236
+ tmpSavedSettings = this.initializeFilterExperienceSettings(pRecordSet, pViewContext);
237
+ tmpSettings = tmpSavedSettings ? JSON.parse(tmpSavedSettings) : {};
89
238
  }
90
- // Make sure other tabs didn't do something funny.
91
- // Also. This means users can do FUNNY BUSINESS and mess with the state if they have a
92
- // crapton of tabs open and delete a manyfest in one tab and later this check happens.
93
- // Will not result in data loss but will result in flaky behavior.
94
- this.loadFilterMeta(pRecordSet, false);
95
- const filtersList = this.filtersMap[pRecordSet] || [];
96
- return filtersList.indexOf(pFilterExperienceHash) >= 0;
239
+ // always update the last modified date, recordSet, and viewContext for tracking
240
+ pSettings.RecordSet = pRecordSet ? pRecordSet : tmpSettings.RecordSet;
241
+ pSettings.ViewContext = pViewContext ? pViewContext : tmpSettings.ViewContext;
242
+ pSettings.LastModifiedDate = new Date().toISOString();
243
+ // merge in the new settings
244
+ tmpSettings = Object.assign({}, tmpSettings, pSettings);
245
+ this.storageProvider.setItem(`Filter_Meta_${pRecordSet}_${pViewContext}_SETTINGS`, JSON.stringify(tmpSettings));
246
+ return tmpSettings;
247
+ }
248
+
249
+ /**
250
+ * Get a single filter experience by its hash for a given record set and view context.
251
+ * @param {string} pRecordSet - The record set to get the filter experience for
252
+ * @param {string} pViewContext - The current view context
253
+ * @param {string} pFilterExperienceHash - The filter experience hash to get
254
+ * @return {object} - The filter experience object
255
+ */
256
+ getFilterExperienceByHash(pRecordSet, pViewContext, pFilterExperienceHash)
257
+ {
258
+ if (!pRecordSet || !pViewContext)
259
+ {
260
+ console.error('No record set or view context provided to get filter experience.');
261
+ return null;
262
+ }
263
+ if (!pFilterExperienceHash || pFilterExperienceHash.length === 0)
264
+ {
265
+ console.warn('No filter experience hash provided to get filter experience.');
266
+ return null;
267
+ }
268
+ const tmpKey = this.getFilterStorageKey(pRecordSet, pViewContext, pFilterExperienceHash);
269
+ const tmpFilterExperienceJSON = this.storageProvider.getItem(tmpKey);
270
+ let tmpFilterExperience = tmpFilterExperienceJSON ? JSON.parse(tmpFilterExperienceJSON) : null;
271
+ return tmpFilterExperience;
272
+ }
273
+
274
+ /**
275
+ * List all available filter experiences (from the Filter Meta data) for a given record set and return them as an array of filter meta objects.
276
+ * @param {string} pRecordSet - the record set to list the filter experiences for
277
+ * @param {string} pViewContext - the current view context
278
+ * @return {Array<object>} - An array of filter meta objects for the given record set.
279
+ */
280
+ getAllFiltersExperiencesForRecordSet(pRecordSet, pViewContext)
281
+ {
282
+ let tmpFilterExperiences = [];
283
+ for (const recordSet in this.storageProvider)
284
+ {
285
+ if (recordSet.startsWith(`Filter_Meta_${pRecordSet}_${pViewContext}_`))
286
+ {
287
+ const tmpFilterMetaJSON = this.storageProvider.getItem(recordSet);
288
+ if (tmpFilterMetaJSON)
289
+ {
290
+ const tmpFilterMeta = JSON.parse(tmpFilterMetaJSON);
291
+ tmpFilterExperiences.push(tmpFilterMeta);
292
+ }
293
+ }
294
+ }
295
+ return tmpFilterExperiences;
97
296
  }
98
297
 
99
298
  /**
100
299
  * Resolve a key in the LocalStorage keyspace for a filter experience for a given record set.
101
- *
102
300
  * @param {string} pRecordSet - The record set to resolve a key for
301
+ * @param {string} pViewContext - The current view context
103
302
  * @param {string} pFilterExperienceHash - The scope to resolve a key for
104
303
  *
105
304
  * @return {string} A string that points to the record.
106
305
  */
107
- getFilterStorageKey(pRecordSet, pFilterExperienceHash)
306
+ getFilterStorageKey(pRecordSet, pViewContext, pFilterExperienceHash)
108
307
  {
109
- this.loadFilterMeta(pRecordSet, false);
110
- // Default to the loaded manyfest if nothing is passed in.
111
- let tmpFilterExperienceHash = (typeof(pFilterExperienceHash) === 'string') ? pFilterExperienceHash : this.pict.AppData?.FilterRecord?.FilterExperienceHash ?? 'RECENT';
112
- return `${pRecordSet}_Filter_${tmpFilterExperienceHash}`;
308
+ let tmpFilterExperienceHash = (typeof(pFilterExperienceHash) === 'string') ? pFilterExperienceHash : null;
309
+ if (!tmpFilterExperienceHash || tmpFilterExperienceHash.length === 0)
310
+ {
311
+ console.error('No filter experience hash provided to resolve storage key.');
312
+ return '';
313
+ }
314
+ return `Filter_Meta_${pRecordSet}_${pViewContext}_${tmpFilterExperienceHash}`;
113
315
  }
114
316
 
115
317
  /**
116
- * Save the application metadata (list of Filters, last loaded FilterExperienceHash, etc.)
117
- *
118
- * @param {string} pRecordSet - The record set to save the filter for; TODO: should this have a default?
119
- * @param {boolean} [pRender=false] - Whether or not to also render the list of Filters in the UI automatically
318
+ * Check if a filter experience exists for a given record set and filter experience hash.
319
+ * @param {string} pRecordSet - The record set to check.
320
+ * @param {string} pViewContext - The current view context
321
+ * @param {string} pFilterExperienceHash - The filter experience hash to check.
322
+ * @return {boolean} - True if the filter experience exists, false otherwise.
120
323
  */
121
- saveFilterMeta(pRecordSet, pRender = false)
324
+ checkIfFilterExperienceExists(pRecordSet, pViewContext, pFilterExperienceHash)
122
325
  {
123
- const filtersList = this.filtersMap[pRecordSet] || [];
326
+ const tmpKey = this.getFilterStorageKey(pRecordSet, pViewContext, pFilterExperienceHash);
327
+ const tmpFilterMetaJSON = this.storageProvider.getItem(tmpKey);
328
+ return (tmpFilterMetaJSON !== null);
329
+ }
124
330
 
125
- // TODO: BUG: Gotta have a more complex merge happen here for multiple tabs
126
- this.storageProvider.setItem(`Filter_Meta_${pRecordSet}`, JSON.stringify({ LastFilterExperienceHash: this.lastFilterExperienceHashMap[pRecordSet] || 'RECENT', FilterList: filtersList }));
331
+ /**
332
+ * Save the application metadata (list of Filters, last loaded FilterExperienceHash, etc.)
333
+ * @param {string} pRecordSet - The record set to save the filter for;
334
+ * @param {string} pViewContext - The current view context
335
+ * @param {string} pFilterExperienceHash - The name of the filter to load
336
+ * @param {boolean} pSkipSaveAsLastUsed - Whether to skip saving this as the last used filter experience - useful when loading last used filter itself
337
+ * @return {boolean} - Returns true when the filter experience has been loaded.
338
+ */
339
+ loadFilterMeta(pRecordSet, pViewContext, pFilterExperienceHash, pSkipSaveAsLastUsed)
340
+ {
341
+ // We get this every time in case the user has multiple tabs open
342
+ const tmpKey = this.getFilterStorageKey(pRecordSet, pViewContext, pFilterExperienceHash);
343
+ const tmpFilterExperienceJSON = this.storageProvider.getItem(tmpKey);
344
+ let tmpFilterExperience = tmpFilterExperienceJSON ? JSON.parse(tmpFilterExperienceJSON) : null;
345
+ if (!tmpFilterExperience)
346
+ {
347
+ // check if this is the "last used" filter experience, since that one is special and we can have the URL param stored separately for unsaved searches
348
+ const tmpLastUsedAndUnsavedExperience = this.isLastUsedFilterExperienceHash(pRecordSet, pViewContext, pFilterExperienceHash);
349
+ if (tmpLastUsedAndUnsavedExperience)
350
+ {
351
+ const tmpLastUsedFilterExperience = this.getLastUsedFilterExperience(pRecordSet, pViewContext);
352
+ this.setCurrentFilterName(tmpLastUsedFilterExperience, pRecordSet, pViewContext, 'My Last Used Filter Experience');
353
+ this.navigateToFilterExperienceRoute(tmpLastUsedFilterExperience, pRecordSet, pViewContext);
354
+ return true;
355
+ }
127
356
 
128
- if (pRender && this.pict.views.FilterPersistenceView)
357
+ this.pict.log.warn(`No filter experience available to remove for record set: ${pRecordSet} with filter experience hash: ${pFilterExperienceHash}`);
358
+ return false;
359
+ }
360
+ // update the current filter name in the UI
361
+ this.setCurrentFilterName(tmpFilterExperience, pRecordSet, pViewContext);
362
+ // re-render views if needed
363
+ this.navigateToFilterExperienceRoute(tmpFilterExperience, pRecordSet, pViewContext);
364
+ // update the last used filter experience
365
+ if (!pSkipSaveAsLastUsed)
129
366
  {
130
- this.pict.views.FilterPersistenceView.render()
367
+ this.setLastUsedFilterExperience(tmpFilterExperience, pRecordSet, pViewContext);
131
368
  }
132
369
 
133
370
  return true;
134
371
  }
135
372
 
136
373
  /**
137
- * Save the application metadata (list of Filters, last loaded FilterExperienceHash, etc.)
138
- *
139
- * @param {string} pRecordSet - The record set to save the filter for; TODO: should this have a default?
140
- * @param {boolean} [pRender=false] - Whether or not to also render the list of Filters in the UI automatically
141
- *
142
- * @return {Array<object>} The list of available Filters.
374
+ * Remove a filter meta from storage for a given record set and filter experience hash.
375
+ * @param {string} pRecordSet - The record set to remove the filter for
376
+ * @param {string} pViewContext - The current view context
377
+ * @param {string} pFilterExperienceHash - The filter experience hash to remove
378
+ * @return {boolean} - Returns true when the filter meta has been removed.
143
379
  */
144
- loadFilterMeta(pRecordSet, pRender = false)
380
+ removeFilterMeta(pRecordSet, pViewContext, pFilterExperienceHash)
145
381
  {
146
- // We get this every time in case the user has multiple tabs open
147
- let tmpFilterMetaJSON = this.storageProvider.getItem(`Filter_Meta_${pRecordSet}`);
148
- if (!tmpFilterMetaJSON)
382
+ // TODO: add confirmation dialog in the UI before removing?
383
+ const tmpKey = this.getFilterStorageKey(pRecordSet, pViewContext, pFilterExperienceHash);
384
+ const tmpFilterExperienceJSON = this.storageProvider.getItem(tmpKey);
385
+ let tmpFilterExperience = tmpFilterExperienceJSON ? JSON.parse(tmpFilterExperienceJSON) : null;
386
+ if (!tmpFilterExperience)
149
387
  {
150
- tmpFilterMetaJSON = '{}';
388
+ this.pict.log.warn(`No filter experience available to remove for record set: ${pRecordSet} with filter experience hash: ${pFilterExperienceHash}`);
389
+ return false;
151
390
  }
152
- let tmpFilterMeta = JSON.parse(tmpFilterMetaJSON)
153
-
154
- let filtersList = tmpFilterMeta.FilterList;
155
- this.filtersMap[pRecordSet] = filtersList;
156
- this.lastFilterExperienceHashMap[pRecordSet] = tmpFilterMeta.LastFilterExperienceHash;
157
-
158
- if (!Array.isArray(filtersList))
391
+ // check if the filter being removed is set as last used or default; if so, clear those settings too
392
+ if (this.isLastUsedFilterExperienceHash(pRecordSet, pViewContext, pFilterExperienceHash))
159
393
  {
160
- this.filtersMap[pRecordSet] = [];
161
- //this.lastFilterExperienceHashMap[pRecordSet] = 'LATEST';
162
- this.saveFilterMeta(pRecordSet)
394
+ this.pict.log.info(`The filter experience being removed is set as the last used filter experience. Clearing last used filter experience settings.`);
395
+ this.updateFilterExperienceSettingsFromStorage(pRecordSet, pViewContext, { LastUsedFilterExperienceHash: null, LastUsedFilterExperienceURLParam: null });
163
396
  }
164
-
165
- if (pRender && this.pict.views.FilterPersistenceView)
397
+ if (this.isDefaultFilterExperience(pRecordSet, pViewContext, pFilterExperienceHash))
398
+ {
399
+ this.pict.log.info(`The filter experience being removed is set as the default filter experience. Clearing default filter experience settings.`);
400
+ this.updateFilterExperienceSettingsFromStorage(pRecordSet, pViewContext, { DefaultFilterExperienceHash: null, DefaultFilterExperienceURLParam: null });
401
+ }
402
+ if (this.isCurrentFilterExperience(pRecordSet, pViewContext, pFilterExperienceHash))
166
403
  {
167
- this.pict.views.FilterPersistenceView.render()
404
+ this.pict.log.info(`The filter experience being removed is the current active filter. Navigating to base record set URL.`);
405
+ this.pict.views['PRSP-Filters'].handleReset(null, pRecordSet, pViewContext);
168
406
  }
407
+ // remove the filter meta from localStorage
408
+ this.storageProvider.removeItem(tmpKey);
409
+ // NOTE: I think we want to keep the last used filter experience even if the user deletes it from the list? (so they can undo)
169
410
 
170
- return filtersList;
411
+ return true;
171
412
  }
172
413
 
173
414
  /**
174
- * @param {string} pRecordSet - The record set to add the filter experience hash to
175
- * @param {string} pFilterExperienceHash - The filter experience hash to add
176
- * @param {boolean} [pRender=true] - Whether or not to also render the list of Filters in the UI automatically
177
- *
178
- * @return {boolean} True if the filter experience hash was added, false if it already exists.
415
+ * Save the application metadata (list of Filters, last loaded FilterExperienceHash, etc.)
416
+ * @param {string} pRecordSet - The record set to save the filter for; TODO: should this have a default?
417
+ * @param {string} pViewContext - The current view context
418
+ * @return {boolean} - Returns true when the settings have been saved.
179
419
  */
180
- addFilterExperienceHashToFilterList(pRecordSet, pFilterExperienceHash, pRender)
420
+ saveFilterMeta(pRecordSet, pViewContext)
181
421
  {
182
- let tmpRender = (typeof(pRender) === 'undefined') ? true : pRender;
183
-
184
- this.loadFilterMeta(pRecordSet);
422
+ const activeFilterExperienceClauses = this.pict.Bundle._ActiveFilterState[pRecordSet]?.FilterClauses || [];
423
+ const filterDisplayName = this.getCurrentFilterName({ FilterClauses: activeFilterExperienceClauses }, pRecordSet, pViewContext);
424
+ const tmpFilterExperienceHash = filterDisplayName.replace(/[^a-zA-Z0-9_-]/g, '');
185
425
 
186
- const tmpFilterList = this.filtersMap[pRecordSet] || [];
187
-
188
- let tmpFilterFilterExperienceHashIndex = tmpFilterList.indexOf(pFilterExperienceHash);
189
-
190
- if (tmpFilterFilterExperienceHashIndex >= 0)
426
+ if (this.checkIfFilterExperienceExists(pRecordSet, pViewContext, tmpFilterExperienceHash))
191
427
  {
192
- return false;
428
+ this.pict.log.info(`Filter experience with hash 'Filter_Meta_${pRecordSet}_${pViewContext}_${tmpFilterExperienceHash}' already exists for record set ${pRecordSet}. Overwriting.`);
429
+ // TODO: add a confirmation dialog in the UI and compare before overwriting?
193
430
  }
194
- const filtersList = this.filtersMap[pRecordSet] || [];
195
- filtersList.push(pFilterExperienceHash);
196
- this.filtersMap[pRecordSet] = filtersList;
197
431
 
198
- this.saveFilterMeta(pRecordSet);
199
-
200
- if (tmpRender && this.pict.views.FilterPersistenceView)
201
- {
202
- this.pict.views.FilterPersistenceView.render()
432
+ // TODO: BUG: Gotta have a more complex merge happen here for multiple tabs
433
+ const newFilterExperience = {
434
+ RecordSet: pRecordSet,
435
+ ViewContext: pViewContext,
436
+ FilterClauses: activeFilterExperienceClauses,
437
+ FilterDisplayName: filterDisplayName,
438
+ FilterExperienceHash: tmpFilterExperienceHash,
439
+ FilterExperienceEncodedURLParam: window.location.hash.split('/FilterExperience/')?.[1] || '',
440
+ LastModifiedDate: new Date().toISOString(),
203
441
  }
442
+ // Save the specific filter metadata to localStorage
443
+ this.storageProvider.setItem(`Filter_Meta_${pRecordSet}_${pViewContext}_${tmpFilterExperienceHash}`, JSON.stringify(newFilterExperience));
444
+ // Also set the last used filter experience (this one is reserved and continually updated)
445
+ this.setLastUsedFilterExperience(newFilterExperience, pRecordSet, pViewContext);
204
446
 
205
447
  return true;
206
448
  }
207
449
 
450
+ /** ===== LAST USED Filter Experience ============= */
451
+
208
452
  /**
209
- * @param {string} pRecordSet - The record set to remove the filter experience hash from
210
- * @param {string} pFilterExperienceHash - The filter experience hash to remove
211
- * @param {boolean} [pRender=true] - Whether or not to also render the list of Filters in the UI automatically
453
+ * Save the application metadata as the last used filter experience (continually updated).
454
+ * @param {object} pFilterExperience - The new filter experience object to save as last used
455
+ * @param {string} pRecordSet - The record set to save the filter for;
456
+ * @param {string} pViewContext - The current view context
457
+ * @return {boolean} - Returns true when the filter experience has been saved.
212
458
  */
213
- removeFilterExperienceHashFromFilterList(pRecordSet, pFilterExperienceHash, pRender)
459
+ setLastUsedFilterExperience(pFilterExperience, pRecordSet, pViewContext)
214
460
  {
215
- let tmpRender = (typeof(pRender) === 'undefined') ? true : pRender;
461
+ const activeFilterExperienceClauses = this.pict.Bundle._ActiveFilterState[pRecordSet]?.FilterClauses || [];
462
+ const filterDisplayName = this.getCurrentFilterName({ FilterClauses: activeFilterExperienceClauses }, pRecordSet, pViewContext);
463
+ const tmpFilterExperienceHash = filterDisplayName.replace(/[^a-zA-Z0-9_-]/g, '');
464
+
465
+ // NOTE: We probably don't want to save EVERY. SINGLE. SEARCH. so if no filter experience is provided (new unsaved search), we can just redirect to the url param rather than create a new saved filter experience
466
+ const filterMetaSettings = {
467
+ LastUsedFilterExperienceHash: pFilterExperience?.FilterExperienceHash || tmpFilterExperienceHash,
468
+ LastUsedFilterExperienceURLParam: pFilterExperience?.FilterExperienceEncodedURLParam || window.location.hash.split('/FilterExperience/')?.[1] || '',
469
+ }
470
+ this.updateFilterExperienceSettingsFromStorage(pRecordSet, pViewContext, filterMetaSettings);
216
471
 
217
- let tmpFilterList = this.loadFilterMeta(pRecordSet);
472
+ return true;
473
+ }
218
474
 
219
- let tmpFilterFilterExperienceHashIndex = tmpFilterList.indexOf(pFilterExperienceHash);
475
+ /**
476
+ * Remove the last used filter experience for a given record set and view context. (used on "Clear" action to get back to empty filter state)
477
+ * @param {string} pRecordSet - The record set to remove the last used filter experience for
478
+ * @param {string} pViewContext - The current view context
479
+ * @return {boolean} - Returns true when the last used filter experience has been removed.
480
+ */
481
+ removeLastUsedFilterExperience(pRecordSet, pViewContext)
482
+ {
483
+ this.updateFilterExperienceSettingsFromStorage(pRecordSet, pViewContext, { LastUsedFilterExperienceHash: null, LastUsedFilterExperienceURLParam: null });
484
+ return true;
485
+ }
220
486
 
221
- if (tmpFilterFilterExperienceHashIndex >= 0)
487
+ /**
488
+ * Get the last used filter experience for a given record set and view context.
489
+ * @param {string} pRecordSet - The record set to get the last used filter experience for
490
+ * @param {string} pViewContext - The current view context
491
+ * @return {object} - The last used filter experience (takes priority over default if both exist)
492
+ */
493
+ getLastUsedFilterExperience(pRecordSet, pViewContext)
494
+ {
495
+ const tmpSavedSettings = this.storageProvider.getItem(`Filter_Meta_${pRecordSet}_${pViewContext}_SETTINGS`);
496
+ if (!tmpSavedSettings)
222
497
  {
223
- // Could use array splice but meh
224
- let tmpNewFilterList = [];
498
+ return null;
499
+ }
500
+ const tmpSettings = JSON.parse(tmpSavedSettings);
501
+ if (!tmpSettings.RememberLastUsedFilterExperience)
502
+ {
503
+ return null;
504
+ }
505
+ const lastUsedFilterExperienceHash = tmpSettings.LastUsedFilterExperience;
506
+ const tmpKey = this.getFilterStorageKey(pRecordSet, pViewContext, lastUsedFilterExperienceHash);
507
+ const tmpFilterExperienceJSON = this.storageProvider.getItem(tmpKey);
508
+ let tmpFilterExperience = tmpFilterExperienceJSON ? JSON.parse(tmpFilterExperienceJSON) : null;
509
+ return tmpFilterExperience;
510
+ }
225
511
 
226
- for (let i = 0; i < tmpFilterList.length; i++)
227
- {
228
- if (tmpFilterList[i] != pFilterExperienceHash)
229
- {
230
- tmpNewFilterList.push(tmpFilterList[i]);
231
- }
232
- }
512
+ /**
513
+ * Check if the given filter experience is the last used filter experience for the given record set and view context.
514
+ * @param {string} pRecordSet - The record set to check
515
+ * @param {string} pViewContext - The current view context
516
+ * @param {string} pFilterExperienceHash - The filter experience hash to check
517
+ * @return {boolean} - True if the given filter experience is the last used filter experience, false otherwise
518
+ */
519
+ isLastUsedFilterExperienceHash(pRecordSet, pViewContext, pFilterExperienceHash)
520
+ {
521
+ const tmpLastUsedFilter = this.getLastUsedFilterExperience(pRecordSet, pViewContext);
522
+ if (!tmpLastUsedFilter)
523
+ {
524
+ return false;
525
+ }
526
+ return (tmpLastUsedFilter.FilterExperienceHash === pFilterExperienceHash);
527
+ }
233
528
 
234
- this.filtersMap[pRecordSet] = tmpNewFilterList;
529
+ /**
530
+ * Set whether to remember the last used filter experience across sessions.
531
+ * @param {string} pRecordSet - The record set to set the setting for
532
+ * @param {string} pViewContext - The current view context
533
+ * @param {boolean} pRemember - Whether to remember the last used filter experience
534
+ */
535
+ setRememberLastUsedFilterExperience(pRecordSet, pViewContext, pRemember)
536
+ {
537
+ this.updateFilterExperienceSettingsFromStorage(pRecordSet, pViewContext, { RememberLastUsedFilterExperience: pRemember });
538
+ return true;
539
+ }
235
540
 
236
- this.saveFilterMeta(pRecordSet, true);
541
+ /**
542
+ * Get whether to remember the last used filter experience across sessions.
543
+ * @param {string} pRecordSet - The record set to get the setting for
544
+ * @param {string} pViewContext - The current view context
545
+ * @return {boolean} - Whether to remember the last used filter experience
546
+ */
547
+ getRememberLastUsedFilterExperience(pRecordSet, pViewContext)
548
+ {
549
+ const tmpSavedSettings = this.storageProvider.getItem(`Filter_Meta_${pRecordSet}_${pViewContext}_SETTINGS`);
550
+ if (!tmpSavedSettings)
551
+ {
552
+ return null;
553
+ }
554
+ const tmpSettings = JSON.parse(tmpSavedSettings);
555
+ return tmpSettings.RememberLastUsedFilterExperience;
556
+ }
237
557
 
238
- if (tmpRender && this.pict.views.FilterPersistenceView)
239
- {
240
- this.pict.views.FilterPersistenceView.render()
241
- }
558
+ /** ===== CURRENT Filter Experience ============= */
242
559
 
560
+ /**
561
+ * Check if the given filter experience hash is the current active filter for the given record set.
562
+ * @param {string} pRecordSet - The record set to check.
563
+ * @param {string} pViewContext - The current view context.
564
+ * @param {string} pFilterExperienceHash - The filter experience hash to check.
565
+ * @return {boolean} - True if the given filter experience hash is the current active filter, false otherwise.
566
+ */
567
+ isCurrentFilterExperience(pRecordSet, pViewContext, pFilterExperienceHash)
568
+ {
569
+ // NOTE: Could be an confusing issue if we have the same URL param with different display names
570
+ const tmpExperienceURLParam = window.location.hash.split('/FilterExperience/')?.[1] || '';
571
+ // look in the map for the filter experience with the given hash
572
+ const filterExperiences = this.getAllFiltersExperiencesForRecordSet(pRecordSet, pViewContext) || [];
573
+ const matchingExperience = filterExperiences.find((pExperience) => pExperience.FilterExperienceHash === pFilterExperienceHash);
574
+ if (matchingExperience && (matchingExperience.FilterExperienceEncodedURLParam === tmpExperienceURLParam))
575
+ {
243
576
  return true;
244
577
  }
245
-
246
578
  return false;
247
579
  }
248
-
580
+
249
581
  /**
250
- * @param {string} pRecordSet - The record set to create a new filter for
251
- * @param {string} [pFilterExperienceHash] - The filter experience hash to create a new filter for; if not provided, a new one will be generated
582
+ * Get the current filter name from the UI input (or generate a default one)
583
+ * @param {object} pFilterExperience - The filter experience to get the current filter name for
584
+ * @param {string} pRecordSet - The record set to get the filter name for
585
+ * @param {string} pViewContext - The current view context
586
+ * @return {string} - The current filter name
252
587
  */
253
- newFilter(pRecordSet, pFilterExperienceHash)
588
+ getCurrentFilterName(pFilterExperience, pRecordSet, pViewContext)
254
589
  {
255
- let tmpFilterExperienceHash = ((typeof(pFilterExperienceHash) === 'string') && (pFilterExperienceHash.length > 0)) ? pFilterExperienceHash : false;
256
-
257
- if (!tmpFilterExperienceHash)
590
+ /* @type {HTMLInputElement} */
591
+ const tmpValue = this.pict.ContentAssignment.readContent('#FilterPersistenceView-CurrentFilterNameInput');
592
+ if (tmpValue && tmpValue.trim().length > 0)
258
593
  {
259
- // Autogenerate a scope
260
- const filtersList = this.filtersMap[pRecordSet] || [];
261
- let tmpProspectiveIndex = filtersList.length;
594
+ return tmpValue.trim();
595
+ }
596
+ return this.generateContextualDefaultFilterName(pFilterExperience, pRecordSet, pViewContext);
597
+ }
262
598
 
263
- // If a user has more than 10,000 manifests we need to talk. In person.
264
- for (let i = 0; i < 10000; i++)
265
- {
266
- let tmpFilterFilterExperienceHash = `New Manifest ${tmpProspectiveIndex}`;
599
+ /**
600
+ * Set the current filter name in the UI input
601
+ * @param {object} [pFilterExperience] - The filter experience to set the current filter name for
602
+ * @param {string} [pRecordSet] - The record set to set the filter name for
603
+ * @param {string} [pViewContext] - The current view context
604
+ * @param {string} [pNewName] - The new name to set
605
+ * @return {boolean} - Returns true when the name has been set
606
+ */
607
+ setCurrentFilterName(pFilterExperience, pRecordSet, pViewContext, pNewName)
608
+ {
609
+ const tmpDisplayName = pNewName || this.generateContextualDefaultFilterName(pFilterExperience, pRecordSet, pViewContext);
610
+ this.pict.ContentAssignment.assignContent('#FilterPersistenceView-CurrentFilterNameInput', tmpDisplayName);
611
+ return true;
612
+ }
267
613
 
268
- if (!this.checkFilterExists(pRecordSet, tmpFilterFilterExperienceHash))
269
- {
270
- tmpFilterExperienceHash = tmpFilterFilterExperienceHash;
271
- break;
272
- }
273
- }
614
+ /** ===== DEFAULT Filter Experience ============= */
615
+
616
+ /**
617
+ * Set the default filter experience to load on application load for a given record set and filter experience hash.
618
+ * @param {string} pRecordSet - The record set to set the default filter experience for
619
+ * @param {string} pViewContext - The current view context
620
+ * @param {string} pFilterExperienceHash - The filter experience hash to set as default on load
621
+ * @param {boolean} pSetAsDefault - Whether to set as default or not
622
+ * @return {boolean} - Returns true when the default filter experience has been set
623
+ */
624
+ setDefaultFilterExperience(pRecordSet, pViewContext, pFilterExperienceHash, pSetAsDefault)
625
+ {
626
+ const tmpStorageKey = this.getFilterStorageKey(pRecordSet, pViewContext, pFilterExperienceHash);
627
+ const tmpFilterExperienceJSON = this.storageProvider.getItem(tmpStorageKey);
628
+ let tmpFilterExperience = tmpFilterExperienceJSON ? JSON.parse(tmpFilterExperienceJSON) : null;
629
+ if (!tmpFilterExperience)
630
+ {
631
+ this.pict.log.warn(`No filter experience available to set as default for record set: ${pRecordSet} with filter experience hash: ${pFilterExperienceHash}`);
632
+ return false;
274
633
  }
275
- else
634
+ // if already default, do nothing
635
+ if (pSetAsDefault && this.isDefaultFilterExperience(pRecordSet, pViewContext, tmpFilterExperience.FilterExperienceHash))
276
636
  {
277
- // Check to ensure the manyfest doesn't already exist.
278
- if (this.checkFilterExists(pRecordSet, tmpFilterExperienceHash))
279
- {
280
- this.log.warn(`Filter ${tmpFilterExperienceHash} already exists but it was explicitly requested by the user. Loading insted.`);
281
- return this.loadFilter(pRecordSet, tmpFilterExperienceHash);
282
- }
637
+ this.pict.log.info(`Filter experience 'Filter_Meta_${pRecordSet}_${pViewContext}_${pFilterExperienceHash}' is already set as default on load. No action taken.`);
638
+ return true;
283
639
  }
284
-
285
- // As far as I can tell this only happens if the user has more than 10,000 manifests
286
- if (!tmpFilterExperienceHash)
640
+ // if unsetting default, but not currently default, do nothing
641
+ if (!pSetAsDefault && !this.isDefaultFilterExperience(pRecordSet, pViewContext, tmpFilterExperience.FilterExperienceHash))
287
642
  {
288
- this.log.warn(`You have won the lottery. Seriously. Call us to learn more! Please email steven@velozo.com for more details.`);
289
- tmpFilterExperienceHash = 'LotteryWinner';
643
+ this.pict.log.info(`Filter experience 'Filter_Meta_${pRecordSet}_${pViewContext}_${pFilterExperienceHash}' is not currently set as default on load. No action taken.`);
644
+ return true;
290
645
  }
646
+ // update the settings
647
+ // NOTE: We may probably want an customer-based fallback is there user default settings are cleared. For now, we will just clear it until we know that pattern.
648
+ this.updateFilterExperienceSettingsFromStorage(pRecordSet, pViewContext, { DefaultFilterExperienceHash: pSetAsDefault ? pFilterExperienceHash : null });
649
+ return true;
650
+ }
291
651
 
292
- // Now create the new manyfest
293
- let tmpNewManifest = JSON.parse(JSON.stringify(this.options.DefaultManifest));
294
- tmpNewManifest.FilterExperienceHash = tmpFilterExperienceHash;
295
-
296
- // Now save it.
297
- this.storageProvider.setItem(this.getFilterStorageKey(pRecordSet, tmpFilterExperienceHash), JSON.stringify(tmpNewManifest));
298
- this.addFilterExperienceHashToFilterList(pRecordSet, tmpFilterExperienceHash, true);
299
-
300
- // TODO: Do we load it? Maaaaaaaybe. Figure out the "autosave before load" workflow.
652
+ /**
653
+ * Remove the default filter experience for a given record set and view context. (used on "Clear" action to get back to empty filter state)
654
+ * @param {string} pRecordSet - The record set to remove the default filter experience for
655
+ * @param {string} pViewContext - The current view context
656
+ * @return {boolean} - Returns true when the default filter experience has been removed.
657
+ */
658
+ removeDefaultFilterExperience(pRecordSet, pViewContext)
659
+ {
660
+ this.updateFilterExperienceSettingsFromStorage(pRecordSet, pViewContext, { DefaultFilterExperienceHash: null });
661
+ return true;
662
+ }
301
663
 
302
- this.loadFilter(pRecordSet, tmpFilterExperienceHash);
664
+ /**
665
+ * Get the default filter experience to load on application load for a given record set and view context.
666
+ * @param {string} pRecordSet - The record set to get the default filter experience for
667
+ * @param {string} pViewContext - The current view context
668
+ * @return {object} - The default filter experience to load on application load (used if no last used filter experience is found or they clear filters back to default)
669
+ */
670
+ getDefaultFilterExperience(pRecordSet, pViewContext)
671
+ {
672
+ const tmpSavedSettings = this.storageProvider.getItem(`Filter_Meta_${pRecordSet}_${pViewContext}_SETTINGS`);
673
+ if (!tmpSavedSettings)
674
+ {
675
+ return null;
676
+ }
677
+ const tmpSettings = JSON.parse(tmpSavedSettings);
678
+ const tmpExpectedHash = tmpSettings.DefaultFilterExperienceHash;
679
+ if (!tmpExpectedHash || tmpExpectedHash.length === 0)
680
+ {
681
+ return null;
682
+ }
683
+ // look up the filter experience by hash (it's expected all Default hashes are saved filter experiences)
684
+ return this.getFilterExperienceByHash(pRecordSet, pViewContext, tmpExpectedHash);
303
685
  }
304
686
 
305
687
  /**
306
- * @param {string} pRecordSet - The record set to save the filter for; TODO: should this have a default?
688
+ * Check if the given filter experience is the default filter experience to load on application load for the given record set and view context.
689
+ * @param {string} pRecordSet - The record set to check
690
+ * @param {string} pViewContext - The current view context
691
+ * @param {string} pFilterExperienceHash - The filter experience hash to check
692
+ * @return {boolean} - True if the given filter experience is the default filter experience on load, false otherwise
307
693
  */
308
- saveFilter(pRecordSet)
694
+ isDefaultFilterExperience(pRecordSet, pViewContext, pFilterExperienceHash)
309
695
  {
310
- let tmpFilterFilterExperienceHash = this.pict.AppData.FilterRecord.FilterExperienceHash;
311
- // TODO: Should this be a .... merge? Yikes. Multiple tabs is bonkers.
312
- this.storageProvider.setItem(this.getFilterStorageKey(pRecordSet, tmpFilterFilterExperienceHash), JSON.stringify(this.pict.AppData.FilterRecord));
313
- this.addFilterExperienceHashToFilterList(pRecordSet, tmpFilterFilterExperienceHash);
696
+ const tmpSavedSettings = this.storageProvider.getItem(`Filter_Meta_${pRecordSet}_${pViewContext}_SETTINGS`);
697
+ if (!tmpSavedSettings)
698
+ {
699
+ return false;
700
+ }
701
+ const tmpSettings = JSON.parse(tmpSavedSettings);
702
+ return (tmpSettings.DefaultFilterExperienceHash === pFilterExperienceHash);
314
703
  }
315
704
 
316
705
  /**
317
- * @param {string} pRecordSet - The record set to load the filter for
318
- * @param {string} pFilterFilterExperienceHash - The filter experience hash to load; if not provided, the last used one will be loaded
706
+ * Get the fallback default filter experience URL param to load on application load for a given record set and view context.
707
+ * @param {string} pRecordSet - The record set to get the fallback default filter experience for
708
+ * @param {string} pViewContext - The current view context
709
+ * @return {string|null} - The fallback default filter experience URL param to load on application load (could be server/customer provided)
319
710
  */
320
- loadFilter(pRecordSet, pFilterFilterExperienceHash)
711
+ getFallbackDefaultFilterExperienceSettings(pRecordSet, pViewContext)
321
712
  {
322
- let tmpFilterJSON = this.storageProvider.getItem(this.getFilterStorageKey(pRecordSet, pFilterFilterExperienceHash));
323
- if (tmpFilterJSON)
713
+ const tmpSavedSettings = this.storageProvider.getItem(`Filter_Meta_${pRecordSet}_${pViewContext}_SETTINGS`);
714
+ if (!tmpSavedSettings)
324
715
  {
325
- this.pict.AppData.FilterRecord = JSON.parse(tmpFilterJSON);
716
+ return null;
326
717
  }
327
- this.pict.providers.FilterRouter.postPersistNavigate();
718
+ const tmpSettings = JSON.parse(tmpSavedSettings);
719
+ return tmpSettings.FallbackDefaultExperienceURLParam || null;
720
+ }
721
+
722
+ /**
723
+ * Set the fallback default filter experience URL param to load on application load for a given record set and view context. Expected to be used for server/customer provided fallbacks.
724
+ * @param {string} pRecordSet - The record set to set the fallback default filter experience for
725
+ * @param {string} pViewContext - The current view context
726
+ * @param {string} pURLParam - The fallback default filter experience URL param to set
727
+ * @return {boolean} - Returns true when the fallback default filter experience URL param has been set
728
+ */
729
+ setFallbackDefaultFilterExperienceSettings(pRecordSet, pViewContext, pURLParam)
730
+ {
731
+ this.updateFilterExperienceSettingsFromStorage(pRecordSet, pViewContext, { FallbackDefaultExperienceURLParam: pURLParam });
732
+ return true;
328
733
  }
329
734
 
735
+ /** ===== SIMPLE KEY-VALUE CACHE ============= */
736
+
330
737
  /**
331
738
  * @param {string} pKey - The key to get from the cache
332
- *
333
739
  * @return {any} - The value associated with the key, or false if not found
334
740
  */
335
741
  getItem(pKey)
@@ -353,7 +759,6 @@ class FilterDataProvider extends libPictProvider
353
759
 
354
760
  /**
355
761
  * @param {string} pKey - The key to remove from the cache
356
- *
357
762
  * @return {boolean} - True if the item was removed, false if it was not found
358
763
  */
359
764
  removeItem(pKey)