pict-section-recordset 1.3.0 → 1.4.0

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 (21) hide show
  1. package/package.json +1 -1
  2. package/source/views/Filter-PersistenceView.js +5 -1
  3. package/source/views/RecordSet-Filters.js +51 -6
  4. package/source/views/filters/RecordSet-Filter-EntityReference-Base.js +291 -0
  5. package/source/views/filters/RecordSet-Filter-ExternalJoinSelectedValue.js +10 -256
  6. package/source/views/filters/RecordSet-Filter-ExternalJoinSelectedValueList.js +10 -263
  7. package/source/views/filters/RecordSet-Filter-InternalJoinSelectedValue.js +10 -256
  8. package/source/views/filters/RecordSet-Filter-InternalJoinSelectedValueList.js +10 -263
  9. package/types/views/Filter-PersistenceView.d.ts.map +1 -1
  10. package/types/views/RecordSet-Filters.d.ts +8 -0
  11. package/types/views/RecordSet-Filters.d.ts.map +1 -1
  12. package/types/views/filters/RecordSet-Filter-EntityReference-Base.d.ts +37 -0
  13. package/types/views/filters/RecordSet-Filter-EntityReference-Base.d.ts.map +1 -0
  14. package/types/views/filters/RecordSet-Filter-ExternalJoinSelectedValue.d.ts +13 -18
  15. package/types/views/filters/RecordSet-Filter-ExternalJoinSelectedValue.d.ts.map +1 -1
  16. package/types/views/filters/RecordSet-Filter-ExternalJoinSelectedValueList.d.ts +13 -18
  17. package/types/views/filters/RecordSet-Filter-ExternalJoinSelectedValueList.d.ts.map +1 -1
  18. package/types/views/filters/RecordSet-Filter-InternalJoinSelectedValue.d.ts +13 -18
  19. package/types/views/filters/RecordSet-Filter-InternalJoinSelectedValue.d.ts.map +1 -1
  20. package/types/views/filters/RecordSet-Filter-InternalJoinSelectedValueList.d.ts +13 -18
  21. package/types/views/filters/RecordSet-Filter-InternalJoinSelectedValueList.d.ts.map +1 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pict-section-recordset",
3
- "version": "1.3.0",
3
+ "version": "1.4.0",
4
4
  "description": "Pict dynamic record set management views",
5
5
  "main": "source/Pict-Section-RecordSet.js",
6
6
  "files": [
@@ -24,11 +24,15 @@ const _DEFAULT_CONFIGURATION_FilterPersistenceView = (
24
24
  CSS: /*css*/`
25
25
  .prsp-exp { display: flex; flex-direction: column; gap: 0.4rem; }
26
26
  .prsp-exp-label { font-size: 0.72rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.05em; color: var(--theme-color-text-muted, #6b7686); }
27
- .prsp-exp-row { display: flex; align-items: center; gap: 0.5rem; }
27
+ .prsp-exp-row { display: flex; align-items: center; flex-wrap: wrap; gap: 0.5rem; }
28
28
  .prsp-exp-row > span:empty { display: none; }
29
29
  .prsp-exp-select, .prsp-exp-name { font: inherit; font-size: 0.9rem; padding: 0.42rem 0.6rem; border-radius: 8px;
30
30
  border: 1px solid var(--theme-color-border-default, #d7dce3); background: var(--theme-color-background-primary, #fff); color: var(--theme-color-text-primary, #1f2733); }
31
31
  .prsp-exp-select { flex: 0 0 auto; width: 230px; min-width: 0; max-width: 100%; }
32
+ /* When a host layers select2 (or a similar widget) on the stored-filter <select>, its container
33
+ defaults to display:block at the row's full width, pushing Save/Delete onto a second line. Pin it to
34
+ a fixed-width inline flex item so the controls stay on one row. */
35
+ .prsp-exp-row .select2-container { flex: 0 0 auto !important; display: inline-block !important; width: 230px !important; max-width: 100%; vertical-align: middle; }
32
36
  .prsp-exp-select:focus, .prsp-exp-name:focus { outline: none; border-color: var(--theme-color-brand-primary, #156dd1);
33
37
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--theme-color-brand-primary, #156dd1) 16%, transparent); }
34
38
  .prsp-exp-btn { flex: 0 0 auto; white-space: nowrap; }
@@ -67,14 +67,19 @@ const _DEFAULT_CONFIGURATION_SUBSET_Filter =
67
67
  .prsp-filters-actions { flex: 0 0 auto; display: flex; align-items: center; gap: 0.5rem; }
68
68
 
69
69
  /* Module-owned "Add filter" popover (replaces the old native <select> pickers). */
70
- .prsp-addfilter-pop { position: absolute; z-index: 30; top: calc(100% + 0.35rem); left: 0; min-width: 250px; max-width: 340px; display: none; }
70
+ /* Fixed (viewport-anchored) + JS-positioned on open, so no ancestor overflow:hidden the filter card,
71
+ the slide-out drawer — can clip it, whatever the host's layout. */
72
+ .prsp-addfilter-pop { position: fixed; z-index: 30; min-width: 280px; max-width: 360px; display: none; }
71
73
  .prsp-addfilter-pop.open { display: block; }
72
- .prsp-addfilter-panel { background: var(--theme-color-background-panel, #fff); border: 1px solid var(--theme-color-border-default, #d7dce3);
74
+ /* Transparent full-viewport backdrop: catches outside clicks to close (no document listener). */
75
+ .prsp-addfilter-backdrop { position: fixed; inset: 0; z-index: 0; }
76
+ .prsp-addfilter-panel { position: relative; z-index: 1; display: flex; flex-direction: column; max-height: min(70vh, 460px);
77
+ background: var(--theme-color-background-panel, #fff); border: 1px solid var(--theme-color-border-default, #d7dce3);
73
78
  border-radius: 10px; box-shadow: 0 10px 28px rgba(17, 24, 39, 0.14); overflow: hidden; }
74
- .prsp-addfilter-search { display: flex; align-items: center; gap: 0.4rem; padding: 0.5rem 0.7rem; border-bottom: 1px solid var(--theme-color-border-light, #e8ebf0); }
79
+ .prsp-addfilter-search { flex: 0 0 auto; display: flex; align-items: center; gap: 0.4rem; padding: 0.5rem 0.7rem; border-bottom: 1px solid var(--theme-color-border-light, #e8ebf0); }
75
80
  .prsp-addfilter-search-ic { display: inline-flex; color: var(--theme-color-text-muted, #6b7686); font-size: 0.9rem; }
76
81
  .prsp-addfilter-search input { flex: 1 1 auto; min-width: 0; font: inherit; font-size: 0.9rem; border: none; outline: none; background: transparent; color: var(--theme-color-text-primary, #1f2733); }
77
- .prsp-addfilter-list { max-height: 280px; overflow-y: auto; }
82
+ .prsp-addfilter-list { flex: 1 1 auto; overflow-y: auto; }
78
83
  .prsp-addfilter-empty { padding: 0.7rem 0.8rem; color: var(--theme-color-text-muted, #6b7686); font-size: 0.86rem; }
79
84
  .prsp-addfilter-field { border-bottom: 1px solid var(--theme-color-border-light, #eef1f5); }
80
85
  .prsp-addfilter-field:last-child { border-bottom: none; }
@@ -109,7 +114,7 @@ const _DEFAULT_CONFIGURATION_SUBSET_Filter =
109
114
  </form>
110
115
  <div class="prsp-filters-drawer" id="PRSP_Filter_Drawer">
111
116
  <div class="prsp-filters-drawer-inner">
112
- <div id="PRSP_Filter_Instances" class="prsp-filters-clauses">
117
+ <div id="PRSP_Filter_Instances" class="prsp-filters-clauses" onkeydown="if (event.key === 'Enter' &amp;&amp; !event.target.closest('.pps')) { event.preventDefault(); _Pict.views['PRSP-Filters'].handleSearch(event, '{~D:Record.RecordSet~}', '{~D:Record.ViewContext~}'); }">
113
118
  {~FIV:Record~}
114
119
  </div>
115
120
  {~T:PRSP-SUBSET-Filters-Template-AddFilter-Fieldset~}
@@ -183,6 +188,7 @@ const _DEFAULT_CONFIGURATION_SUBSET_Filter =
183
188
  Hash: 'PRSP-AddFilter-Popover',
184
189
  Template: /*html*/`
185
190
  <!-- DefaultPackage pict view template: [PRSP-AddFilter-Popover] -->
191
+ <div class="prsp-addfilter-backdrop" onclick="_Pict.views['PRSP-Filters'].closeAddFilterPopover()"></div>
186
192
  <div class="prsp-addfilter-panel">
187
193
  <div class="prsp-addfilter-search">
188
194
  <span class="prsp-addfilter-search-ic">{~I:Search~}</span>
@@ -747,7 +753,46 @@ class ViewRecordSetSUBSETFilters extends libPictView
747
753
  _paintAddFilterOpenState()
748
754
  {
749
755
  const tmpPopover = document.getElementById('PRSP_AddFilter_Popover');
750
- if (tmpPopover) { tmpPopover.classList.toggle('open', !!this._addFilterOpen); }
756
+ if (!tmpPopover) { return; }
757
+ tmpPopover.classList.toggle('open', !!this._addFilterOpen);
758
+ if (this._addFilterOpen) { this._positionAddFilterPopover(tmpPopover); }
759
+ }
760
+
761
+ /**
762
+ * Position the (fixed) add-filter popover against its trigger button, flipping above when there's
763
+ * more room there. Fixed positioning means no ancestor overflow:hidden (the host's filter card, the
764
+ * slide-out drawer) can clip it — the price is we set its top/left from the trigger's rect here.
765
+ *
766
+ * @param {HTMLElement} pPopover - the #PRSP_AddFilter_Popover element (already display:block).
767
+ */
768
+ _positionAddFilterPopover(pPopover)
769
+ {
770
+ const tmpTrigger = document.getElementById('PRSP_Filter_Button_Add');
771
+ if (!tmpTrigger) { return; }
772
+ const tmpPanel = /** @type {HTMLElement} */ (pPopover.querySelector('.prsp-addfilter-panel'));
773
+ const tmpRect = tmpTrigger.getBoundingClientRect();
774
+ const tmpGap = 6;
775
+ const tmpMargin = 8;
776
+ const tmpVH = window.innerHeight;
777
+ const tmpVW = window.innerWidth;
778
+ const tmpWidth = pPopover.offsetWidth || 300;
779
+ pPopover.style.left = `${Math.round(Math.max(tmpMargin, Math.min(tmpRect.left, tmpVW - tmpWidth - tmpMargin)))}px`;
780
+ pPopover.style.right = 'auto';
781
+ const tmpSpaceBelow = tmpVH - tmpRect.bottom - tmpGap - tmpMargin;
782
+ const tmpSpaceAbove = tmpRect.top - tmpGap - tmpMargin;
783
+ // Prefer the natural downward direction; only flip above when the room below is genuinely cramped.
784
+ if (tmpSpaceBelow >= 220 || tmpSpaceBelow >= tmpSpaceAbove)
785
+ {
786
+ pPopover.style.top = `${Math.round(tmpRect.bottom + tmpGap)}px`;
787
+ pPopover.style.bottom = 'auto';
788
+ if (tmpPanel) { tmpPanel.style.maxHeight = `${Math.max(160, Math.min(tmpSpaceBelow, 460))}px`; }
789
+ }
790
+ else
791
+ {
792
+ pPopover.style.top = 'auto';
793
+ pPopover.style.bottom = `${Math.round(tmpVH - tmpRect.top + tmpGap)}px`;
794
+ if (tmpPanel) { tmpPanel.style.maxHeight = `${Math.max(160, Math.min(tmpSpaceAbove, 460))}px`; }
795
+ }
751
796
  }
752
797
 
753
798
  /**
@@ -0,0 +1,291 @@
1
+ const ViewRecordSetSUBSETFilterBase = require('./RecordSet-Filter-Base');
2
+
3
+ /**
4
+ * Shared base for the four entity-reference filter types — Internal/External join × single/list.
5
+ *
6
+ * These were ~95% duplicate views, each with the same bespoke "search results | selection" table UI
7
+ * and identical search/add/remove logic. This base consolidates all of it. Subclasses supply only the
8
+ * two seams that actually differ:
9
+ * - `getSearchEntity(pClause)` — internal join searches `RemoteTable`, external `ExternalFilterByTable`.
10
+ * - `isMultiSelect()` — the `…List` variants append; the plain `…SelectedValue` variants replace.
11
+ *
12
+ * Rendering uses the same mechanism as before (the input template is emitted during the clause's
13
+ * template parse, via `getFilterFormTemplate()`), so the consolidation is behavior-preserving. The
14
+ * input template is itself a seam (`getEntityInputTemplate()`) so a host can swap the table UI for a
15
+ * picker-backed input (a pict-section-form `Picker` InputType) without the module changing.
16
+ *
17
+ * Host-injected contextual scoping rides through `getContextScopeFilter(pClause)` — extra FoxHound
18
+ * stanzas AND-applied to the entity search (project / spec-year / …). The module never learns what
19
+ * those contexts mean; the host overrides the hook to read its own app state.
20
+ */
21
+ const _DEFAULT_CONFIGURATION_EntityReference =
22
+ {
23
+ ViewIdentifier: 'PRSP-FilterType-EntityReference-Base',
24
+
25
+ // When set (by a host), entity clauses delegate their input to this pict-section-form InputType
26
+ // (rendered via {~IWVDA:PSRSFilterProxyView:Record.ClauseDescriptor~}) instead of the built-in
27
+ // table UI — e.g. 'Picker' to use pict-section-picker. Default false → the table UI.
28
+ EntityInputType: false,
29
+
30
+ Templates:
31
+ [
32
+ {
33
+ // Table UI (default). Two cells: live search results | current selection.
34
+ Hash: 'PRSP-Filter-EntityRef-Table',
35
+ Template: /*html*/`
36
+ <table><tbody><tr>
37
+ <td valign="top">{~T:PRSP-Filter-EntityRef-SearchResults~}</td>
38
+ <td valign="top">{~T:PRSP-Filter-EntityRef-SelectedValues~}</td>
39
+ </tr></tbody></table>
40
+ `
41
+ },
42
+ {
43
+ // Picker / form-input delegation (opt-in via EntityInputType). Renders one dynamic input
44
+ // on the shared PSRSFilterProxyView from the clause's ClauseDescriptor (whose PictForm
45
+ // InputType is stamped in prepareRecord). Bound to the clause's Values address.
46
+ Hash: 'PRSP-Filter-EntityRef-Input',
47
+ Template: /*html*/`
48
+ {~IWVDA:PSRSFilterProxyView:Record.ClauseDescriptor~}
49
+ `
50
+ },
51
+ {
52
+ Hash: 'PRSP-Filter-EntityRef-SearchResults',
53
+ Template: /*html*/`
54
+ <form id="PRSP_Filter_{~D:Record.Hash~}_Search_Form" onsubmit="_Pict.views['{~D:Context[0].Hash~}'].performSearch(event, '{~D:Record.ClauseAddress~}', '{~D:Record.Hash~}'); return false;">
55
+ <input id="PRSP_Filter_{~D:Record.Hash~}_Search_Value" type="text" placeholder="Search..." value="{~D:Record.SearchInputValue~}" />
56
+ <button type="submit" id="PRSP_Filter_{~D:Record.Hash~}_Button_Search" onclick="_Pict.views['{~D:Context[0].Hash~}'].performSearch(event, '{~D:Record.ClauseAddress~}', '{~D:Record.Hash~}')">Search</button>
57
+ </form>
58
+ <table>
59
+ <thead><tr><th colspan="2">{~D:Record.Label~}</th></tr></thead>
60
+ <tbody>{~TSWP:PRSP-Filter-EntityRef-SearchResults-Entry:Record.SearchResults:Record~}</tbody>
61
+ </table>
62
+ <button type="button" id="PRSP_Filter_{~D:Record.Hash~}_Button_LoadMore" class="is-hidden" onclick="_Pict.views['{~D:Context[0].Hash~}'].loadMore(event, '{~D:Record.ClauseAddress~}', '{~D:Record.Hash~}', {~D:Record.SearchResultsOffset:0~})">Load More</button>
63
+ `
64
+ },
65
+ {
66
+ Hash: 'PRSP-Filter-EntityRef-SelectedValues',
67
+ Template: /*html*/`
68
+ <table>
69
+ <thead><tr><th colspan="2">Selection</th></tr></thead>
70
+ <tbody>{~TSWP:PRSP-Filter-EntityRef-SelectedValues-Entry:Record.SelectedValues:Record~}</tbody>
71
+ </table>
72
+ `
73
+ },
74
+ {
75
+ Hash: 'PRSP-Filter-EntityRef-SearchResults-Entry',
76
+ Template: /*html*/`
77
+ <tr><td><button onclick="_Pict.views['{~D:Context[0].Hash~}'].handleAdd(event, {~DVBK:Record.Data:Record.Payload.ExternalFilterTableLookupColumn~}, '{~D:Record.Payload.ClauseAddress~}', '{~D:Record.Payload.Hash~}')">Select</button></td><td>{~TFA:Record.Payload.ExternalRecordDisplayTemplate:Record~}</td></tr>
78
+ `
79
+ },
80
+ {
81
+ Hash: 'PRSP-Filter-EntityRef-SelectedValues-Entry',
82
+ Template: /*html*/`
83
+ <tr><td><button onclick="_Pict.views['{~D:Context[0].Hash~}'].handleRemove(event, {~DVBK:Record.Data:Record.Payload.ExternalFilterTableLookupColumn~}, '{~D:Record.Payload.ClauseAddress~}', '{~D:Record.Payload.Hash~}')">Remove</button></td><td>{~TFA:Record.Payload.ExternalRecordDisplayTemplate:Record~}</td></tr>
84
+ `
85
+ },
86
+ ],
87
+ };
88
+
89
+ class ViewRecordSetSUBSETFilterEntityReferenceBase extends ViewRecordSetSUBSETFilterBase
90
+ {
91
+ constructor(pFable, pOptions, pServiceHash)
92
+ {
93
+ if (!pOptions.PageSize)
94
+ {
95
+ pOptions.PageSize = 15;
96
+ }
97
+ super(pFable, pOptions, pServiceHash);
98
+ }
99
+
100
+ // --- Seams the subclasses override (the only real differences between the 4 types) ---
101
+
102
+ /** @param {Record<string, any>} pClause @return {string} The entity to search (RemoteTable / ExternalFilterByTable). */
103
+ getSearchEntity(pClause)
104
+ {
105
+ return pClause.RemoteTable || pClause.ExternalFilterByTable;
106
+ }
107
+
108
+ /** @param {Record<string, any>} pClause @return {string} The value/ID column on the searched entity. */
109
+ getLookupColumn(pClause)
110
+ {
111
+ return pClause.ExternalFilterTableLookupColumn || `ID${this.getSearchEntity(pClause)}`;
112
+ }
113
+
114
+ /** @return {boolean} True for the `…List` (multi-select) variants. */
115
+ isMultiSelect()
116
+ {
117
+ return true;
118
+ }
119
+
120
+ /**
121
+ * Host-injectable contextual search scope. Returns extra FoxHound filter stanza(s) AND-applied to
122
+ * the entity search (e.g. "only this project's line items"). Default: the clause's static
123
+ * `ContextScopeFilter` if any, else none. Hosts override this to read app state — the module never
124
+ * learns what a "project" or "spec year" is.
125
+ *
126
+ * @param {Record<string, any>} pClause @return {string|Array<string>}
127
+ */
128
+ getContextScopeFilter(pClause)
129
+ {
130
+ return pClause.ContextScopeFilter || '';
131
+ }
132
+
133
+ /** @return {string} The template hash for the clause's value input (table UI, or the opt-in delegated input). */
134
+ getFilterFormTemplate()
135
+ {
136
+ return this.options.EntityInputType ? 'PRSP-Filter-EntityRef-Input' : 'PRSP-Filter-EntityRef-Table';
137
+ }
138
+
139
+ /** @param {Record<string, any>} pRecord */
140
+ prepareRecord(pRecord)
141
+ {
142
+ super.prepareRecord(pRecord);
143
+
144
+ pRecord.ClauseDescriptor.PictForm = Object.assign({}, pRecord.PictForm);
145
+ if (this.options.EntityInputType)
146
+ {
147
+ // Delegated input: a csv string round-trips through the informary at `.StringArrayValue`,
148
+ // while the picker adapter writes the real ARRAY to `.Values` (what Filter.js reads) via
149
+ // ValueArrayAddress. Keeping the informary off `.Values` avoids a Number field defaulting it
150
+ // to "0". The descriptor also carries everything the picker needs + the contextual scope hook.
151
+ pRecord.ClauseDescriptor.Address = pRecord.ClauseAddress + '.StringArrayValue';
152
+ pRecord.ClauseDescriptor.DataType = 'String';
153
+ pRecord.ClauseDescriptor.PictForm.InputType = pRecord.ClauseDescriptor.PictForm.InputType || this.options.EntityInputType;
154
+ pRecord.ClauseDescriptor.PictForm.Entity = pRecord.ClauseDescriptor.PictForm.Entity || this.getSearchEntity(pRecord);
155
+ pRecord.ClauseDescriptor.PictForm.ValueField = pRecord.ClauseDescriptor.PictForm.ValueField || this.getLookupColumn(pRecord);
156
+ pRecord.ClauseDescriptor.PictForm.Multiple = this.isMultiSelect();
157
+ pRecord.ClauseDescriptor.PictForm.SearchFields = pRecord.ClauseDescriptor.PictForm.SearchFields
158
+ || pRecord.ExternalFilterByColumns || (pRecord.ExternalFilterByColumn ? [ pRecord.ExternalFilterByColumn ] : [ 'Name' ]);
159
+ pRecord.ClauseDescriptor.PictForm.ValueArrayAddress = pRecord.ClauseValuesAddress;
160
+ pRecord.ClauseDescriptor.PictForm.GetContextScopeFilter = () => this.getContextScopeFilter(this.getInformaryScopedValue(pRecord.ClauseAddress) || pRecord);
161
+ // Saved-filter seeding: mirror the live clause's Values array into the csv `.StringArrayValue`
162
+ // the input reads, so a reloaded/persisted filter shows its current selections on render.
163
+ const tmpLiveClause = this.getInformaryScopedValue(pRecord.ClauseAddress);
164
+ if (tmpLiveClause && Array.isArray(tmpLiveClause.Values))
165
+ {
166
+ tmpLiveClause.StringArrayValue = tmpLiveClause.Values.join(',');
167
+ }
168
+ }
169
+ else
170
+ {
171
+ pRecord.ClauseDescriptor.DataType = pRecord.DataType || 'Number';
172
+ }
173
+ if (!pRecord.ExternalFilterTableLookupColumn)
174
+ {
175
+ pRecord.ExternalFilterTableLookupColumn = this.getLookupColumn(pRecord);
176
+ }
177
+ if (!pRecord.SearchResults) { pRecord.SearchResults = []; }
178
+ if (!pRecord.SelectedValues) { pRecord.SelectedValues = []; }
179
+ }
180
+
181
+ // --- Table UI search/select methods (consolidated from the 4 legacy views) ---
182
+
183
+ loadMore(pEvent, pClauseInformaryAddress, pClauseHash, pCurrentOffset = 0)
184
+ {
185
+ this.performSearch(pEvent, pClauseInformaryAddress, pClauseHash, pCurrentOffset + this.options.PageSize);
186
+ }
187
+
188
+ performSearch(pEvent, pClauseInformaryAddress, pClauseHash, pOffset = 0)
189
+ {
190
+ if (pEvent) { pEvent.preventDefault(); }
191
+ const tmpClause = this.getInformaryScopedValue(pClauseInformaryAddress);
192
+ if (!tmpClause)
193
+ {
194
+ this.pict.log.error(`[Filter-EntityReference] No clause found for address: ${pClauseInformaryAddress}`);
195
+ return;
196
+ }
197
+ tmpClause.SearchResultsOffset = pOffset;
198
+ const tmpSearchInputValue = pOffset > 0 ? tmpClause.SearchInputValue : this.pict.ContentAssignment.readContent(`#PRSP_Filter_${tmpClause.Hash}_Search_Value`);
199
+ tmpClause.SearchInputValue = tmpSearchInputValue;
200
+ if (!tmpSearchInputValue)
201
+ {
202
+ tmpClause.SearchResults = [];
203
+ tmpClause.LoadMoreEnabled = false;
204
+ this._reRenderClause(tmpClause, pClauseInformaryAddress, pClauseHash);
205
+ return;
206
+ }
207
+ const tmpFilterByColumns = tmpClause.ExternalFilterByColumns || (tmpClause.ExternalFilterByColumn ? [ tmpClause.ExternalFilterByColumn ] : [ 'Name' ]);
208
+ const tmpSearchStanza = tmpFilterByColumns.map((pColumn) => `FBVOR~${pColumn}~LK~${encodeURIComponent(`%${tmpSearchInputValue}%`)}`).join('~');
209
+ const tmpScope = this.getContextScopeFilter(tmpClause);
210
+ const tmpScopeStanza = Array.isArray(tmpScope) ? tmpScope.filter(Boolean).join('~') : tmpScope;
211
+ const tmpMeadowFilter = [ tmpScopeStanza, tmpSearchStanza ].filter(Boolean).join('~');
212
+ this.pict.EntityProvider.gatherDataFromServer(
213
+ [
214
+ {
215
+ Entity: this.getSearchEntity(tmpClause),
216
+ Filter: tmpMeadowFilter,
217
+ Destination: pOffset > 0 ? `${this.getInformaryAddressPrefix()}${pClauseInformaryAddress}.SearchResultsAppend` : `${this.getInformaryAddressPrefix()}${pClauseInformaryAddress}.SearchResults`,
218
+ RecordStartCursor: pOffset,
219
+ PageSize: this.options.PageSize,
220
+ }
221
+ ],
222
+ () =>
223
+ {
224
+ if (pOffset > 0 && tmpClause.SearchResultsAppend?.length > 0)
225
+ {
226
+ tmpClause.SearchResults = tmpClause.SearchResults.concat(tmpClause.SearchResultsAppend);
227
+ }
228
+ const tmpLoadedRecords = pOffset > 0 ? tmpClause.SearchResultsAppend : tmpClause.SearchResults;
229
+ delete tmpClause.SearchResultsAppend;
230
+ tmpClause.SearchResultsOffset = pOffset;
231
+ tmpClause.LoadMoreEnabled = tmpLoadedRecords && tmpLoadedRecords.length >= this.options.PageSize;
232
+ this._reRenderClause(tmpClause, pClauseInformaryAddress, pClauseHash);
233
+ if (tmpClause.LoadMoreEnabled)
234
+ {
235
+ this.pict.ContentAssignment.removeClass(`#PRSP_Filter_${tmpClause.Hash}_Button_LoadMore`, 'is-hidden');
236
+ }
237
+ });
238
+ }
239
+
240
+ handleAdd(pEvent, pRecordLookupValue, pClauseInformaryAddress, pClauseHash)
241
+ {
242
+ if (pEvent) { pEvent.preventDefault(); }
243
+ const tmpClause = this.getInformaryScopedValue(pClauseInformaryAddress);
244
+ if (!tmpClause) { return; }
245
+ const tmpRecordLookupColumn = this.getLookupColumn(tmpClause);
246
+ const tmpRecordToAdd = (tmpClause.SearchResults || []).find((pRow) => pRow[tmpRecordLookupColumn] == pRecordLookupValue);
247
+ if (!tmpRecordToAdd) { return; }
248
+ const tmpValue = tmpRecordToAdd[tmpRecordLookupColumn];
249
+ if (tmpValue == null) { return; }
250
+ if (!tmpClause.SelectedValues) { tmpClause.SelectedValues = []; }
251
+ if (!tmpClause.Values) { tmpClause.Values = []; }
252
+ if (this.isMultiSelect())
253
+ {
254
+ if (tmpClause.SelectedValues.some((pSV) => pSV[tmpRecordLookupColumn] == pRecordLookupValue)) { return; }
255
+ tmpClause.SelectedValues.push(tmpRecordToAdd);
256
+ if (!tmpClause.Values.some((pV) => pV == tmpValue)) { tmpClause.Values.push(tmpValue); }
257
+ }
258
+ else
259
+ {
260
+ tmpClause.SelectedValues = [ tmpRecordToAdd ];
261
+ tmpClause.Values = [ tmpValue ];
262
+ }
263
+ this._reRenderClause(tmpClause, pClauseInformaryAddress, pClauseHash);
264
+ }
265
+
266
+ handleRemove(pEvent, pRecordLookupValue, pClauseInformaryAddress, pClauseHash)
267
+ {
268
+ if (pEvent) { pEvent.preventDefault(); }
269
+ const tmpClause = this.getInformaryScopedValue(pClauseInformaryAddress);
270
+ if (!tmpClause) { return; }
271
+ const tmpRecordLookupColumn = this.getLookupColumn(tmpClause);
272
+ const tmpIndex = (tmpClause.SelectedValues || []).findIndex((pRow) => pRow[tmpRecordLookupColumn] == pRecordLookupValue);
273
+ if (tmpIndex < 0) { return; }
274
+ const tmpRemoved = tmpClause.SelectedValues.splice(tmpIndex, 1)[0];
275
+ const tmpValue = tmpRemoved[tmpRecordLookupColumn];
276
+ tmpClause.Values = (tmpClause.Values || []).filter((pV) => pV !== tmpValue);
277
+ this._reRenderClause(tmpClause, pClauseInformaryAddress, pClauseHash);
278
+ }
279
+
280
+ /** Re-render a clause's container (the table UI re-paints on every search / add / remove). */
281
+ _reRenderClause(pClause, pClauseInformaryAddress, pClauseHash)
282
+ {
283
+ const tmpRecord = Object.assign({ ClauseAddress: pClauseInformaryAddress }, pClause);
284
+ this.prepareRecord(tmpRecord);
285
+ this.render(null, `#PRSP_Filter_Container_${pClauseHash}`, tmpRecord);
286
+ }
287
+ }
288
+
289
+ module.exports = ViewRecordSetSUBSETFilterEntityReferenceBase;
290
+
291
+ module.exports.default_configuration = Object.assign({}, ViewRecordSetSUBSETFilterBase.default_configuration, _DEFAULT_CONFIGURATION_EntityReference);