@loadbare/app 0.9.0 → 0.11.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 +107 -90
- package/dist/build/assemble.js.map +1 -1
- package/dist/build/expand.d.ts +6 -1
- package/dist/build/expand.d.ts.map +1 -1
- package/dist/build/expand.js +112 -26
- 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 +27 -24
- package/dist/core/lb-constants.d.ts.map +1 -1
- package/dist/core/lb-constants.js +103 -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 +195 -199
- 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 +410 -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 +908 -585
- package/docs/comparison.md +243 -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 +291 -173
- package/docs/reference/data-binding.md +381 -374
- package/docs/reference/overview.md +12 -10
- package/docs/reference/page-files.md +164 -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/docs/what-does-loadbare-extend.md +124 -0
- package/package.json +1 -1
- package/skills/loadbare-app/SKILL.md +201 -123
- package/skills/loadbare-app/references/TECHREF-1.0.md +908 -585
- 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 +291 -173
- package/skills/loadbare-app/references/data-binding.md +381 -374
- package/skills/loadbare-app/references/overview.md +12 -10
- package/skills/loadbare-app/references/page-files.md +164 -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
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
# Custom Elements
|
|
2
2
|
|
|
3
|
-
A
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
A custom element is written as a set of files sharing one tag name. It
|
|
4
|
+
serves two purposes, and an application uses it for either or for both at
|
|
5
|
+
once.
|
|
6
6
|
|
|
7
7
|
| File | Holds |
|
|
8
8
|
|--------------------------|----------------------------------|
|
|
9
|
-
| `<tag-name>.html` | The markup the tag expands into
|
|
9
|
+
| `<tag-name>.html` | The element file: the markup the tag expands into |
|
|
10
10
|
| `<tag-name>.browser.ts` | The class the tag registers |
|
|
11
11
|
|
|
12
12
|
Write one of the two, or both, but write at least one. A tag with neither is
|
|
@@ -26,13 +26,13 @@ either.
|
|
|
26
26
|
Put the files anywhere under `src/`. The builder finds them by name, the
|
|
27
27
|
same way it finds pages — see [The Builder](./builder.md).
|
|
28
28
|
|
|
29
|
-
Loadbare uses light DOM throughout. A
|
|
29
|
+
Loadbare uses light DOM throughout. A custom element's markup is ordinary markup in
|
|
30
30
|
the document, visible in view-source, reachable by `closest()` and by the
|
|
31
31
|
application's stylesheets.
|
|
32
32
|
|
|
33
33
|
## HTML
|
|
34
34
|
|
|
35
|
-
An `.html` file named for a tag defines that tag. When the builder finds the
|
|
35
|
+
An element file is an `.html` file named for a tag, and defines that tag. When the builder finds the
|
|
36
36
|
tag in the chrome, in a page, or in another definition, it inserts the
|
|
37
37
|
definition's content as children of the tag. We call this process
|
|
38
38
|
'HTML expansion'.
|
|
@@ -44,13 +44,13 @@ application's.
|
|
|
44
44
|
### Parameters
|
|
45
45
|
|
|
46
46
|
A definition takes parameters. Three namespaces share the attributes of a
|
|
47
|
-
|
|
47
|
+
custom element's tag, and the prefix says which namespace an attribute is in:
|
|
48
48
|
|
|
49
|
-
| Prefix | Belongs to | Read
|
|
50
|
-
|
|
51
|
-
| `exp-` |
|
|
52
|
-
| `lb-` |
|
|
53
|
-
| No prefix | HTML | By the browser, as HTML says
|
|
49
|
+
| Prefix | Belongs to | Read |
|
|
50
|
+
|-------------|------------|-------------------------------|
|
|
51
|
+
| `exp-` | The definition | By the builder, at build time |
|
|
52
|
+
| `lb-` | Loadbare | By the builder and the hub |
|
|
53
|
+
| No prefix | HTML | By the browser, as HTML says |
|
|
54
54
|
|
|
55
55
|
Pass a parameter by writing `exp-<name>` on the tag. Read it in the
|
|
56
56
|
definition by writing `{{<name>}}`. The prefix marks the attribute as
|
|
@@ -64,11 +64,11 @@ supplies `{{label}}`:
|
|
|
64
64
|
|
|
65
65
|
```html
|
|
66
66
|
<!-- authored -->
|
|
67
|
-
<note-field
|
|
67
|
+
<note-field exp-label="Name"></note-field>
|
|
68
68
|
```
|
|
69
69
|
|
|
70
|
-
Write `class`, `title`, or any other unprefixed attribute on a
|
|
71
|
-
freely. They are HTML's, and never collide with a definition's parameters.
|
|
70
|
+
Write `class`, `title`, or any other unprefixed attribute on a custom
|
|
71
|
+
element freely. They are HTML's, and never collide with a definition's parameters.
|
|
72
72
|
|
|
73
73
|
Write a placeholder as an entire attribute value or an entire text node.
|
|
74
74
|
Nothing inside one is evaluated, and expansion has no data to branch on —
|
|
@@ -102,12 +102,12 @@ escaping.
|
|
|
102
102
|
|
|
103
103
|
### The slot
|
|
104
104
|
|
|
105
|
-
`lb-slot` marks the one element in a definition whose children receive
|
|
105
|
+
`lb-exp-slot` marks the one element in a definition whose children receive
|
|
106
106
|
whatever the author wrote inside the tag:
|
|
107
107
|
|
|
108
108
|
```html
|
|
109
109
|
<!-- definition: src/note-card.html -->
|
|
110
|
-
<article class="card"><div lb-slot></div></article>
|
|
110
|
+
<article class="card"><div lb-exp-slot></div></article>
|
|
111
111
|
```
|
|
112
112
|
|
|
113
113
|
```html
|
|
@@ -130,35 +130,35 @@ whatever the author wrote inside the tag:
|
|
|
130
130
|
</note-card>
|
|
131
131
|
```
|
|
132
132
|
|
|
133
|
-
The tag stays and the definition becomes its children.
|
|
134
|
-
|
|
133
|
+
The tag stays and the definition becomes its children. The builder removes
|
|
134
|
+
`lb-exp-slot` from what ships.
|
|
135
135
|
|
|
136
|
-
Give a definition at most one `lb-slot`. A definition with a slot and
|
|
136
|
+
Give a definition at most one `lb-exp-slot`. A definition with a slot and
|
|
137
137
|
nothing written inside it leaves the slot empty.
|
|
138
138
|
|
|
139
|
-
`lb-slot` is an attribute rather than an element because HTML's content
|
|
139
|
+
`lb-exp-slot` is an attribute rather than an element because HTML's content
|
|
140
140
|
model discards foreign elements inside `<select>` and `<table>`. A slot
|
|
141
141
|
marker has to survive on an element the surrounding tag already permits.
|
|
142
142
|
|
|
143
143
|
### Destinations
|
|
144
144
|
|
|
145
|
-
`lb-template` names a destination in a definition. On an authored
|
|
146
|
-
`<template lb-template="name">`, it names the destination that template
|
|
145
|
+
`lb-exp-template` names a destination in a definition. On an authored
|
|
146
|
+
`<template lb-exp-template="name">`, it names the destination that template
|
|
147
147
|
fills:
|
|
148
148
|
|
|
149
149
|
```html
|
|
150
150
|
<!-- definition: src/ledger-table.html -->
|
|
151
151
|
<table>
|
|
152
152
|
<caption>{{caption}}</caption>
|
|
153
|
-
<thead lb-template="head"></thead>
|
|
154
|
-
<tbody lb-slot></tbody>
|
|
153
|
+
<thead lb-exp-template="head"></thead>
|
|
154
|
+
<tbody lb-exp-slot></tbody>
|
|
155
155
|
</table>
|
|
156
156
|
```
|
|
157
157
|
|
|
158
158
|
```html
|
|
159
159
|
<!-- authored -->
|
|
160
160
|
<ledger-table exp-caption="Ledger">
|
|
161
|
-
<template lb-template="head">
|
|
161
|
+
<template lb-exp-template="head">
|
|
162
162
|
<tr><th>Date</th><th>Amount</th></tr>
|
|
163
163
|
</template>
|
|
164
164
|
<tbody>
|
|
@@ -180,15 +180,45 @@ an ordinary `<thead>` in view-source — not the template element.
|
|
|
180
180
|
|
|
181
181
|
### Recursion
|
|
182
182
|
|
|
183
|
-
A definition may use other
|
|
184
|
-
appears, so a
|
|
185
|
-
it.
|
|
183
|
+
A definition may use other custom elements. Expansion repeats until nothing
|
|
184
|
+
new appears, so a custom element built out of others needs nothing declared
|
|
185
|
+
for it.
|
|
186
186
|
|
|
187
187
|
Keep the definition graph acyclic. A cycle is a build error, reported as the
|
|
188
188
|
path that closes it.
|
|
189
189
|
|
|
190
|
-
Expansion runs over `<template>` contents wherever they occur, so a
|
|
191
|
-
row template ships already expanded, `{{placeholder}}` included.
|
|
190
|
+
Expansion runs over `<template>` contents wherever they occur, so a
|
|
191
|
+
definition's row template ships already expanded, `{{placeholder}}` included.
|
|
192
|
+
|
|
193
|
+
### Where a custom element can go
|
|
194
|
+
|
|
195
|
+
HTML limits what an element may hold. A `<p>` holds only phrasing content,
|
|
196
|
+
such as text, `<span>` and `<button>`, and a `<button>` may not hold another
|
|
197
|
+
`<button>`. The parser does not refuse markup that breaks these rules; it
|
|
198
|
+
rearranges it. `<p>Total <div>12</div></p>` becomes a paragraph, then a div,
|
|
199
|
+
then an empty paragraph, with nothing logged.
|
|
200
|
+
|
|
201
|
+
Each file is parsed on its own, so a page file and an element file each get
|
|
202
|
+
the tree the browser would give them. Expansion then places one inside the
|
|
203
|
+
other, and that join is where a rule can break. A definition containing a
|
|
204
|
+
`<div>` or a `<dialog>` cannot go inside a `<p>`, since the browser would
|
|
205
|
+
close the paragraph early and move the rest of the definition out of its
|
|
206
|
+
element. The build refuses such a page, naming the file, the custom element
|
|
207
|
+
and the path to what would move:
|
|
208
|
+
|
|
209
|
+
```
|
|
210
|
+
assemble: 'src/pages/ledger.page.html': expand: <lb-confirm> puts <dialog>
|
|
211
|
+
where the browser's parser would not keep it, at p > lb-confirm > dialog.
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
Put a custom element whose definition holds block content, such as a
|
|
215
|
+
`<div>`, a `<dialog>` or a `<p>`, in a `<div>`, a `<li>`, a `<td>` or another
|
|
216
|
+
element that holds flow content.
|
|
217
|
+
|
|
218
|
+
Never write a custom element inside a `<select>`, an `<option>` or a
|
|
219
|
+
`<textarea>`. The parser drops the tag inside the first two, and inside a
|
|
220
|
+
`<textarea>` it is text. Both happen when the page file is read, before
|
|
221
|
+
expansion, so the build cannot see the element to report it.
|
|
192
222
|
|
|
193
223
|
### What fails at build time
|
|
194
224
|
|
|
@@ -199,54 +229,181 @@ shipped:
|
|
|
199
229
|
- a cycle in the definition graph
|
|
200
230
|
- an `exp-` attribute the definition never declared
|
|
201
231
|
- a `{{placeholder}}` spelled so that no `exp-` attribute could supply it
|
|
202
|
-
- content written inside a tag whose definition has no `lb-slot`
|
|
203
|
-
- more than one `lb-slot` in one definition
|
|
204
|
-
- a `<template lb-template="name">` naming a destination the definition
|
|
232
|
+
- content written inside a tag whose definition has no `lb-exp-slot`
|
|
233
|
+
- more than one `lb-exp-slot` in one definition
|
|
234
|
+
- a `<template lb-exp-template="name">` naming a destination the definition
|
|
205
235
|
does not have
|
|
206
236
|
- two templates for the same destination
|
|
237
|
+
- a custom element placed where the browser's parser would move its
|
|
238
|
+
contents; see [Where a custom element can go](#where-a-custom-element-can-go)
|
|
207
239
|
|
|
208
240
|
An unfilled `{{placeholder}}` is not among these. That is presence
|
|
209
241
|
propagation working as intended, indistinguishable from an author who left
|
|
210
242
|
an optional parameter out.
|
|
211
243
|
|
|
244
|
+
|
|
212
245
|
## Code
|
|
213
246
|
|
|
214
|
-
A `<tag>.browser.ts` file registers that tag's class.
|
|
215
|
-
ordinary custom element
|
|
216
|
-
in data binding
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
Import every
|
|
220
|
-
as a string literal
|
|
221
|
-
|
|
222
|
-
| Constant
|
|
223
|
-
|
|
224
|
-
| `
|
|
225
|
-
| `
|
|
226
|
-
| `ATTR_SHOW`
|
|
227
|
-
| `
|
|
228
|
-
| `
|
|
229
|
-
| `
|
|
230
|
-
| `
|
|
231
|
-
| `
|
|
232
|
-
| `
|
|
233
|
-
| `
|
|
234
|
-
| `
|
|
235
|
-
| `
|
|
236
|
-
| `
|
|
247
|
+
A `<tag>.browser.ts` file registers that tag's class. The class is an
|
|
248
|
+
ordinary custom element, and Loadbare imposes no base class. A custom element
|
|
249
|
+
takes part in data binding in one of four ways: it is a control, it renders
|
|
250
|
+
a value, it sends a request, or it holds rows.
|
|
251
|
+
|
|
252
|
+
Import every Loadbare name from `@loadbare/app/constants`, and never write
|
|
253
|
+
one as a string literal:
|
|
254
|
+
|
|
255
|
+
| Constant | Value |
|
|
256
|
+
|------------------------|----------------------|
|
|
257
|
+
| `ATTR_QUERY` | `lb-query` |
|
|
258
|
+
| `ATTR_COLUMN` | `lb-column` |
|
|
259
|
+
| `ATTR_SHOW` | `lb-show` |
|
|
260
|
+
| `ATTR_REQUEST` | `lb-request` |
|
|
261
|
+
| `ATTR_COLUMN_VALUE` | `lb-column-value` |
|
|
262
|
+
| `ATTR_KEY_VALUE` | `lb-key-value` |
|
|
263
|
+
| `ATTR_ROW_LIVE` | `lb-row-live` |
|
|
264
|
+
| `LIVE_ROW` | `[lb-row-live]` |
|
|
265
|
+
| `ATTR_QUERY_ROW_COUNT` | `lb-query-row-count` |
|
|
266
|
+
| `ATTR_REQUEST_PENDING` | `lb-request-pending` |
|
|
267
|
+
| `ATTR_REQUEST_ERROR` | `lb-request-error` |
|
|
268
|
+
| `REQUEST_ROW_INSERT`, `REQUEST_ROW_UPDATE`, `REQUEST_ROW_DELETE` | The requests Loadbare provides |
|
|
269
|
+
| `LB_ROW_REQUESTS` | All three, in one array |
|
|
270
|
+
| `LB_EVENT_NAME` | `lb-request`, the event every request travels as |
|
|
271
|
+
| `URL_QUERY` | `lb-url` |
|
|
272
|
+
| `URL_COLUMN_PATH`, `URL_COLUMN_PAGE_LABEL`, `URL_COLUMN_PAGE_UNKNOWN` | `lb-path`, `lb-page-label`, `lb-page-unknown` |
|
|
273
|
+
| `LB_RESERVED_PREFIX` | `lb-` |
|
|
274
|
+
|
|
275
|
+
A custom element reads `lb-` attributes and never assigns one. The developer
|
|
276
|
+
writes them in markup, and the hub and the builder write their stamps.
|
|
277
|
+
|
|
278
|
+
Loadbare reserves method names beginning with `lb` on a custom element, for
|
|
279
|
+
the methods it calls; see [Holding rows](#holding-rows).
|
|
280
|
+
|
|
281
|
+
### When its code runs
|
|
282
|
+
|
|
283
|
+
The hub creates, places and parks elements, and each of those reaches a
|
|
284
|
+
custom element as the browser's lifecycle callbacks. It is the same instance
|
|
285
|
+
from its constructor on, however often it moves:
|
|
286
|
+
|
|
287
|
+
| What the hub does | Callbacks, in order |
|
|
288
|
+
|----------------------------------------------------|---------------------------------------------|
|
|
289
|
+
| Shows a page | `constructor`, `connectedCallback` |
|
|
290
|
+
| Creates a live row, filled before it is placed | `constructor` |
|
|
291
|
+
| Places a live row for the first time | `connectedCallback` |
|
|
292
|
+
| Places it again, as it does on every set of all rows | `disconnectedCallback`, `connectedCallback` |
|
|
293
|
+
| Removes a live row | `disconnectedCallback` |
|
|
294
|
+
| Turns `lb-show` off | `disconnectedCallback`, `adoptedCallback` |
|
|
295
|
+
| Turns `lb-show` on | `adoptedCallback`, `connectedCallback` |
|
|
296
|
+
| Turns on an element the builder shipped absent | `constructor`, `connectedCallback` |
|
|
297
|
+
|
|
298
|
+
The builder ships every `lb-show` element inside its template, and nothing
|
|
299
|
+
in a template is upgraded, so such an element has no instance until its
|
|
300
|
+
column first turns on. See
|
|
301
|
+
[Conditional rendering](./data-binding.md#conditional-rendering).
|
|
302
|
+
|
|
303
|
+
So each kind of work has one place:
|
|
304
|
+
|
|
305
|
+
1. **A listener on the element itself goes in the constructor.** It is
|
|
306
|
+
attached once and survives every move, and a live row gets its own
|
|
307
|
+
because each row is a new instance. An event from a child bubbles to the
|
|
308
|
+
element, so the listener needs no child to exist yet. The constructor
|
|
309
|
+
reads no attribute and no child: an element made with
|
|
310
|
+
`document.createElement` has neither when it runs.
|
|
311
|
+
2. **A child is looked up when it is needed**, in a handler, a getter,
|
|
312
|
+
`lbPlaceRow` or `lbRowsLanded`, and never held from setup.
|
|
313
|
+
3. **`connectedCallback` runs on every move**, so it is written to run
|
|
314
|
+
again. It rearranges the element's own children into a state it checks
|
|
315
|
+
for first, or adds a listener to `document` or `window`, which
|
|
316
|
+
`disconnectedCallback` removes. Work done once per instance that needs
|
|
317
|
+
the element's attributes, such as a control taking up a value that landed
|
|
318
|
+
before it upgraded, goes behind a flag there; see
|
|
319
|
+
[Being a control](#being-a-control).
|
|
320
|
+
|
|
321
|
+
### Being a control
|
|
322
|
+
|
|
323
|
+
A form-associated custom element with a `value` property that fires `change`
|
|
324
|
+
is a control, the same as an `<input>`. The hub sets its `value` from the
|
|
325
|
+
column its `lb-column` names, gathers its `value` back under that column,
|
|
326
|
+
and commits its `lb-request` on `change`:
|
|
327
|
+
|
|
328
|
+
```ts
|
|
329
|
+
// src/star-rating.browser.ts
|
|
330
|
+
import { ATTR_COLUMN_VALUE } from "@loadbare/app/constants";
|
|
331
|
+
|
|
332
|
+
class StarRating extends HTMLElement {
|
|
333
|
+
static formAssociated = true;
|
|
334
|
+
#internals = this.attachInternals();
|
|
335
|
+
#value = "";
|
|
336
|
+
#connected = false;
|
|
337
|
+
|
|
338
|
+
connectedCallback() {
|
|
339
|
+
if (this.#connected) return;
|
|
340
|
+
this.#connected = true;
|
|
341
|
+
const stamped = this.getAttribute(ATTR_COLUMN_VALUE);
|
|
342
|
+
if (stamped !== null) this.value = stamped;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
get value(): string {
|
|
346
|
+
return this.#value;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
set value(value: string) {
|
|
350
|
+
this.#value = value ?? "";
|
|
351
|
+
this.#internals.setFormValue(this.#value);
|
|
352
|
+
this.render();
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
formResetCallback() {
|
|
356
|
+
this.value = "";
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
pick(stars: number) {
|
|
360
|
+
this.value = String(stars);
|
|
361
|
+
this.dispatchEvent(new Event("change", { bubbles: true }));
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
render() {
|
|
365
|
+
// Draw this.#value stars.
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
customElements.define("star-rating", StarRating);
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
```html
|
|
373
|
+
<star-rating lb-column="rating" lb-request="lb-row-update"></star-rating>
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
Dispatch `change` with `bubbles: true`, as a native control's does, when the
|
|
377
|
+
user commits an edit. Setting `value` from the hub fires nothing.
|
|
378
|
+
|
|
379
|
+
A control can receive its value before it is a control. An element the
|
|
380
|
+
builder shipped absent, or one whose definition loads after the hub has
|
|
381
|
+
landed, is not upgraded when its column lands, so the hub cannot set its
|
|
382
|
+
`value` and the value arrives as `lb-column-value` alone. Take the stamp up
|
|
383
|
+
the first time the control connects, as `connectedCallback` does above, and
|
|
384
|
+
never again: after that the hub sets `value` itself, and a later move must
|
|
385
|
+
not undo an edit.
|
|
386
|
+
|
|
387
|
+
A form owns a form-associated custom element the way it owns an `<input>`,
|
|
388
|
+
including through the HTML `form` attribute, so a form gathers it on submit.
|
|
389
|
+
Write `formResetCallback` to restore the default value: the hub resets a form
|
|
390
|
+
after a successful insert.
|
|
391
|
+
|
|
392
|
+
The `<lb-input>`, `<lb-select>`, `<lb-options>` and `<lb-picker>` widgets
|
|
393
|
+
are controls; see [The Basic Widget Library](./widgets.md).
|
|
237
394
|
|
|
238
395
|
### Receiving a value
|
|
239
396
|
|
|
240
|
-
A
|
|
241
|
-
|
|
242
|
-
|
|
397
|
+
A custom element that is not a control keeps the content the builder placed
|
|
398
|
+
in it, and receives a column as its `lb-column-value` attribute. Observe it,
|
|
399
|
+
and render the value:
|
|
243
400
|
|
|
244
401
|
```ts
|
|
245
402
|
// src/visit-count.browser.ts
|
|
246
|
-
import {
|
|
403
|
+
import { ATTR_COLUMN_VALUE } from "@loadbare/app/constants";
|
|
247
404
|
|
|
248
405
|
class VisitCount extends HTMLElement {
|
|
249
|
-
static observedAttributes = [
|
|
406
|
+
static observedAttributes = [ATTR_COLUMN_VALUE];
|
|
250
407
|
|
|
251
408
|
attributeChangedCallback(_name: string, _old: string, value: string) {
|
|
252
409
|
this.textContent = `visited ${value} times`;
|
|
@@ -256,142 +413,103 @@ class VisitCount extends HTMLElement {
|
|
|
256
413
|
customElements.define("visit-count", VisitCount);
|
|
257
414
|
```
|
|
258
415
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
requires.
|
|
272
|
-
|
|
273
|
-
List `ATTR_VALUE` in `observedAttributes`, or `attributeChangedCallback`
|
|
274
|
-
never fires. The browser calls it for an attribute already present when an
|
|
275
|
-
element upgrades, not only for one that changes afterward, so a widget's
|
|
276
|
-
first render and every later refresh go through the one callback. There is
|
|
277
|
-
no separate hydration path to write.
|
|
416
|
+
```html
|
|
417
|
+
<p lb-query="visits"><visit-count lb-column="count"></visit-count></p>
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
List `ATTR_COLUMN_VALUE` in `observedAttributes`. The browser calls
|
|
421
|
+
`attributeChangedCallback` for an attribute already present when an element
|
|
422
|
+
upgrades, as well as for one that changes afterward, so the first render and
|
|
423
|
+
every later one go through the one callback.
|
|
424
|
+
|
|
425
|
+
A custom element carrying `lb-show`, or inside an element that does, still
|
|
426
|
+
receives its column while it is away, so it returns current; see
|
|
427
|
+
[When its code runs](#when-its-code-runs).
|
|
278
428
|
|
|
279
429
|
### Sending a request
|
|
280
430
|
|
|
281
|
-
A
|
|
282
|
-
click
|
|
283
|
-
|
|
284
|
-
|
|
431
|
+
A custom element that carries `lb-request` and is not a control commits on
|
|
432
|
+
`click`, as a button does. To send a request at a moment of its own, a
|
|
433
|
+
custom element dispatches the `lb-request` event itself, a bubbling
|
|
434
|
+
`CustomEvent` whose `detail` is the request:
|
|
285
435
|
|
|
286
436
|
```ts
|
|
287
437
|
import { LB_EVENT_NAME } from "@loadbare/app/constants";
|
|
288
438
|
import type { HubRequest } from "@loadbare/app/types";
|
|
289
439
|
|
|
290
|
-
const detail: HubRequest = {
|
|
440
|
+
const detail: HubRequest = { name: "archiveOld" };
|
|
291
441
|
this.dispatchEvent(new CustomEvent(LB_EVENT_NAME, { bubbles: true, detail }));
|
|
292
442
|
```
|
|
293
443
|
|
|
294
|
-
The hub
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
sends that one cell. The hub builds `values` from the cell's name and the
|
|
301
|
-
`value` the widget sent, or reads the control the widget wraps when it sent
|
|
302
|
-
none.
|
|
303
|
-
|
|
304
|
-
A widget that carries no `lb-cell` doesn't read its own controls. It
|
|
305
|
-
dispatches the bare action from the row, or from anything inside it, and the
|
|
306
|
-
hub gathers `values` from the row the event came from:
|
|
307
|
-
the nearest `<form>`, `<tr>` or live row around the dispatching element,
|
|
308
|
-
itself included, inside its scope. Every readable `lb-cell` in that row is
|
|
309
|
-
gathered, wherever in the row it sits, except the cells of a scope nested in
|
|
310
|
-
it: a picker carrying `lb-list` and `lb-cell` gives its own value, not its
|
|
311
|
-
options'. A request from an element in no such
|
|
312
|
-
row is not sent, and the console says to use a `<form>`.
|
|
444
|
+
The hub completes the request from where the element sits before any
|
|
445
|
+
ancestor sees the event, gathering as it does for an element carrying
|
|
446
|
+
`lb-request`; see [Gathering](./data-binding.md#gathering). A field the
|
|
447
|
+
element supplies in `detail` is kept. The hub does not send a request
|
|
448
|
+
missing a field its name needs, and stamps the dispatching element with
|
|
449
|
+
`lb-request-pending` and `lb-request-error` as it would any other.
|
|
313
450
|
|
|
314
|
-
|
|
315
|
-
row.dispatchEvent(
|
|
316
|
-
new CustomEvent(LB_EVENT_NAME, {
|
|
317
|
-
bubbles: true,
|
|
318
|
-
detail: { action: "lb-row-insert" } as HubRequest,
|
|
319
|
-
}),
|
|
320
|
-
);
|
|
321
|
-
```
|
|
322
|
-
|
|
323
|
-
Values the widget supplies itself are kept, and nothing is gathered over
|
|
324
|
-
them.
|
|
325
|
-
|
|
326
|
-
When an insert succeeds, the hub resets the controls it gathered to their
|
|
327
|
-
defaults, so a blank row for entering a new one is blank again without the
|
|
328
|
-
widget watching the request settle. A hidden input the widget added keeps
|
|
329
|
-
its value, since a hidden input's default is its value. Values the widget
|
|
330
|
-
supplied in the request were not gathered, and are not reset.
|
|
451
|
+
Let the event bubble, so an ancestor can stop it before the hub sends it.
|
|
331
452
|
|
|
332
|
-
|
|
333
|
-
the hub sees it. A hand-written widget and a native element carrying
|
|
334
|
-
`lb-action` produce the same event.
|
|
453
|
+
### Holding rows
|
|
335
454
|
|
|
336
|
-
|
|
455
|
+
The hub lands every row template itself. A plain element carrying `lb-query`
|
|
456
|
+
with a row template inside it needs no code, so a custom element holds rows
|
|
457
|
+
only when they need placement or scaffolding that only it can decide.
|
|
337
458
|
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
so a widget exists only when the rows need scaffolding or placement that
|
|
341
|
-
only it can decide.
|
|
342
|
-
|
|
343
|
-
Two optional methods say what it decides. Both are named in the `lb`
|
|
344
|
-
namespace, which Loadbare reserves for methods it calls on classes it does
|
|
345
|
-
not own, so a widget's own methods can never collide with a later one:
|
|
459
|
+
A custom element carrying `lb-query` and a row template may implement two
|
|
460
|
+
optional methods, which the hub calls:
|
|
346
461
|
|
|
347
462
|
```ts
|
|
348
|
-
import type {
|
|
463
|
+
import type { RowsHost, Row } from "@loadbare/app/types";
|
|
349
464
|
|
|
350
|
-
class SortedList extends HTMLElement implements
|
|
465
|
+
class SortedList extends HTMLElement implements RowsHost {
|
|
351
466
|
lbPlaceRow(el: Element, row: Row, template: HTMLTemplateElement) {
|
|
352
|
-
// Where this row goes. Called with the
|
|
353
|
-
// appearance and again whenever
|
|
467
|
+
// Where this live row goes. Called with the live row detached, on its
|
|
468
|
+
// first appearance and again whenever all rows decide the order.
|
|
354
469
|
}
|
|
355
470
|
|
|
356
471
|
lbRowsLanded() {
|
|
357
|
-
// Once, after the
|
|
472
|
+
// Once, after the rows have landed. For scaffolding derived from the
|
|
358
473
|
// rows: a section heading, an <optgroup>, anything that goes when its
|
|
359
474
|
// last row does.
|
|
360
475
|
}
|
|
361
476
|
}
|
|
362
477
|
```
|
|
363
478
|
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
`
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
479
|
+
| Method | The hub calls it |
|
|
480
|
+
|----------------|---------------------|
|
|
481
|
+
| `lbPlaceRow` | To place a live row |
|
|
482
|
+
| `lbRowsLanded` | After the rows land |
|
|
483
|
+
|
|
484
|
+
Everything else is the hub's:
|
|
485
|
+
|
|
486
|
+
| Concern | The hub |
|
|
487
|
+
|-------------|----------------------------------------------------------|
|
|
488
|
+
| Cloning | Clones the row template once per new key |
|
|
489
|
+
| Matching | Fills the live row already showing that key |
|
|
490
|
+
| Removing | Removes the live rows the response says are gone |
|
|
491
|
+
| Stamping | Stamps each live row with `lb-row-live` and `lb-key-value` |
|
|
492
|
+
| Counting | Stamps `lb-query-row-count` with the number of live rows |
|
|
493
|
+
|
|
494
|
+
`lbPlaceRow` receives the live row already filled and not yet in the
|
|
495
|
+
document, so a custom element that reads a column to decide where the row
|
|
496
|
+
goes can. Without it, the hub places a live row immediately before the row
|
|
497
|
+
template, in arrival order. `lb-options.browser.ts` and
|
|
498
|
+
`lb-table.browser.ts` in [`@loadbare/widgets`](./widgets.md) are two
|
|
499
|
+
different placements over the same landing.
|
|
500
|
+
|
|
501
|
+
A custom element that walks its own rows finds them with `LIVE_ROW` from
|
|
502
|
+
`@loadbare/app/constants`, never by `lb-key-value`: an element a `row` lands
|
|
503
|
+
on carries a key too, and may sit among the rows, as a total in a table's
|
|
504
|
+
foot does.
|
|
505
|
+
|
|
506
|
+
Style an empty query against `lb-query-row-count` rather than carrying an
|
|
507
|
+
empty state in the custom element — see
|
|
508
|
+
[Counting rows](./data-binding.md#counting-rows).
|
|
509
|
+
|
|
510
|
+
### Filling a subtree by hand
|
|
511
|
+
|
|
512
|
+
`@loadbare/app` exports `applyRow(root, row)`, the operation that lands a
|
|
513
|
+
`row` on an element. Call it in a custom element that fills a subtree of its
|
|
514
|
+
own rather than one the hub lands on. It sets every element carrying a
|
|
515
|
+
matching `lb-column` inside `root`, outside any nested `lb-query`.
|