pict-section-recordset 1.0.63 → 1.0.66
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/example_applications/bookstore/.quackage.json +9 -0
- package/example_applications/bookstore/Bookstore-Application-Configuration.json +4 -0
- package/example_applications/bookstore/Bookstore-Application.js +671 -0
- package/example_applications/bookstore/css/bookstore.css +729 -0
- package/example_applications/bookstore/css/pure.min.css +11 -0
- package/example_applications/bookstore/html/index.html +46 -0
- package/example_applications/bookstore/package.json +34 -0
- package/example_applications/bookstore/providers/PictRouter-Bookstore.json +32 -0
- package/example_applications/bookstore/views/PictView-Bookstore-Content-About.json +21 -0
- package/example_applications/bookstore/views/PictView-Bookstore-Content-Legal.json +21 -0
- package/example_applications/bookstore/views/PictView-Bookstore-Dashboard.js +147 -0
- package/example_applications/bookstore/views/PictView-Bookstore-Layout.js +85 -0
- package/example_applications/bookstore/views/PictView-Bookstore-Login.js +58 -0
- package/example_applications/bookstore/views/PictView-Bookstore-Navigation.js +228 -0
- package/package.json +14 -14
- package/source/templates/Pict-Template-FilterInstanceViews.js +142 -25
- package/source/views/RecordSet-Filters.js +25 -0
- package/test/PictSectionRecordSet-Filter-InstanceViews-Render_tests.js +328 -0
- /package/docs/{cover.md → _cover.md} +0 -0
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
const libPictView = require('pict-view');
|
|
2
|
+
|
|
3
|
+
const _ViewConfiguration =
|
|
4
|
+
{
|
|
5
|
+
ViewIdentifier: "Bookstore-Navigation",
|
|
6
|
+
|
|
7
|
+
DefaultRenderable: "Bookstore-Navigation-Content",
|
|
8
|
+
DefaultDestinationAddress: "#Bookstore-Navigation-Container",
|
|
9
|
+
|
|
10
|
+
AutoRender: false,
|
|
11
|
+
|
|
12
|
+
CSS: /*css*/`
|
|
13
|
+
#Bookstore-Navigation-Container .pure-menu-heading
|
|
14
|
+
{
|
|
15
|
+
font-size: 110%;
|
|
16
|
+
color: #fff;
|
|
17
|
+
margin: 0;
|
|
18
|
+
background: #E76F51;
|
|
19
|
+
padding: 0.75em 0.6em;
|
|
20
|
+
}
|
|
21
|
+
.bookstore-nav-session
|
|
22
|
+
{
|
|
23
|
+
padding: 0.6em;
|
|
24
|
+
border-top: 1px solid #333;
|
|
25
|
+
font-size: 0.8rem;
|
|
26
|
+
color: #999;
|
|
27
|
+
}
|
|
28
|
+
.bookstore-nav-session .dot
|
|
29
|
+
{
|
|
30
|
+
display: inline-block;
|
|
31
|
+
width: 8px;
|
|
32
|
+
height: 8px;
|
|
33
|
+
border-radius: 50%;
|
|
34
|
+
background: #2A9D8F;
|
|
35
|
+
box-shadow: 0 0 4px rgba(42,157,143,0.5);
|
|
36
|
+
margin-right: 0.35em;
|
|
37
|
+
}
|
|
38
|
+
.bookstore-nav-session-name
|
|
39
|
+
{
|
|
40
|
+
display: block;
|
|
41
|
+
color: #D4A373;
|
|
42
|
+
font-weight: 600;
|
|
43
|
+
margin-top: 0.25em;
|
|
44
|
+
}
|
|
45
|
+
.bookstore-nav-logout
|
|
46
|
+
{
|
|
47
|
+
display: block;
|
|
48
|
+
margin-top: 0.5em;
|
|
49
|
+
background: #E76F51;
|
|
50
|
+
color: #fff;
|
|
51
|
+
border: none;
|
|
52
|
+
padding: 0.35em 0.75em;
|
|
53
|
+
border-radius: 4px;
|
|
54
|
+
font-size: 0.75rem;
|
|
55
|
+
font-weight: 600;
|
|
56
|
+
cursor: pointer;
|
|
57
|
+
width: 100%;
|
|
58
|
+
text-align: center;
|
|
59
|
+
}
|
|
60
|
+
.bookstore-nav-logout:hover
|
|
61
|
+
{
|
|
62
|
+
background: #C45A3E;
|
|
63
|
+
}
|
|
64
|
+
/* Preprocessor toggle section */
|
|
65
|
+
.bookstore-nav-preprocessor
|
|
66
|
+
{
|
|
67
|
+
padding: 0.6em;
|
|
68
|
+
border-top: 1px solid #333;
|
|
69
|
+
font-size: 0.75rem;
|
|
70
|
+
color: #999;
|
|
71
|
+
}
|
|
72
|
+
.bookstore-nav-preprocessor-label
|
|
73
|
+
{
|
|
74
|
+
display: block;
|
|
75
|
+
margin-bottom: 0.35em;
|
|
76
|
+
font-weight: 600;
|
|
77
|
+
color: #D4A373;
|
|
78
|
+
font-size: 0.7rem;
|
|
79
|
+
text-transform: uppercase;
|
|
80
|
+
letter-spacing: 0.05em;
|
|
81
|
+
}
|
|
82
|
+
.bookstore-preprocessor-toggle
|
|
83
|
+
{
|
|
84
|
+
position: relative;
|
|
85
|
+
display: inline-block;
|
|
86
|
+
width: 36px;
|
|
87
|
+
height: 20px;
|
|
88
|
+
vertical-align: middle;
|
|
89
|
+
}
|
|
90
|
+
.bookstore-preprocessor-toggle input
|
|
91
|
+
{
|
|
92
|
+
opacity: 0;
|
|
93
|
+
width: 0;
|
|
94
|
+
height: 0;
|
|
95
|
+
}
|
|
96
|
+
.bookstore-preprocessor-slider
|
|
97
|
+
{
|
|
98
|
+
position: absolute;
|
|
99
|
+
cursor: pointer;
|
|
100
|
+
top: 0;
|
|
101
|
+
left: 0;
|
|
102
|
+
right: 0;
|
|
103
|
+
bottom: 0;
|
|
104
|
+
background-color: #555;
|
|
105
|
+
transition: background-color 0.2s;
|
|
106
|
+
border-radius: 20px;
|
|
107
|
+
}
|
|
108
|
+
.bookstore-preprocessor-slider:before
|
|
109
|
+
{
|
|
110
|
+
position: absolute;
|
|
111
|
+
content: "";
|
|
112
|
+
height: 14px;
|
|
113
|
+
width: 14px;
|
|
114
|
+
left: 3px;
|
|
115
|
+
bottom: 3px;
|
|
116
|
+
background-color: #fff;
|
|
117
|
+
transition: transform 0.2s;
|
|
118
|
+
border-radius: 50%;
|
|
119
|
+
}
|
|
120
|
+
.bookstore-preprocessor-toggle input:checked + .bookstore-preprocessor-slider
|
|
121
|
+
{
|
|
122
|
+
background-color: #2A9D8F;
|
|
123
|
+
}
|
|
124
|
+
.bookstore-preprocessor-toggle input:checked + .bookstore-preprocessor-slider:before
|
|
125
|
+
{
|
|
126
|
+
transform: translateX(16px);
|
|
127
|
+
}
|
|
128
|
+
.bookstore-preprocessor-status
|
|
129
|
+
{
|
|
130
|
+
display: inline-block;
|
|
131
|
+
margin-left: 0.35em;
|
|
132
|
+
vertical-align: middle;
|
|
133
|
+
font-size: 0.7rem;
|
|
134
|
+
color: #999;
|
|
135
|
+
}
|
|
136
|
+
`,
|
|
137
|
+
|
|
138
|
+
Templates:
|
|
139
|
+
[
|
|
140
|
+
{
|
|
141
|
+
Hash: "Bookstore-Navigation-Template",
|
|
142
|
+
Template: /*html*/`
|
|
143
|
+
<span class="pure-menu-heading">Bookstore</span>
|
|
144
|
+
<ul class="pure-menu-list">
|
|
145
|
+
<li class="pure-menu-item"><a href="#" onclick="{~P~}.PictApplication.navigateTo('/Dashboard')" class="pure-menu-link">Dashboard</a></li>
|
|
146
|
+
<li class="pure-menu-item menu-item-divided"><a href="#" onclick="{~P~}.PictApplication.navigateTo('/Books')" class="pure-menu-link">Books</a></li>
|
|
147
|
+
<li class="pure-menu-item"><a href="#" onclick="{~P~}.PictApplication.navigateTo('/Authors')" class="pure-menu-link">Authors</a></li>
|
|
148
|
+
<li class="pure-menu-item"><a href="#" onclick="{~P~}.PictApplication.navigateTo('/BookStores')" class="pure-menu-link">Stores</a></li>
|
|
149
|
+
<li class="pure-menu-item menu-item-divided"><a href="#" onclick="{~P~}.PictApplication.navigateTo('/About')" class="pure-menu-link">About</a></li>
|
|
150
|
+
<li class="pure-menu-item"><a href="#" onclick="{~P~}.PictApplication.navigateTo('/Legal')" class="pure-menu-link">Legal</a></li>
|
|
151
|
+
</ul>
|
|
152
|
+
<div class="bookstore-nav-preprocessor">
|
|
153
|
+
<span class="bookstore-nav-preprocessor-label">Template Preprocessor</span>
|
|
154
|
+
<label class="bookstore-preprocessor-toggle">
|
|
155
|
+
<input type="checkbox" id="Bookstore-Preprocessor-Toggle" onclick="{~P~}.PictApplication.togglePreprocessor()">
|
|
156
|
+
<span class="bookstore-preprocessor-slider"></span>
|
|
157
|
+
</label>
|
|
158
|
+
<span class="bookstore-preprocessor-status" id="Bookstore-Preprocessor-Status">Off</span>
|
|
159
|
+
</div>
|
|
160
|
+
<div class="bookstore-nav-session">
|
|
161
|
+
<span class="dot"></span> Signed in
|
|
162
|
+
<span class="bookstore-nav-session-name" id="Bookstore-Nav-UserName"></span>
|
|
163
|
+
<button class="bookstore-nav-logout" type="button" onclick="{~P~}.PictApplication.doLogout()">Log out</button>
|
|
164
|
+
</div>
|
|
165
|
+
`
|
|
166
|
+
}
|
|
167
|
+
],
|
|
168
|
+
|
|
169
|
+
Renderables:
|
|
170
|
+
[
|
|
171
|
+
{
|
|
172
|
+
RenderableHash: "Bookstore-Navigation-Content",
|
|
173
|
+
TemplateHash: "Bookstore-Navigation-Template",
|
|
174
|
+
DestinationAddress: "#Bookstore-Navigation-Container"
|
|
175
|
+
}
|
|
176
|
+
]
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
class BookstoreNavigationView extends libPictView
|
|
180
|
+
{
|
|
181
|
+
constructor(pFable, pOptions, pServiceHash)
|
|
182
|
+
{
|
|
183
|
+
super(pFable, pOptions, pServiceHash);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
onAfterRender(pRenderable, pRenderDestinationAddress, pRecord, pContent)
|
|
187
|
+
{
|
|
188
|
+
// Populate the user name from session data
|
|
189
|
+
let tmpSession = this.pict.AppData.Session;
|
|
190
|
+
let tmpDisplayName = '';
|
|
191
|
+
|
|
192
|
+
if (tmpSession && tmpSession.UserRecord)
|
|
193
|
+
{
|
|
194
|
+
tmpDisplayName = tmpSession.UserRecord.FullName
|
|
195
|
+
|| tmpSession.UserRecord.LoginID
|
|
196
|
+
|| '';
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
let tmpUserNameElements = this.services.ContentAssignment.getElement('#Bookstore-Nav-UserName');
|
|
200
|
+
if (tmpUserNameElements && tmpUserNameElements.length > 0)
|
|
201
|
+
{
|
|
202
|
+
tmpUserNameElements[0].textContent = tmpDisplayName;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Sync the preprocessor toggle state with the application's current state
|
|
206
|
+
let tmpApp = this.pict.PictApplication;
|
|
207
|
+
let tmpEnabled = tmpApp && tmpApp.isPreprocessorEnabled && tmpApp.isPreprocessorEnabled();
|
|
208
|
+
|
|
209
|
+
let tmpToggleElements = this.services.ContentAssignment.getElement('#Bookstore-Preprocessor-Toggle');
|
|
210
|
+
if (tmpToggleElements && tmpToggleElements.length > 0)
|
|
211
|
+
{
|
|
212
|
+
tmpToggleElements[0].checked = !!tmpEnabled;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
let tmpStatusElements = this.services.ContentAssignment.getElement('#Bookstore-Preprocessor-Status');
|
|
216
|
+
if (tmpStatusElements && tmpStatusElements.length > 0)
|
|
217
|
+
{
|
|
218
|
+
tmpStatusElements[0].textContent = tmpEnabled ? 'On' : 'Off';
|
|
219
|
+
tmpStatusElements[0].style.color = tmpEnabled ? '#2A9D8F' : '#999';
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
return super.onAfterRender(pRenderable, pRenderDestinationAddress, pRecord, pContent);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
module.exports = BookstoreNavigationView;
|
|
227
|
+
|
|
228
|
+
module.exports.default_configuration = _ViewConfiguration;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pict-section-recordset",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.66",
|
|
4
4
|
"description": "Pict dynamic record set management views",
|
|
5
5
|
"main": "source/Pict-Section-RecordSet.js",
|
|
6
6
|
"directories": {
|
|
@@ -16,10 +16,10 @@
|
|
|
16
16
|
"homepage": "https://github.com/stevenvelozo/pict-section-recordset#readme",
|
|
17
17
|
"scripts": {
|
|
18
18
|
"start": "node source/Pict-Section-RecordSet.js",
|
|
19
|
-
"tests": "npx
|
|
20
|
-
"coverage": "npx
|
|
19
|
+
"tests": "npx quack test -g",
|
|
20
|
+
"coverage": "npx quack coverage",
|
|
21
21
|
"build": "npx quack build",
|
|
22
|
-
"test": "npx
|
|
22
|
+
"test": "npx quack test",
|
|
23
23
|
"lint": "eslint source",
|
|
24
24
|
"types": "tsc -p tsconfig.build.json"
|
|
25
25
|
},
|
|
@@ -33,19 +33,19 @@
|
|
|
33
33
|
"browser-env": "^3.3.0",
|
|
34
34
|
"eslint": "^9.28.0",
|
|
35
35
|
"jquery": "^3.7.1",
|
|
36
|
-
"pict": "^1.0.
|
|
37
|
-
"pict-application": "^1.0.
|
|
38
|
-
"pict-service-commandlineutility": "^1.0.
|
|
39
|
-
"quackage": "^1.0.
|
|
36
|
+
"pict": "^1.0.363",
|
|
37
|
+
"pict-application": "^1.0.33",
|
|
38
|
+
"pict-service-commandlineutility": "^1.0.19",
|
|
39
|
+
"quackage": "^1.0.65",
|
|
40
40
|
"typescript": "^5.9.3"
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
|
-
"fable-serviceproviderbase": "^3.0.
|
|
44
|
-
"pict-provider": "^1.0.
|
|
45
|
-
"pict-router": "^1.0.
|
|
46
|
-
"pict-section-form": "^1.0.
|
|
47
|
-
"pict-template": "^1.0.
|
|
48
|
-
"pict-view": "^1.0.
|
|
43
|
+
"fable-serviceproviderbase": "^3.0.19",
|
|
44
|
+
"pict-provider": "^1.0.12",
|
|
45
|
+
"pict-router": "^1.0.9",
|
|
46
|
+
"pict-section-form": "^1.0.196",
|
|
47
|
+
"pict-template": "^1.0.15",
|
|
48
|
+
"pict-view": "^1.0.68",
|
|
49
49
|
"sinon": "^21.0.1"
|
|
50
50
|
},
|
|
51
51
|
"mocha": {
|
|
@@ -5,6 +5,48 @@ const libFilterViews = require('../views/filters/index.js');
|
|
|
5
5
|
* Specialized instruction for rendering the filter view and plumbing in required context.
|
|
6
6
|
*
|
|
7
7
|
* Based on the Pict base {~V:...~} template instruction.
|
|
8
|
+
*
|
|
9
|
+
* ## Async render architecture (see renderAsync below)
|
|
10
|
+
*
|
|
11
|
+
* The async path is tuned to get the dashboard records list painted before any
|
|
12
|
+
* speculative filter-list REST lookups complete. Three things collaborate to
|
|
13
|
+
* make that happen:
|
|
14
|
+
*
|
|
15
|
+
* 1. **Parallel filter fan-out.** Every filter clause starts its own
|
|
16
|
+
* `renderWithScopeAsync` in a single pass, instead of chaining through a
|
|
17
|
+
* sequential `Anticipate` queue. Template parsing, `onBefore*`/`onProject*`
|
|
18
|
+
* microtask hops, and the synchronous DOM walks done inside each filter's
|
|
19
|
+
* `onAfterRender` all happen concurrently rather than serialized across N
|
|
20
|
+
* filters. Results are collected into an indexed array so final output
|
|
21
|
+
* order still matches the clause order.
|
|
22
|
+
*
|
|
23
|
+
* 2. **Per-filter transaction isolation.** Each filter render gets its own
|
|
24
|
+
* synthetic `RootRenderable` carrying a fresh `TransactionHash`. Any
|
|
25
|
+
* `virtual-assignment` sub-renders that the filter's dynamic form spawns
|
|
26
|
+
* (notably `PSRSFilterProxyView`, which pict-section-form uses to host
|
|
27
|
+
* each filter's input) push into THIS transaction's queue, not the
|
|
28
|
+
* dashboard's. The dashboard's outer `renderAsync` callback fires as soon
|
|
29
|
+
* as every filter has its template string - without waiting on any of
|
|
30
|
+
* the nested input-initialize work.
|
|
31
|
+
*
|
|
32
|
+
* 3. **Deferred post-render drain.** The filter's post-render pipeline
|
|
33
|
+
* (`onAfterRenderAsync`, which is what runs pict-section-form's
|
|
34
|
+
* `runInputProviderFunctions('onInputInitialize', ...)` and therefore
|
|
35
|
+
* triggers `EntityBundleRequest.gatherDataFromServer` for any
|
|
36
|
+
* speculative-load inputs) is intentionally NOT run inline. It is queued
|
|
37
|
+
* via `setTimeout(..., 0)` so it fires on the next macrotask, giving the
|
|
38
|
+
* browser a tick to paint the dashboard first.
|
|
39
|
+
*
|
|
40
|
+
* ### Render-epoch race guard
|
|
41
|
+
*
|
|
42
|
+
* Between the dashboard callback firing and the setTimeout actually running,
|
|
43
|
+
* the user may have navigated away / applied a different filter experience /
|
|
44
|
+
* cleared filters. The `PRSP-Filters` view owns a monotonic `_renderEpoch`
|
|
45
|
+
* counter that gets bumped on every mutating action (`performSearch`,
|
|
46
|
+
* `handleClear`, `handleReset`, `addFilter`, `removeFilter`). Each scheduled
|
|
47
|
+
* drain captures the epoch at schedule time and bails out if a newer render
|
|
48
|
+
* has invalidated it, so a stale REST response can never clobber a filter
|
|
49
|
+
* container that now belongs to a different experience.
|
|
8
50
|
*/
|
|
9
51
|
class PictTemplateFilterInstanceViewInstruction extends libPictTemplate
|
|
10
52
|
{
|
|
@@ -177,10 +219,61 @@ class PictTemplateFilterInstanceViewInstruction extends libPictTemplate
|
|
|
177
219
|
{
|
|
178
220
|
pRecord.ViewContext = pRecord.DashboardHash ? `${tmpViewContext}-${pRecord.DashboardHash}` : tmpViewContext;
|
|
179
221
|
}
|
|
180
|
-
const tmpAnticipate = this.pict.newAnticipate();
|
|
181
|
-
let tmpResult = '';
|
|
182
222
|
|
|
183
223
|
const tmpClauses = this.pict.Bundle._ActiveFilterState?.[pRecord.RecordSet]?.FilterClauses || [];
|
|
224
|
+
if (tmpClauses.length === 0)
|
|
225
|
+
{
|
|
226
|
+
return fCallback(null, '');
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Snapshot the current render epoch so the deferred drain below can
|
|
230
|
+
// detect a filter re-render that happened between schedule time and
|
|
231
|
+
// now (see class doc-comment, "Render-epoch race guard").
|
|
232
|
+
const tmpFiltersView = this.pict.views['PRSP-Filters'];
|
|
233
|
+
const tmpRenderEpoch = tmpFiltersView ? tmpFiltersView._renderEpoch : 0;
|
|
234
|
+
|
|
235
|
+
const tmpResults = new Array(tmpClauses.length).fill('');
|
|
236
|
+
const tmpDeferredDrains = [];
|
|
237
|
+
let tmpRemaining = tmpClauses.length;
|
|
238
|
+
let tmpCallbackFired = false;
|
|
239
|
+
|
|
240
|
+
const tmpFinalize = () =>
|
|
241
|
+
{
|
|
242
|
+
if (tmpCallbackFired || tmpRemaining > 0)
|
|
243
|
+
{
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
tmpCallbackFired = true;
|
|
247
|
+
// Fire the dashboard callback first so the paint lands on the
|
|
248
|
+
// current tick, then defer the filters' onAfterRender work (which
|
|
249
|
+
// runs pict-section-form's input-initialize pass and fires any
|
|
250
|
+
// EntityBundleRequest REST calls) to the next macrotask.
|
|
251
|
+
fCallback(null, tmpResults.join(''));
|
|
252
|
+
if (tmpDeferredDrains.length === 0)
|
|
253
|
+
{
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
setTimeout(() =>
|
|
257
|
+
{
|
|
258
|
+
// Epoch guard: bail out if a newer render has invalidated us.
|
|
259
|
+
if (tmpFiltersView && tmpFiltersView._renderEpoch !== tmpRenderEpoch)
|
|
260
|
+
{
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
for (let d = 0; d < tmpDeferredDrains.length; d++)
|
|
264
|
+
{
|
|
265
|
+
try
|
|
266
|
+
{
|
|
267
|
+
tmpDeferredDrains[d]();
|
|
268
|
+
}
|
|
269
|
+
catch (pError)
|
|
270
|
+
{
|
|
271
|
+
this.log.warn(`Pict: Filter Instance Views Template Render: Error draining deferred filter transaction`, pError);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}, 0);
|
|
275
|
+
};
|
|
276
|
+
|
|
184
277
|
//FIXME: lookup by hash instead?
|
|
185
278
|
for (let i = 0; i < tmpClauses.length; i++)
|
|
186
279
|
{
|
|
@@ -189,39 +282,63 @@ class PictTemplateFilterInstanceViewInstruction extends libPictTemplate
|
|
|
189
282
|
const tmpView = this._getViewForFilterClause(tmpClause);
|
|
190
283
|
if (!tmpView)
|
|
191
284
|
{
|
|
285
|
+
tmpRemaining--;
|
|
192
286
|
continue;
|
|
193
287
|
}
|
|
194
288
|
|
|
195
289
|
const tmpRenderGUID = this.pict.getUUID();
|
|
196
|
-
|
|
290
|
+
const tmpRecord = Object.assign({}, pRecord, tmpClause);
|
|
291
|
+
tmpRecord.ClauseAddress = `_ActiveFilterState[${pRecord.RecordSet}].FilterClauses[${i}]`;
|
|
292
|
+
tmpView.prepareRecord(tmpRecord);
|
|
293
|
+
|
|
294
|
+
// Synthetic per-filter root renderable: nested virtual-assignment
|
|
295
|
+
// renders from this filter's subtree push into the filter's own
|
|
296
|
+
// transaction queue instead of the dashboard's.
|
|
297
|
+
const tmpFilterTransactionHash = `FilterInstance-${tmpView.Hash}-${tmpRenderGUID}`;
|
|
298
|
+
this.pict.TransactionTracking.registerTransaction(tmpFilterTransactionHash);
|
|
299
|
+
const tmpFilterRootRenderable =
|
|
300
|
+
{
|
|
301
|
+
RenderableHash: '__Virtual',
|
|
302
|
+
TemplateHash: null,
|
|
303
|
+
ContentDestinationAddress: null,
|
|
304
|
+
RenderMethod: 'virtual-assignment',
|
|
305
|
+
TransactionHash: tmpFilterTransactionHash,
|
|
306
|
+
RootRenderableViewHash: tmpView.Hash,
|
|
307
|
+
};
|
|
308
|
+
|
|
309
|
+
tmpDeferredDrains.push(() =>
|
|
197
310
|
{
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
311
|
+
tmpView.onAfterRenderAsync(() =>
|
|
312
|
+
{
|
|
313
|
+
// Remove the per-filter transaction we registered above
|
|
314
|
+
// once its drain completes. Pict-View will also attempt
|
|
315
|
+
// to unregister during its own onAfterRenderAsync chain;
|
|
316
|
+
// whichever call hits first wins, the other is a no-op.
|
|
317
|
+
this.pict.TransactionTracking.unregisterTransaction(tmpFilterTransactionHash);
|
|
318
|
+
}, tmpFilterRootRenderable);
|
|
319
|
+
});
|
|
201
320
|
|
|
202
|
-
|
|
203
|
-
|
|
321
|
+
tmpView.renderWithScopeAsync(tmpClause, '__Virtual', `__TemplateOutputCache.${tmpRenderGUID}`, tmpRecord, tmpFilterRootRenderable,
|
|
322
|
+
(pError) =>
|
|
323
|
+
{
|
|
324
|
+
if (pError)
|
|
204
325
|
{
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
tmpResult += this.pict.__TemplateOutputCache[tmpRenderGUID];
|
|
326
|
+
this.log.warn(`Pict: Filter Instance Views Template Render: Error rendering view [${tmpView.Hash}]`, pError);
|
|
327
|
+
//TODO: should we fail the whole render?
|
|
328
|
+
tmpResults[i] = '';
|
|
329
|
+
}
|
|
330
|
+
else
|
|
331
|
+
{
|
|
332
|
+
tmpResults[i] = this.pict.__TemplateOutputCache[tmpRenderGUID] || '';
|
|
214
333
|
// TODO: Uncomment this when we like how it's working
|
|
215
334
|
//delete this.pict.__TemplateOutputCache[tmpRenderGUID];
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
335
|
+
}
|
|
336
|
+
tmpRemaining--;
|
|
337
|
+
tmpFinalize();
|
|
338
|
+
});
|
|
220
339
|
}
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
return fCallback(pError, tmpResult);
|
|
224
|
-
});
|
|
340
|
+
// Handle the case where every filter was skipped synchronously (no valid view).
|
|
341
|
+
tmpFinalize();
|
|
225
342
|
}
|
|
226
343
|
}
|
|
227
344
|
|
|
@@ -236,6 +236,26 @@ class ViewRecordSetSUBSETFilters extends libPictView
|
|
|
236
236
|
this.newFilterSearchApplied = false;
|
|
237
237
|
this.addFilterCallback = null;
|
|
238
238
|
this.removeFilterCallback = null;
|
|
239
|
+
// Render-epoch counter, bumped any time the filter list is re-rendered
|
|
240
|
+
// or a new filter experience is applied. Deferred filter post-render
|
|
241
|
+
// work (e.g. the setTimeout-scheduled transaction drain in
|
|
242
|
+
// Pict-Template-FilterInstanceViews) captures the epoch at schedule
|
|
243
|
+
// time and compares against the current value before running, so a
|
|
244
|
+
// stale callback doesn't clobber DOM that now belongs to a different
|
|
245
|
+
// filter experience.
|
|
246
|
+
this._renderEpoch = 0;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Bump the render epoch. Call this whenever the active filter clauses are
|
|
251
|
+
* about to change in a way that would invalidate in-flight filter renders.
|
|
252
|
+
*
|
|
253
|
+
* @return {number} The new epoch value.
|
|
254
|
+
*/
|
|
255
|
+
bumpRenderEpoch()
|
|
256
|
+
{
|
|
257
|
+
this._renderEpoch++;
|
|
258
|
+
return this._renderEpoch;
|
|
239
259
|
}
|
|
240
260
|
|
|
241
261
|
/**
|
|
@@ -350,6 +370,7 @@ class ViewRecordSetSUBSETFilters extends libPictView
|
|
|
350
370
|
*/
|
|
351
371
|
performSearch(pRecordSet, pViewContext, pFilterString)
|
|
352
372
|
{
|
|
373
|
+
this.bumpRenderEpoch();
|
|
353
374
|
const tmpPictRouter = this.pict.providers.PictRouter;
|
|
354
375
|
const tmpProviderConfiguration = this.pict.PictSectionRecordSet.recordSetProviderConfigurations[pRecordSet];
|
|
355
376
|
let filterExpr = '';
|
|
@@ -410,6 +431,7 @@ class ViewRecordSetSUBSETFilters extends libPictView
|
|
|
410
431
|
handleClear(pEvent, pRecordSet, pViewContext)
|
|
411
432
|
{
|
|
412
433
|
if (pEvent) pEvent.preventDefault();
|
|
434
|
+
this.bumpRenderEpoch();
|
|
413
435
|
this.pict.ContentAssignment.assignContent('input[name="filter"]', '');
|
|
414
436
|
this.pict.Bundle._ActiveFilterState[pRecordSet].FilterClauses = [];
|
|
415
437
|
this.pict.providers.FilterDataProvider.removeDefaultFilterExperience(pRecordSet, pViewContext);
|
|
@@ -426,6 +448,7 @@ class ViewRecordSetSUBSETFilters extends libPictView
|
|
|
426
448
|
handleReset(pEvent, pRecordSet, pViewContext)
|
|
427
449
|
{
|
|
428
450
|
if (pEvent) pEvent.preventDefault();
|
|
451
|
+
this.bumpRenderEpoch();
|
|
429
452
|
this.pict.providers.FilterDataProvider.removeLastUsedFilterExperience(pRecordSet, pViewContext);
|
|
430
453
|
this.pict.log.info(`Clearing filters for record set: ${pRecordSet} in view context: ${pViewContext}`);
|
|
431
454
|
// Apply default filter experience if it exists, otherwise just clear
|
|
@@ -479,6 +502,7 @@ class ViewRecordSetSUBSETFilters extends libPictView
|
|
|
479
502
|
addFilter(pEvent, pRecordSet, pViewContext, pFilterKey, pClauseKey)
|
|
480
503
|
{
|
|
481
504
|
if (pEvent) pEvent.preventDefault();
|
|
505
|
+
this.bumpRenderEpoch();
|
|
482
506
|
this.pict.log.info(`Adding filter: ${pFilterKey} with clause: ${pClauseKey} to record set: ${pRecordSet} in view context: ${pViewContext}`);
|
|
483
507
|
this.pict.providers[`RSP-Provider-${pRecordSet}`].addFilterClause(pFilterKey, pClauseKey);
|
|
484
508
|
//FIXME: we need the record from the original render here but no longer have it...
|
|
@@ -495,6 +519,7 @@ class ViewRecordSetSUBSETFilters extends libPictView
|
|
|
495
519
|
removeFilter(pEvent, pRecordSet, pViewContext, pSpecificFilterKey)
|
|
496
520
|
{
|
|
497
521
|
if (pEvent) pEvent.preventDefault();
|
|
522
|
+
this.bumpRenderEpoch();
|
|
498
523
|
this.pict.log.info(`Removing filter: ${pSpecificFilterKey} from record set: ${pRecordSet} in view context: ${pViewContext}`);
|
|
499
524
|
this.pict.providers[`RSP-Provider-${pRecordSet}`].removeFilterClause(pSpecificFilterKey);
|
|
500
525
|
//FIXME: we need the record from the original render here but no longer have it...
|