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.
- package/package.json +1 -1
- package/source/views/Filter-PersistenceView.js +5 -1
- package/source/views/RecordSet-Filters.js +51 -6
- package/source/views/filters/RecordSet-Filter-EntityReference-Base.js +291 -0
- package/source/views/filters/RecordSet-Filter-ExternalJoinSelectedValue.js +10 -256
- package/source/views/filters/RecordSet-Filter-ExternalJoinSelectedValueList.js +10 -263
- package/source/views/filters/RecordSet-Filter-InternalJoinSelectedValue.js +10 -256
- package/source/views/filters/RecordSet-Filter-InternalJoinSelectedValueList.js +10 -263
- package/types/views/Filter-PersistenceView.d.ts.map +1 -1
- package/types/views/RecordSet-Filters.d.ts +8 -0
- package/types/views/RecordSet-Filters.d.ts.map +1 -1
- package/types/views/filters/RecordSet-Filter-EntityReference-Base.d.ts +37 -0
- package/types/views/filters/RecordSet-Filter-EntityReference-Base.d.ts.map +1 -0
- package/types/views/filters/RecordSet-Filter-ExternalJoinSelectedValue.d.ts +13 -18
- package/types/views/filters/RecordSet-Filter-ExternalJoinSelectedValue.d.ts.map +1 -1
- package/types/views/filters/RecordSet-Filter-ExternalJoinSelectedValueList.d.ts +13 -18
- package/types/views/filters/RecordSet-Filter-ExternalJoinSelectedValueList.d.ts.map +1 -1
- package/types/views/filters/RecordSet-Filter-InternalJoinSelectedValue.d.ts +13 -18
- package/types/views/filters/RecordSet-Filter-InternalJoinSelectedValue.d.ts.map +1 -1
- package/types/views/filters/RecordSet-Filter-InternalJoinSelectedValueList.d.ts +13 -18
- package/types/views/filters/RecordSet-Filter-InternalJoinSelectedValueList.d.ts.map +1 -1
package/package.json
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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 {
|
|
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' && !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) {
|
|
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);
|