@loadbare/app 0.9.0 → 0.10.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/README.md +3 -3
- package/dist/build/assemble.d.ts.map +1 -1
- package/dist/build/assemble.js +74 -66
- package/dist/build/assemble.js.map +1 -1
- package/dist/build/expand.d.ts.map +1 -1
- package/dist/build/expand.js +20 -19
- package/dist/build/expand.js.map +1 -1
- package/dist/build/locations.d.ts +2 -3
- package/dist/build/locations.d.ts.map +1 -1
- package/dist/build/locations.js +2 -3
- package/dist/build/locations.js.map +1 -1
- package/dist/build/pages.d.ts +3 -4
- package/dist/build/pages.d.ts.map +1 -1
- package/dist/build/pages.js +3 -4
- package/dist/build/pages.js.map +1 -1
- package/dist/core/lb-constants.d.ts +25 -24
- package/dist/core/lb-constants.d.ts.map +1 -1
- package/dist/core/lb-constants.js +95 -168
- package/dist/core/lb-constants.js.map +1 -1
- package/dist/core/lb-types.d.ts +64 -77
- package/dist/core/lb-types.d.ts.map +1 -1
- package/dist/core/lb-types.js +40 -7
- package/dist/core/lb-types.js.map +1 -1
- package/dist/hub/lb-apply.d.ts +47 -37
- package/dist/hub/lb-apply.d.ts.map +1 -1
- package/dist/hub/lb-apply.js +174 -193
- package/dist/hub/lb-apply.js.map +1 -1
- package/dist/hub/lb-hub.browser.d.ts +1 -1
- package/dist/hub/lb-hub.browser.d.ts.map +1 -1
- package/dist/hub/lb-hub.browser.js +411 -449
- package/dist/hub/lb-hub.browser.js.map +1 -1
- package/dist/server/lb-express.d.ts +5 -5
- package/dist/server/lb-express.d.ts.map +1 -1
- package/dist/server/lb-express.js +35 -66
- package/dist/server/lb-express.js.map +1 -1
- package/dist/server/lb-server.d.ts +77 -135
- package/dist/server/lb-server.d.ts.map +1 -1
- package/dist/server/lb-server.js +132 -79
- package/dist/server/lb-server.js.map +1 -1
- package/docs/TECHREF-1.0.md +861 -587
- package/docs/comparison.md +222 -185
- package/docs/prior-art.md +15 -14
- package/docs/reference/builder.md +9 -3
- package/docs/reference/chrome.md +107 -56
- package/docs/reference/custom-elements.md +199 -173
- package/docs/reference/data-binding.md +374 -374
- package/docs/reference/overview.md +12 -10
- package/docs/reference/page-files.md +135 -99
- package/docs/reference/server.md +2 -2
- package/docs/reference/widgets.md +104 -110
- package/docs/roadmap.md +32 -39
- package/docs/terms-of-art.md +57 -0
- package/docs/testing.md +97 -68
- package/docs/theory.md +92 -58
- package/docs/tutorials/010-pages-and-navigation.md +20 -12
- package/docs/tutorials/020-css.md +6 -3
- package/docs/tutorials/030-html-decomposition.md +9 -7
- package/docs/tutorials/040-displaying-data.md +30 -13
- package/docs/tutorials/{050-actions.md → 050-requests.md} +25 -15
- package/docs/tutorials/060-custom-element-code.md +17 -16
- package/docs/tutorials/065-conditional-rendering.md +34 -23
- package/docs/tutorials/070-displaying-a-list.md +29 -21
- package/docs/tutorials/072-inserting-into-a-list.md +24 -16
- package/docs/tutorials/074-deleting-from-a-list.md +9 -7
- package/docs/tutorials/076-updating-a-list-item.md +11 -10
- package/docs/tutorials/080-widget-requests.md +71 -43
- package/docs/tutorials/090-using-widget-libraries.md +22 -22
- package/package.json +1 -1
- package/skills/loadbare-app/SKILL.md +178 -122
- package/skills/loadbare-app/references/TECHREF-1.0.md +861 -587
- package/skills/loadbare-app/references/builder.md +9 -3
- package/skills/loadbare-app/references/chrome.md +107 -56
- package/skills/loadbare-app/references/custom-elements.md +199 -173
- package/skills/loadbare-app/references/data-binding.md +374 -374
- package/skills/loadbare-app/references/overview.md +12 -10
- package/skills/loadbare-app/references/page-files.md +135 -99
- package/skills/loadbare-app/references/server.md +2 -2
- package/skills/loadbare-app/references/widgets.md +104 -110
- package/docs/analysis-accidental-complexity.md +0 -149
- package/docs/analysis-closed-set.md +0 -210
package/dist/hub/lb-apply.js
CHANGED
|
@@ -1,26 +1,27 @@
|
|
|
1
1
|
/// <reference lib="dom" />
|
|
2
2
|
/**
|
|
3
|
-
* Landing
|
|
3
|
+
* Landing — see docs/reference/data-binding.md.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* the same operation, and the only difference is how much of the page the
|
|
9
|
-
* root covers.
|
|
5
|
+
* The hub lands a response item's rows on every element whose `lb-query`
|
|
6
|
+
* names its query. The item says the query's kind and key, so the markup
|
|
7
|
+
* says neither:
|
|
10
8
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
9
|
+
* kind row template the hub
|
|
10
|
+
* row no lands the row on the element itself
|
|
11
|
+
* row yes lands one live row
|
|
12
|
+
* rows yes lands one live row per row
|
|
13
|
+
* rows no lands nothing
|
|
14
|
+
*
|
|
15
|
+
* Filling a live row and filling an element a `row` lands on are the same
|
|
16
|
+
* operation, and the only difference is how much of the page the root
|
|
17
|
+
* covers.
|
|
17
18
|
*
|
|
18
19
|
* A condition is the one other thing landing moves. An element whose
|
|
19
20
|
* `lb-show` column is off stands in the document inside a template, and
|
|
20
21
|
* landing reaches in there as it reaches everywhere else, so a branch that
|
|
21
22
|
* returns is already current. Nothing but landing reaches in.
|
|
22
23
|
*/
|
|
23
|
-
import {
|
|
24
|
+
import { ATTR_COLUMN, ATTR_COLUMN_VALUE, ATTR_KEY_VALUE, ATTR_QUERY, ATTR_QUERY_ROW_COUNT, ATTR_SHOW, KIND_ROW, } from "../core/lb-constants.js";
|
|
24
25
|
import { isPatch, } from "../core/lb-types.js";
|
|
25
26
|
/** The template holding an absent branch — see ATTR_SHOW. */
|
|
26
27
|
const PARKED = `template[${ATTR_SHOW}]`;
|
|
@@ -29,64 +30,73 @@ const FRAGMENT_NODE = 11;
|
|
|
29
30
|
/** The input types whose state is not their `value`, or cannot be set. */
|
|
30
31
|
const UNLANDED_INPUTS = ["checkbox", "radio", "file"];
|
|
31
32
|
/**
|
|
32
|
-
* A
|
|
33
|
-
* or an `<input>` of any type but checkbox, radio and file.
|
|
34
|
-
* gathering draw the same line, so a value read back from a form is the one
|
|
35
|
-
* that landed there.
|
|
33
|
+
* A native control whose state is its `value`: a `<select>`, a
|
|
34
|
+
* `<textarea>`, or an `<input>` of any type but checkbox, radio and file.
|
|
36
35
|
*
|
|
37
36
|
* Told apart by tag name and type rather than by class, so it holds for an
|
|
38
37
|
* element from any document.
|
|
39
38
|
*/
|
|
40
|
-
|
|
39
|
+
function isNativeControl(el) {
|
|
41
40
|
if (el.localName === "select" || el.localName === "textarea")
|
|
42
41
|
return true;
|
|
43
42
|
return (el.localName === "input" &&
|
|
44
43
|
!UNLANDED_INPUTS.includes(el.type));
|
|
45
44
|
}
|
|
46
45
|
/**
|
|
47
|
-
* A
|
|
48
|
-
*
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
|
|
46
|
+
* A form-associated custom element with a `value` property. HTML marks one
|
|
47
|
+
* with `static formAssociated = true` on its class, and a control has a value.
|
|
48
|
+
*/
|
|
49
|
+
export function isFormAssociated(el) {
|
|
50
|
+
const ctor = el.constructor;
|
|
51
|
+
return ctor.formAssociated === true && "value" in el;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* A control: an `<input>`, `<select>` or `<textarea>`, or a form-associated
|
|
55
|
+
* custom element with a `value` property. Landing and gathering draw the
|
|
56
|
+
* same line, so a value read back is the one that landed there.
|
|
57
|
+
*/
|
|
58
|
+
export function isControl(el) {
|
|
59
|
+
return isNativeControl(el) || isFormAssociated(el);
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Set an element from a column. A control's `value` is set. A custom element
|
|
63
|
+
* that is not a control keeps its content, which the builder placed there
|
|
64
|
+
* from its element file, and renders the stamp instead. Any other element's
|
|
65
|
+
* text content is set. Every one carries the value as `lb-column-value`.
|
|
59
66
|
*/
|
|
60
67
|
function land(el, value) {
|
|
61
68
|
// Untouched, whatever the server sent: the browser decides what a
|
|
62
69
|
// non-string looks like. See Row in core/lb-types.ts.
|
|
63
|
-
if (
|
|
64
|
-
el
|
|
70
|
+
if (value === null || value === undefined) {
|
|
71
|
+
if (isControl(el))
|
|
72
|
+
el.value = "";
|
|
73
|
+
else if (!el.localName.includes("-") && el.localName !== "input") {
|
|
74
|
+
el.textContent = "";
|
|
75
|
+
}
|
|
76
|
+
el.removeAttribute(ATTR_COLUMN_VALUE);
|
|
77
|
+
return;
|
|
65
78
|
}
|
|
66
|
-
|
|
79
|
+
if (isControl(el)) {
|
|
67
80
|
el.value = value;
|
|
68
|
-
el.setAttribute(ATTR_VALUE, value);
|
|
69
81
|
}
|
|
70
82
|
else if (el.localName === "input") {
|
|
71
83
|
console.warn(`lb-hub: a value does not land on <input type="${el.type}">, ignoring`, el);
|
|
84
|
+
return;
|
|
72
85
|
}
|
|
73
|
-
else {
|
|
86
|
+
else if (!el.localName.includes("-")) {
|
|
74
87
|
el.textContent = value;
|
|
75
|
-
el.setAttribute(ATTR_VALUE, value);
|
|
76
88
|
}
|
|
89
|
+
el.setAttribute(ATTR_COLUMN_VALUE, value);
|
|
77
90
|
}
|
|
78
91
|
/**
|
|
79
|
-
* Whether an element under `root` belongs to root's
|
|
80
|
-
*
|
|
92
|
+
* Whether an element under `root` belongs to root's query, rather than to a
|
|
93
|
+
* query nested inside it.
|
|
81
94
|
*
|
|
82
|
-
*
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
* `lb-list` and `lb-cell` is a cell of the row around it. Root's own
|
|
88
|
-
* attribute does not count either: root is the scope being filled, whatever
|
|
89
|
-
* it carries.
|
|
95
|
+
* A descendant is root's unless something between it and root carries
|
|
96
|
+
* `lb-query`. The element's own `lb-query` does not count: it names what the
|
|
97
|
+
* element holds, and the row it reads from is its ancestors'. So a picker
|
|
98
|
+
* carrying `lb-query` and `lb-column` is set from the row around it. Root's
|
|
99
|
+
* own attribute does not count either: root is what is being filled.
|
|
90
100
|
*
|
|
91
101
|
* Root may be the content of a template holding an absent branch. The walk
|
|
92
102
|
* up from an element inside it stops at the top of that content, which has
|
|
@@ -94,17 +104,17 @@ function land(el, value) {
|
|
|
94
104
|
*/
|
|
95
105
|
function inScope(root, el) {
|
|
96
106
|
for (let node = el.parentElement; node && node !== root;) {
|
|
97
|
-
if (node.hasAttribute(
|
|
107
|
+
if (node.hasAttribute(ATTR_QUERY))
|
|
98
108
|
return false;
|
|
99
|
-
}
|
|
100
109
|
node = node.parentElement;
|
|
101
110
|
}
|
|
102
111
|
return true;
|
|
103
112
|
}
|
|
104
113
|
/**
|
|
105
|
-
* Root if it matches, then every descendant in root's
|
|
106
|
-
* Gathering finds
|
|
107
|
-
* branch, so a
|
|
114
|
+
* Root if it matches, then every descendant in root's query that does.
|
|
115
|
+
* Gathering finds columns through here. It does not reach into an absent
|
|
116
|
+
* branch, so a live row is read back from the places a row lands that are
|
|
117
|
+
* showing.
|
|
108
118
|
*/
|
|
109
119
|
export function within(root, selector) {
|
|
110
120
|
const found = root.matches(selector) ? [root] : [];
|
|
@@ -115,11 +125,11 @@ export function within(root, selector) {
|
|
|
115
125
|
return found;
|
|
116
126
|
}
|
|
117
127
|
/**
|
|
118
|
-
* Every descendant in root's
|
|
128
|
+
* Every descendant in root's query that matches, absent branches included.
|
|
119
129
|
*
|
|
120
130
|
* `querySelectorAll` does not enter template content, so an element that is
|
|
121
131
|
* off would otherwise miss everything landed while it was away, and return
|
|
122
|
-
* stale or with its
|
|
132
|
+
* stale or with its rows empty. A template holding one is in root's query by
|
|
123
133
|
* the ordinary rule, and its content is searched as if it stood there.
|
|
124
134
|
*/
|
|
125
135
|
function below(root, selector) {
|
|
@@ -135,15 +145,9 @@ function below(root, selector) {
|
|
|
135
145
|
}
|
|
136
146
|
return found;
|
|
137
147
|
}
|
|
138
|
-
/** What `within` is for gathering, `reach` is for landing. */
|
|
139
|
-
function reach(root, selector) {
|
|
140
|
-
const found = below(root, selector);
|
|
141
|
-
return root.matches(selector) ? [root, ...found] : found;
|
|
142
|
-
}
|
|
143
148
|
/**
|
|
144
|
-
* Every element in
|
|
145
|
-
*
|
|
146
|
-
* nothing is filtered out by scope.
|
|
149
|
+
* Every element in root that matches, absent branches included. A query is
|
|
150
|
+
* looked up this way before any row is known, so nothing is filtered out.
|
|
147
151
|
*/
|
|
148
152
|
function everywhere(root, selector) {
|
|
149
153
|
const found = [...root.querySelectorAll(selector)];
|
|
@@ -158,9 +162,9 @@ function everywhere(root, selector) {
|
|
|
158
162
|
* says.
|
|
159
163
|
*
|
|
160
164
|
* Off is inside a template that stands where the element stood, carrying the
|
|
161
|
-
* same `lb-show`. The element is moved rather than rebuilt, so a
|
|
162
|
-
* its instance and a control keeps what was typed into it. On
|
|
163
|
-
* and the template goes.
|
|
165
|
+
* same `lb-show`. The element is moved rather than rebuilt, so a custom
|
|
166
|
+
* element keeps its instance and a control keeps what was typed into it. On
|
|
167
|
+
* moves it back and the template goes.
|
|
164
168
|
*
|
|
165
169
|
* Landing reaches into that template, so both it and the element it holds
|
|
166
170
|
* arrive here. The template decides; the element it holds, whose parent is
|
|
@@ -181,23 +185,27 @@ function present(el, on) {
|
|
|
181
185
|
parked.content.append(el);
|
|
182
186
|
}
|
|
183
187
|
/**
|
|
184
|
-
* Fill one
|
|
188
|
+
* Fill from one row: every `lb-column` and `lb-show` in root's query.
|
|
185
189
|
*
|
|
186
|
-
*
|
|
187
|
-
*
|
|
188
|
-
*
|
|
189
|
-
*
|
|
190
|
+
* A live row is its own row, so its root counts when it carries
|
|
191
|
+
* `lb-column`: `<option>`'s content model is text, so an option row has to
|
|
192
|
+
* be the column it displays. An element a `row` lands on is not its own
|
|
193
|
+
* ancestor, so its own `lb-column` reads from the row around it and is left
|
|
194
|
+
* alone here.
|
|
190
195
|
*/
|
|
191
|
-
export function applyRow(root, row) {
|
|
196
|
+
export function applyRow(root, row, liveRow = false) {
|
|
192
197
|
for (const [column, value] of Object.entries(row)) {
|
|
193
|
-
|
|
198
|
+
const selector = `[${ATTR_COLUMN}="${column}"]`;
|
|
199
|
+
const columns = below(root, selector);
|
|
200
|
+
if (liveRow && root.matches(selector))
|
|
201
|
+
columns.unshift(root);
|
|
202
|
+
for (const el of columns)
|
|
194
203
|
land(el, value);
|
|
195
|
-
}
|
|
196
204
|
// Only null and false are off. A string is never read, so "false" is on,
|
|
197
205
|
// and the query spells a condition as a boolean or a null.
|
|
198
206
|
//
|
|
199
|
-
// Never the root's own: a
|
|
200
|
-
//
|
|
207
|
+
// Never the root's own: a row template's root carries none, and the
|
|
208
|
+
// element a row lands on reads its condition from the row around it.
|
|
201
209
|
const on = value !== null && value !== false;
|
|
202
210
|
for (const el of below(root, `[${ATTR_SHOW}="${column}"]`)) {
|
|
203
211
|
present(el, on);
|
|
@@ -205,171 +213,144 @@ export function applyRow(root, row) {
|
|
|
205
213
|
}
|
|
206
214
|
}
|
|
207
215
|
/**
|
|
208
|
-
* The row template: the
|
|
209
|
-
*
|
|
210
|
-
*
|
|
211
|
-
*
|
|
212
|
-
* row comes first in document order.
|
|
213
|
-
*
|
|
214
|
-
* A scope with no template is bound to the list without displaying it.
|
|
215
|
-
* That is not a mistake, so it is silent. A template that does not name its
|
|
216
|
-
* key column is a mistake, and says so.
|
|
216
|
+
* The row template: the first `<template>` among the element's descendants,
|
|
217
|
+
* outside any nested `lb-query`. A template holding an absent branch is not a
|
|
218
|
+
* row template: rows land before the row template, so one inside the first
|
|
219
|
+
* live row comes first in document order.
|
|
217
220
|
*/
|
|
218
|
-
function templateIn(
|
|
219
|
-
const template = within(
|
|
220
|
-
|
|
221
|
-
return null;
|
|
222
|
-
if (!template.getAttribute(ATTR_KEY)) {
|
|
223
|
-
console.error(`lb-hub: the <template> in <${scope.localName}> has no ${ATTR_KEY} ` +
|
|
224
|
-
`naming the column that identifies a row`);
|
|
225
|
-
return null;
|
|
226
|
-
}
|
|
227
|
-
return template;
|
|
221
|
+
export function templateIn(el) {
|
|
222
|
+
const template = within(el, `template:not([${ATTR_SHOW}])`).find((t) => t !== el);
|
|
223
|
+
return template ?? null;
|
|
228
224
|
}
|
|
229
225
|
/**
|
|
230
|
-
* The rows already showing, by the key
|
|
231
|
-
*
|
|
232
|
-
*
|
|
233
|
-
* A row of a nested scope is that scope's. A row's own `lb-list` or `lb-row`
|
|
234
|
-
* names what it displays, not which list it is a row of, which is the same
|
|
235
|
-
* test a cell gets.
|
|
226
|
+
* The live rows already showing, by the key each carries. A row of a nested
|
|
227
|
+
* query is that query's.
|
|
236
228
|
*/
|
|
237
|
-
function showing(
|
|
229
|
+
function showing(el) {
|
|
238
230
|
const rows = new Map();
|
|
239
|
-
for (const
|
|
240
|
-
if (inScope(
|
|
241
|
-
rows.set(
|
|
242
|
-
}
|
|
231
|
+
for (const row of el.querySelectorAll(`[${ATTR_KEY_VALUE}]`)) {
|
|
232
|
+
if (inScope(el, row))
|
|
233
|
+
rows.set(row.getAttribute(ATTR_KEY_VALUE), row);
|
|
243
234
|
}
|
|
244
235
|
return rows;
|
|
245
236
|
}
|
|
246
237
|
/**
|
|
247
|
-
* Land
|
|
238
|
+
* Land rows through a row template.
|
|
248
239
|
*
|
|
249
|
-
*
|
|
250
|
-
*
|
|
251
|
-
*
|
|
252
|
-
*
|
|
240
|
+
* All rows decide membership and order: every row is placed in the order
|
|
241
|
+
* given, and a row whose key did not arrive is gone. A patch disturbs only
|
|
242
|
+
* what it names — a row it did not mention keeps its contents and its
|
|
243
|
+
* position.
|
|
253
244
|
*
|
|
254
|
-
* A
|
|
255
|
-
* it knows whether it sorts or groups. Without it a row lands
|
|
256
|
-
* before the template, so rows accumulate in the order they
|
|
257
|
-
* template stays put as the insertion marker.
|
|
245
|
+
* A custom element that carries `lbPlaceRow` decides where a row goes,
|
|
246
|
+
* because only it knows whether it sorts or groups. Without it a row lands
|
|
247
|
+
* immediately before the template, so rows accumulate in the order they
|
|
248
|
+
* arrive and the template stays put as the insertion marker.
|
|
258
249
|
*/
|
|
259
|
-
export function
|
|
260
|
-
const
|
|
261
|
-
|
|
262
|
-
return;
|
|
263
|
-
const keyColumn = template.getAttribute(ATTR_KEY);
|
|
264
|
-
const shown = showing(scope);
|
|
265
|
-
const host = scope;
|
|
250
|
+
export function applyRows(el, template, keyColumn, result) {
|
|
251
|
+
const shown = showing(el);
|
|
252
|
+
const host = el;
|
|
266
253
|
const whole = !isPatch(result);
|
|
267
|
-
const place = (
|
|
254
|
+
const place = (row, data) => {
|
|
268
255
|
if (host.lbPlaceRow)
|
|
269
|
-
host.lbPlaceRow(
|
|
256
|
+
host.lbPlaceRow(row, data, template);
|
|
270
257
|
else
|
|
271
|
-
template.parentElement.insertBefore(
|
|
258
|
+
template.parentElement.insertBefore(row, template);
|
|
272
259
|
};
|
|
273
|
-
const upsert = (
|
|
274
|
-
if (
|
|
275
|
-
console.error(`lb-hub: a row for <${
|
|
260
|
+
const upsert = (data) => {
|
|
261
|
+
if (data[keyColumn] === undefined) {
|
|
262
|
+
console.error(`lb-hub: a row for <${el.localName}> has no '${keyColumn}' column`);
|
|
276
263
|
return null;
|
|
277
264
|
}
|
|
278
265
|
// The key is stored on the row as an attribute and read back from
|
|
279
266
|
// there, so it is compared as the string the attribute holds.
|
|
280
|
-
const key = String(
|
|
281
|
-
let
|
|
282
|
-
const fresh =
|
|
283
|
-
if (!
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
267
|
+
const key = String(data[keyColumn]);
|
|
268
|
+
let row = shown.get(key);
|
|
269
|
+
const fresh = row === undefined;
|
|
270
|
+
if (!row) {
|
|
271
|
+
// Imported rather than cloned: template content belongs to a document
|
|
272
|
+
// with no custom element definitions, so a clone would not upgrade
|
|
273
|
+
// until inserted, and a form-associated control would be filled as if
|
|
274
|
+
// it were not one.
|
|
275
|
+
row = el.ownerDocument.importNode(template.content.firstElementChild, true);
|
|
276
|
+
row.setAttribute(ATTR_KEY_VALUE, key);
|
|
277
|
+
shown.set(key, row);
|
|
287
278
|
}
|
|
288
279
|
// Fill before insertion. The attributes are already there when the row
|
|
289
|
-
// upgrades, which is the same thing that makes
|
|
290
|
-
// operation everywhere else.
|
|
291
|
-
applyRow(
|
|
280
|
+
// upgrades, which is the same thing that makes a first landing and a
|
|
281
|
+
// refresh one operation everywhere else.
|
|
282
|
+
applyRow(row, data, true);
|
|
292
283
|
if (fresh || whole)
|
|
293
|
-
place(
|
|
284
|
+
place(row, data);
|
|
294
285
|
return key;
|
|
295
286
|
};
|
|
296
287
|
if (Array.isArray(result)) {
|
|
297
288
|
const arrived = new Set();
|
|
298
|
-
for (const
|
|
299
|
-
const key = upsert(
|
|
289
|
+
for (const data of result) {
|
|
290
|
+
const key = upsert(data);
|
|
300
291
|
if (key !== null)
|
|
301
292
|
arrived.add(key);
|
|
302
293
|
}
|
|
303
|
-
for (const [key,
|
|
294
|
+
for (const [key, row] of shown)
|
|
304
295
|
if (!arrived.has(key))
|
|
305
|
-
|
|
296
|
+
row.remove();
|
|
306
297
|
}
|
|
307
298
|
else {
|
|
308
|
-
for (const
|
|
309
|
-
upsert(
|
|
299
|
+
for (const data of result.rows ?? [])
|
|
300
|
+
upsert(data);
|
|
310
301
|
for (const key of result.drop ?? [])
|
|
311
302
|
shown.get(String(key))?.remove();
|
|
312
303
|
}
|
|
313
|
-
//
|
|
314
|
-
//
|
|
315
|
-
//
|
|
316
|
-
//
|
|
317
|
-
|
|
318
|
-
// one conditional a page cannot be sent, since the server answers with rows
|
|
319
|
-
// and says nothing about how many survived reconciliation. A page says what
|
|
320
|
-
// an empty list looks like in a stylesheet, and no list widget carries code
|
|
321
|
-
// for it. See docs/reference/data-binding.md.
|
|
322
|
-
scope.setAttribute(ATTR_ROW_COUNT, String(showing(scope).size));
|
|
304
|
+
// Counted from the DOM rather than from either branch above, so all rows
|
|
305
|
+
// and a patch report the same fact the same way. Only here is the count
|
|
306
|
+
// known: the server answers with rows and says nothing about how many
|
|
307
|
+
// survived reconciliation.
|
|
308
|
+
el.setAttribute(ATTR_QUERY_ROW_COUNT, String(showing(el).size));
|
|
323
309
|
// Derived scaffolding — a section heading, an <optgroup> — goes when its
|
|
324
|
-
// last row does, and only the
|
|
310
|
+
// last row does, and only the custom element knows it exists.
|
|
325
311
|
host.lbRowsLanded?.();
|
|
326
312
|
}
|
|
327
|
-
/** Land
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
const
|
|
332
|
-
if (
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
}
|
|
336
|
-
// Cardinality is a property of the name, so one name is a list or a row
|
|
337
|
-
// and never both. Two spellings of it on one page is a mistake in the
|
|
338
|
-
// markup rather than a case to reconcile.
|
|
339
|
-
if (lists.length > 0 && rows.length > 0) {
|
|
340
|
-
console.error(`lb-hub: '${name}' is bound as a list in one place and a row in ` +
|
|
341
|
-
`another; a name answers with one shape`);
|
|
342
|
-
continue;
|
|
313
|
+
/** Land one response item on one element that names its query. */
|
|
314
|
+
function landItem(el, item) {
|
|
315
|
+
const template = templateIn(el);
|
|
316
|
+
if (item.kind === KIND_ROW) {
|
|
317
|
+
const row = item.row ?? {};
|
|
318
|
+
if (template) {
|
|
319
|
+
applyRows(el, template, item.key, [row]);
|
|
320
|
+
return;
|
|
343
321
|
}
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
}
|
|
349
|
-
// The only disagreement visible from here: a patch and a row are both
|
|
350
|
-
// objects, so an array arriving at a row scope is the one case the
|
|
351
|
-
// browser can name. createHub holds the stronger check, because it knows
|
|
352
|
-
// what the query declared.
|
|
353
|
-
if (Array.isArray(result)) {
|
|
354
|
-
console.error(`lb-hub: '${name}' is bound with ${ATTR_ROW} but answered with rows`);
|
|
355
|
-
continue;
|
|
322
|
+
applyRow(el, row);
|
|
323
|
+
const key = row[item.key];
|
|
324
|
+
if (key !== undefined && key !== null) {
|
|
325
|
+
el.setAttribute(ATTR_KEY_VALUE, String(key));
|
|
356
326
|
}
|
|
357
|
-
|
|
358
|
-
applyRow(scope, result);
|
|
327
|
+
return;
|
|
359
328
|
}
|
|
329
|
+
// A `rows` query with no row template lands nothing: an insert form names
|
|
330
|
+
// the query it adds to, and shows none of its rows.
|
|
331
|
+
if (!template)
|
|
332
|
+
return;
|
|
333
|
+
const result = item.patch ?? item.rows ?? [];
|
|
334
|
+
applyRows(el, template, item.key, result);
|
|
360
335
|
}
|
|
361
336
|
/**
|
|
362
|
-
* Land
|
|
363
|
-
* way a cell lands a column. A parm the URL does not carry lands empty, so a
|
|
364
|
-
* control shows what the address bar says after Back as well as after a
|
|
365
|
-
* reload: the URL is what is on screen, and nothing else is.
|
|
337
|
+
* Land response items. Every answer arrives through here.
|
|
366
338
|
*
|
|
367
|
-
*
|
|
368
|
-
*
|
|
339
|
+
* `quiet` names queries that may have nowhere to land without a warning: the
|
|
340
|
+
* hub's own `lb-url` lands wherever a chrome or page names it, and nowhere
|
|
341
|
+
* otherwise.
|
|
369
342
|
*/
|
|
370
|
-
export function
|
|
371
|
-
for (const
|
|
372
|
-
|
|
343
|
+
export function applyResponse(root, items, quiet = []) {
|
|
344
|
+
for (const item of items) {
|
|
345
|
+
const targets = everywhere(root, `[${ATTR_QUERY}="${item.query}"]`);
|
|
346
|
+
if (targets.length === 0) {
|
|
347
|
+
if (!quiet.includes(item.query)) {
|
|
348
|
+
console.warn(`lb-hub: nothing names '${item.query}', skipping`);
|
|
349
|
+
}
|
|
350
|
+
continue;
|
|
351
|
+
}
|
|
352
|
+
for (const el of targets)
|
|
353
|
+
landItem(el, item);
|
|
373
354
|
}
|
|
374
355
|
}
|
|
375
356
|
//# sourceMappingURL=lb-apply.js.map
|