@loadbare/app 0.5.5 → 0.6.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 -4
- package/dist/build/assemble.d.ts +1 -1
- package/dist/build/assemble.d.ts.map +1 -1
- package/dist/build/assemble.js +80 -7
- package/dist/build/cli.d.ts +2 -2
- package/dist/build/cli.js +2 -2
- package/dist/build/expand.d.ts.map +1 -1
- package/dist/build/expand.js +30 -34
- package/dist/build/locations.d.ts +3 -3
- package/dist/build/locations.d.ts.map +1 -1
- package/dist/build/locations.js +15 -3
- package/dist/build/origins.d.ts +0 -13
- package/dist/build/origins.d.ts.map +1 -1
- package/dist/build/origins.js +32 -8
- package/dist/build/pages.d.ts +3 -3
- package/dist/build/pages.d.ts.map +1 -1
- package/dist/build/pages.js +9 -7
- package/dist/core/lb-constants.d.ts +17 -12
- package/dist/core/lb-constants.d.ts.map +1 -1
- package/dist/core/lb-constants.js +106 -43
- package/dist/core/lb-types.d.ts +103 -62
- package/dist/core/lb-types.d.ts.map +1 -1
- package/dist/core/lb-types.js +11 -3
- package/dist/hub/lb-apply.d.ts +18 -4
- package/dist/hub/lb-apply.d.ts.map +1 -1
- package/dist/hub/lb-apply.js +184 -34
- 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 +165 -83
- package/dist/server/lb-express.d.ts +7 -4
- package/dist/server/lb-express.d.ts.map +1 -1
- package/dist/server/lb-express.js +45 -33
- package/dist/server/lb-server.d.ts +67 -45
- package/dist/server/lb-server.d.ts.map +1 -1
- package/dist/server/lb-server.js +55 -18
- package/dist/tests/assemble.test.js +154 -2
- package/dist/tests/expand.test.js +30 -3
- package/dist/tests/helpers/hub.d.ts +73 -0
- package/dist/tests/helpers/hub.d.ts.map +1 -0
- package/dist/tests/helpers/hub.js +151 -0
- package/dist/tests/lb-apply.test.js +86 -62
- package/dist/tests/lb-express.test.d.ts +1 -1
- package/dist/tests/lb-express.test.js +43 -38
- package/dist/tests/lb-hub.test.d.ts +14 -0
- package/dist/tests/lb-hub.test.d.ts.map +1 -0
- package/dist/tests/lb-hub.test.js +319 -0
- package/dist/tests/{lb-rows.test.d.ts → lb-list.test.d.ts} +1 -1
- package/dist/tests/lb-list.test.d.ts.map +1 -0
- package/dist/tests/{lb-rows.test.js → lb-list.test.js} +109 -106
- package/dist/tests/lb-server.test.js +151 -100
- package/dist/tests/origins.test.js +19 -1
- package/dist/tests/pages.test.d.ts +1 -1
- package/dist/tests/pages.test.js +64 -14
- package/docs/TECHREF-1.0.md +1000 -0
- package/docs/reference/builder.md +4 -4
- package/docs/reference/chrome.md +56 -5
- package/docs/reference/custom-elements.md +59 -36
- package/docs/reference/data-binding.md +142 -84
- package/docs/reference/overview.md +1 -1
- package/docs/reference/page-files.md +64 -49
- package/docs/reference/server.md +7 -6
- package/docs/reference/widgets.md +43 -32
- package/docs/roadmap.md +68 -22
- package/docs/testing.md +47 -17
- package/docs/theory.md +2 -2
- package/docs/tutorials/010-pages-and-navigation.md +14 -8
- package/docs/tutorials/040-displaying-data.md +9 -9
- package/docs/tutorials/050-actions.md +5 -5
- package/docs/tutorials/060-custom-element-code.md +1 -1
- package/docs/tutorials/065-conditional-rendering.md +4 -4
- package/docs/tutorials/070-displaying-a-list.md +24 -47
- package/docs/tutorials/072-inserting-into-a-list.md +17 -17
- package/docs/tutorials/074-deleting-from-a-list.md +15 -17
- package/docs/tutorials/076-updating-a-list-item.md +20 -22
- package/docs/tutorials/080-widget-requests.md +27 -23
- package/docs/tutorials/090-using-widget-libraries.md +23 -1
- package/package.json +1 -2
- package/dist/hub/lb-rows.d.ts +0 -18
- package/dist/hub/lb-rows.d.ts.map +0 -1
- package/dist/hub/lb-rows.js +0 -106
- package/dist/tests/lb-rows.test.d.ts.map +0 -1
|
@@ -40,7 +40,7 @@ The builder classifies by name, not location.
|
|
|
40
40
|
|----------------------------|---------------------------------------------------|
|
|
41
41
|
| `chrome.html` | The chrome — see [`chrome.html`](./chrome.md) |
|
|
42
42
|
| `*.page.html` | A page |
|
|
43
|
-
| `*.
|
|
43
|
+
| `*.requests.ts` | A page's requests, matched by base name |
|
|
44
44
|
| `*.queries.ts` | A page's queries, matched by base name |
|
|
45
45
|
| `imports.ts` | The packages this app takes widgets from |
|
|
46
46
|
| `*.css` | A stylesheet — see [CSS](./css.md) |
|
|
@@ -48,7 +48,7 @@ The builder classifies by name, not location.
|
|
|
48
48
|
| `<tag>.browser.ts` | A widget script, named for the tag it registers |
|
|
49
49
|
|
|
50
50
|
Give the application exactly one `chrome.html` and at most one `imports.ts`.
|
|
51
|
-
Give every `.
|
|
51
|
+
Give every `.requests.ts` and `.queries.ts` a `.page.html` of the same base name.
|
|
52
52
|
|
|
53
53
|
Name a widget script `<tag>.browser.ts`, not `<tag>.ts`. Only a file whose
|
|
54
54
|
name carries `.browser` is bundled for the browser; every other module under
|
|
@@ -125,10 +125,10 @@ A tag with neither a script nor a definition in any origin is an error.
|
|
|
125
125
|
The builder expands the chrome and every page against the available widget
|
|
126
126
|
definitions — see [Custom Elements](./custom-elements.md#html) for the
|
|
127
127
|
substitution rules — wraps each expanded page in
|
|
128
|
-
`<template
|
|
128
|
+
`<template lb-page="<name>">`, and splices them into the chrome's `<body>`. It formats the result with
|
|
129
129
|
Prettier when the application has it installed.
|
|
130
130
|
|
|
131
131
|
The builder writes `app.css` only when it finds a stylesheet, and `pages.ts`
|
|
132
|
-
only when some page has a `.
|
|
132
|
+
only when some page has a `.requests.ts` or a `.queries.ts` file. Import `hub`
|
|
133
133
|
from `pages.ts` — see [The Express Server](./server.md) for the rest of the
|
|
134
134
|
wiring.
|
package/docs/reference/chrome.md
CHANGED
|
@@ -24,15 +24,18 @@ Here is a minimal but fully complaint chrome for a typical app:
|
|
|
24
24
|
</head>
|
|
25
25
|
<body hidden>
|
|
26
26
|
<lb-hub>
|
|
27
|
-
<header
|
|
27
|
+
<header lb-row="lb-navigation">
|
|
28
|
+
<h1>Membership Roster</h1>
|
|
29
|
+
<h2 lb-cell="page-label"></h2>
|
|
30
|
+
</header>
|
|
28
31
|
<nav>
|
|
29
32
|
<a href="/" lb-nav-link>Home</a>
|
|
30
33
|
<a href="/members" lb-nav-link>Members</a>
|
|
31
34
|
<a href="https://example.org/">Our website</a>
|
|
32
35
|
</nav>
|
|
33
36
|
<main></main>
|
|
34
|
-
<dialog lb-unknown-page>
|
|
35
|
-
The
|
|
37
|
+
<dialog lb-unknown-page lb-row="lb-navigation">
|
|
38
|
+
The URL <span lb-cell="page-uri"></span> is not in this app.
|
|
36
39
|
</dialog>
|
|
37
40
|
</lb-hub>
|
|
38
41
|
</body>
|
|
@@ -57,6 +60,7 @@ Everything else is optional:
|
|
|
57
60
|
|-------------------------------------------|---------------------------------------------|
|
|
58
61
|
| `<link rel="stylesheet" href="/app.css">` | The bundled stylesheet |
|
|
59
62
|
| `<a lb-nav-link>` | Navigation between pages |
|
|
63
|
+
| `lb-row="lb-navigation"` | Where the page is — see below |
|
|
60
64
|
| `<dialog lb-unknown-page>` | A message when a URL matches no page |
|
|
61
65
|
| Custom elements | The chrome, decomposed into widget files |
|
|
62
66
|
| `<body hidden>` + a `<noscript>` fallback | Avoids a first-load blink — see below |
|
|
@@ -73,8 +77,55 @@ A navigation anchor's `href` is a path, and the path names a page:
|
|
|
73
77
|
landing page is the one named `index.page.html`. An anchor without
|
|
74
78
|
`lb-nav-link` is left alone and behaves like any other link.
|
|
75
79
|
|
|
76
|
-
The `lb-unknown-page` attribute, if used, must appear on a `<dialog
|
|
77
|
-
|
|
80
|
+
The `lb-unknown-page` attribute, if used, must appear on a `<dialog>` inside
|
|
81
|
+
`<lb-hub>`; the builder rejects it anywhere else. The hub opens it when a
|
|
82
|
+
path names no page. What it says is up to the chrome:
|
|
83
|
+
the dialog is a subtree like any other, and it displays where the page is by
|
|
84
|
+
naming the hub's own query, described next.
|
|
85
|
+
|
|
86
|
+
## Where the page is
|
|
87
|
+
|
|
88
|
+
On every navigation the hub lands a query of its own, `lb-navigation`, on
|
|
89
|
+
any subtree inside the hub that names it. It arrives the way a server's
|
|
90
|
+
row arrives — `lb-row` on the subtree, `lb-cell` on each element that
|
|
91
|
+
shows a value — so a chrome displays the current page with no code at all:
|
|
92
|
+
|
|
93
|
+
```html
|
|
94
|
+
<header lb-row="lb-navigation">
|
|
95
|
+
<h1>Membership Roster</h1>
|
|
96
|
+
<h2 lb-cell="page-label"></h2>
|
|
97
|
+
</header>
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
| Cell | Holds |
|
|
101
|
+
|--------------|-----------------------------------------------------------|
|
|
102
|
+
| `page-label` | The text of the `lb-nav-link` anchor for the path, or empty if none |
|
|
103
|
+
| `page-uri` | The path as the browser has it, such as `/members` |
|
|
104
|
+
|
|
105
|
+
The label is the nav's. The hub takes it from the first `lb-nav-link`
|
|
106
|
+
anchor whose `href` names the current page, so a click, a reload and the
|
|
107
|
+
back button all land the same text, and a path no anchor names lands an
|
|
108
|
+
empty label. A chrome that shows the label somewhere fixed should expect
|
|
109
|
+
that case for a page reachable only by URL.
|
|
110
|
+
|
|
111
|
+
The `lb-` prefix on the query name is what keeps it out of the server's
|
|
112
|
+
namespace: no server answers a query so named. It is landed only where a
|
|
113
|
+
subtree names it, so a chrome that displays no navigation is not warned
|
|
114
|
+
about a query with no scope.
|
|
115
|
+
|
|
116
|
+
The same row is what an unknown-page dialog has to work with. It lands
|
|
117
|
+
before the page host is looked up, so a miss has it too. Name the query on
|
|
118
|
+
the dialog and show whichever cell fits:
|
|
119
|
+
|
|
120
|
+
```html
|
|
121
|
+
<dialog lb-unknown-page lb-row="lb-navigation">
|
|
122
|
+
The URL <span lb-cell="page-uri"></span> is not in this app.
|
|
123
|
+
</dialog>
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
`@loadbare/widgets` ships this dialog as a widget, `<lb-unknown-page>`, for a
|
|
127
|
+
chrome that would rather write one tag — see
|
|
128
|
+
[The Basic Widget Library](./widgets.md#lb-unknown-page).
|
|
78
129
|
|
|
79
130
|
## Preventing the first-load blink
|
|
80
131
|
|
|
@@ -86,6 +86,14 @@ value is a placeholder is removed rather than shipped empty, which is how
|
|
|
86
86
|
placeholder in a text node resolves to nothing, and the whitespace around
|
|
87
87
|
it survives.
|
|
88
88
|
|
|
89
|
+
Give a placeholder a default with a pipe: `{{button-text|OK}}` reads
|
|
90
|
+
`exp-button-text` when the tag supplies it and `OK` when it does not. The
|
|
91
|
+
default is a literal, not an expression. Whitespace around the name and the
|
|
92
|
+
default is not part of either, so `{{ button-text | OK }}` is the same
|
|
93
|
+
placeholder. An empty default, `readonly="{{readonly|}}"`, is the one that
|
|
94
|
+
differs from no default at all: it emits the attribute, empty, rather than
|
|
95
|
+
dropping it, so a definition can ship a boolean attribute switched on.
|
|
96
|
+
|
|
89
97
|
Every attribute stays on the tag after expansion, `exp-` ones included.
|
|
90
98
|
|
|
91
99
|
Parameter values reach a definition through the DOM rather than through
|
|
@@ -215,12 +223,15 @@ as a string literal.
|
|
|
215
223
|
|------------------|------------|
|
|
216
224
|
| `ATTR_VALUE` | `lb-value` |
|
|
217
225
|
| `ATTR_CELL` | `lb-cell` |
|
|
218
|
-
| `
|
|
226
|
+
| `ATTR_LIST` | `lb-list` |
|
|
227
|
+
| `ATTR_ROW` | `lb-row` |
|
|
219
228
|
| `ATTR_KEY` | `lb-key` |
|
|
220
|
-
| `
|
|
221
|
-
| `ATTR_SORT` | `lb-sort` |
|
|
229
|
+
| `ATTR_KEY_VALUE` | `lb-key-value` |
|
|
222
230
|
| `ATTR_ACTION` | `lb-action`|
|
|
223
|
-
| `
|
|
231
|
+
| `ACTION_ROW_INSERT`, `ACTION_ROW_DELETE`, `ACTION_ROW_UPDATE`, `ACTION_CELL_CHANGE` | the reserved `lb-action` values |
|
|
232
|
+
| `LB_ACTIONS` | all four of them, in one array |
|
|
233
|
+
| `LB_RESERVED_PREFIX` | `lb-`, the prefix every reserved name begins with |
|
|
234
|
+
| `ATTR_ROW_COUNT` | `lb-row-count`|
|
|
224
235
|
| `LB_EVENT_NAME` | `lb-request` |
|
|
225
236
|
|
|
226
237
|
### Receiving a value
|
|
@@ -258,20 +269,20 @@ click — builds its own request and dispatches it as a bubbling
|
|
|
258
269
|
as its `detail`:
|
|
259
270
|
|
|
260
271
|
```ts
|
|
261
|
-
import { ATTR_CELL,
|
|
272
|
+
import { ATTR_CELL, ATTR_KEY_VALUE, ATTR_LIST, LB_EVENT_NAME } from "@loadbare/app/constants";
|
|
262
273
|
import type { HubRequest } from "@loadbare/app/types";
|
|
263
274
|
|
|
264
275
|
const detail: HubRequest = {
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
key: this.closest(`[${
|
|
276
|
+
action: "lb-cell-change",
|
|
277
|
+
list: this.closest(`[${ATTR_LIST}]`)!.getAttribute(ATTR_LIST)!,
|
|
278
|
+
key: this.closest(`[${ATTR_KEY_VALUE}]`)!.getAttribute(ATTR_KEY_VALUE)!,
|
|
268
279
|
cell: this.getAttribute(ATTR_CELL)!,
|
|
269
280
|
value: input.value,
|
|
270
281
|
};
|
|
271
282
|
this.dispatchEvent(new CustomEvent(LB_EVENT_NAME, { bubbles: true, detail }));
|
|
272
283
|
```
|
|
273
284
|
|
|
274
|
-
Find
|
|
285
|
+
Find the scope name and `key` by walking up with `closest()`, the same way the hub
|
|
275
286
|
finds the binding for a native element. See
|
|
276
287
|
[Data Binding](./data-binding.md#requests) for the request vocabulary and
|
|
277
288
|
what each operation carries.
|
|
@@ -280,54 +291,66 @@ Let the event bubble, so an ancestor widget can intercept and stop it before
|
|
|
280
291
|
the hub sees it. A hand-written widget and a native element carrying
|
|
281
292
|
`lb-action` produce the same event.
|
|
282
293
|
|
|
283
|
-
###
|
|
294
|
+
### Decorating a list
|
|
295
|
+
|
|
296
|
+
The hub reconciles every list scope itself. A plain element carrying
|
|
297
|
+
`lb-list` with a row template inside it is a whole list and needs no widget,
|
|
298
|
+
so a widget exists only when the rows need scaffolding or placement that
|
|
299
|
+
only it can decide.
|
|
284
300
|
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
301
|
+
Two optional methods say what it decides. Both are named in the `lb`
|
|
302
|
+
namespace, which Loadbare reserves for methods it calls on classes it does
|
|
303
|
+
not own, so a widget's own methods can never collide with a later one:
|
|
288
304
|
|
|
289
305
|
```ts
|
|
290
|
-
import {
|
|
291
|
-
import type { Projection, HubRowHost } from "@loadbare/app/types";
|
|
306
|
+
import type { ListHost, Row } from "@loadbare/app/types";
|
|
292
307
|
|
|
293
|
-
class
|
|
294
|
-
|
|
295
|
-
|
|
308
|
+
class SortedList extends HTMLElement implements ListHost {
|
|
309
|
+
lbPlaceRow(el: Element, row: Row, template: HTMLTemplateElement) {
|
|
310
|
+
// Where this row goes. Called with the element detached, on its first
|
|
311
|
+
// appearance and again whenever a whole set decides the order.
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
lbRowsLanded() {
|
|
315
|
+
// Once, after the result has landed. For scaffolding derived from the
|
|
316
|
+
// rows: a section heading, an <optgroup>, anything that goes when its
|
|
317
|
+
// last row does.
|
|
296
318
|
}
|
|
297
319
|
}
|
|
298
320
|
```
|
|
299
321
|
|
|
300
|
-
|
|
301
|
-
Every built-in list widget uses it, and it settles four things:
|
|
322
|
+
Everything else is the hub's, and a widget never reimplements it:
|
|
302
323
|
|
|
303
|
-
| Concern | What
|
|
324
|
+
| Concern | What the hub does |
|
|
304
325
|
|-------------|---------------------------------------------------------|
|
|
305
|
-
| Cloning | Clones the `<template lb-key="...">` in the
|
|
326
|
+
| Cloning | Clones the `<template lb-key="...">` in the scope |
|
|
306
327
|
| Matching | Updates the row already showing that key, or clones one |
|
|
307
328
|
| Reconciling | Removes the rows the response says are gone |
|
|
308
|
-
| Counting | Stamps `
|
|
329
|
+
| Counting | Stamps `lb-row-count` with the number of rows showing |
|
|
309
330
|
|
|
310
|
-
|
|
311
|
-
absent from it is removed.
|
|
331
|
+
An array is the whole set, so it decides membership and order, and a key
|
|
332
|
+
absent from it is removed. A patch touches only the rows it names and leaves
|
|
312
333
|
every other row's contents and position alone.
|
|
313
334
|
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
order.
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
335
|
+
`lbPlaceRow` is called with the row already filled and not yet in the
|
|
336
|
+
document, so a widget that reads a cell to decide where the row goes can. A
|
|
337
|
+
scope without it lands rows immediately before the template, in arrival
|
|
338
|
+
order. `lb-options.browser.ts` and `lb-table.browser.ts` in
|
|
339
|
+
[`@loadbare/widgets`](./widgets.md) are two different placements over the
|
|
340
|
+
same machinery.
|
|
341
|
+
|
|
342
|
+
A list scope with no row template displays nothing, which is not an error: an
|
|
343
|
+
insert form names the list it adds a row to and has no rows of its own.
|
|
321
344
|
|
|
322
|
-
Style an empty list against `
|
|
345
|
+
Style an empty list against `lb-row-count` rather than carrying an empty-state
|
|
323
346
|
conditional in the widget — see
|
|
324
347
|
[Conditional rendering](./data-binding.md#conditional-rendering).
|
|
325
348
|
|
|
326
349
|
### Filling a scope by hand
|
|
327
350
|
|
|
328
|
-
`@loadbare/app
|
|
329
|
-
|
|
330
|
-
|
|
351
|
+
`@loadbare/app` also exports `applyRow(root, row)`, the same row-landing
|
|
352
|
+
operation a page host uses. Call it in a widget that builds a scope of its
|
|
353
|
+
own rather than one the hub reconciles. It fills `root`
|
|
331
354
|
itself when `root` carries a matching `lb-cell`, and every matching
|
|
332
355
|
descendant.
|
|
333
356
|
</content>
|
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
# Data Binding
|
|
2
2
|
|
|
3
3
|
A page binds its elements to server data with four attributes, and asks the
|
|
4
|
-
server to change that data with
|
|
5
|
-
|
|
4
|
+
server to change that data with one more. The developer writes all five into
|
|
5
|
+
the HTML; the server declares which queries it answers and which operations
|
|
6
6
|
it permits, and refuses anything it has not declared.
|
|
7
7
|
|
|
8
|
+
One vocabulary and one containment ladder: a list holds rows, a row holds
|
|
9
|
+
cells. A column is the second axis, and it is what the value of `lb-cell` or
|
|
10
|
+
`lb-key` always holds.
|
|
11
|
+
|
|
8
12
|
## A page that binds data
|
|
9
13
|
|
|
10
14
|
Here is a page that binds a scalar, a list, and three requests:
|
|
@@ -13,23 +17,21 @@ Here is a page that binds a scalar, a list, and three requests:
|
|
|
13
17
|
<!-- src/pages/members.page.html -->
|
|
14
18
|
<h1>Members</h1>
|
|
15
19
|
|
|
16
|
-
<p lb-
|
|
20
|
+
<p lb-row="dues">Dues collected this year: <span lb-cell="total"></span></p>
|
|
17
21
|
|
|
18
|
-
<form lb-insert lb-
|
|
22
|
+
<form lb-action="lb-row-insert" lb-list="roster">
|
|
19
23
|
<input lb-cell="name" placeholder="Name" />
|
|
20
24
|
<button type="submit">Add member</button>
|
|
21
25
|
</form>
|
|
22
26
|
|
|
23
|
-
<lb-list
|
|
24
|
-
<
|
|
25
|
-
<
|
|
26
|
-
<
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
</ul>
|
|
32
|
-
</lb-list>
|
|
27
|
+
<ul lb-list="roster">
|
|
28
|
+
<template lb-key="id">
|
|
29
|
+
<li>
|
|
30
|
+
<lb-input lb-cell="name"></lb-input>
|
|
31
|
+
<button lb-action="lb-row-delete">Remove</button>
|
|
32
|
+
</li>
|
|
33
|
+
</template>
|
|
34
|
+
</ul>
|
|
33
35
|
```
|
|
34
36
|
|
|
35
37
|
The queries named here — `dues` and `roster` — and the operations the page
|
|
@@ -37,28 +39,49 @@ asks for are declared on the server; see [page files](./page-files.md).
|
|
|
37
39
|
|
|
38
40
|
## Binding
|
|
39
41
|
|
|
40
|
-
| Attribute | Names
|
|
41
|
-
|
|
42
|
-
| `lb-
|
|
43
|
-
| `lb-
|
|
44
|
-
| `lb-
|
|
45
|
-
| `lb-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
42
|
+
| Attribute | Written by | Names |
|
|
43
|
+
|----------------|-------------|-------------------------------------------|
|
|
44
|
+
| `lb-list` | a developer | The set of rows a subtree displays |
|
|
45
|
+
| `lb-row` | a developer | The one row a subtree displays |
|
|
46
|
+
| `lb-key` | a developer | The column that identifies a row |
|
|
47
|
+
| `lb-cell` | a developer | The column an element displays |
|
|
48
|
+
| `lb-key-value` | the hub | A live row's own key |
|
|
49
|
+
| `lb-value` | the hub | Where a widget receives its value |
|
|
50
|
+
|
|
51
|
+
A binding is scoped by ancestry. Either scope attribute scopes its DOM
|
|
52
|
+
children, and a nested one of either kind begins a new scope, so an element
|
|
53
|
+
binds to the name on its nearest ancestor carrying one, and to the row on its
|
|
54
|
+
nearest ancestor carrying `lb-key-value`. Nothing else establishes scope: an
|
|
55
|
+
element outside every scope is bound to nothing and displays nothing, and a
|
|
56
|
+
result never crosses into a nested scope.
|
|
57
|
+
|
|
58
|
+
Which of the two a subtree writes is not a choice about display. Cardinality
|
|
59
|
+
is a property of the name, so one name answers with one shape, always. A page
|
|
60
|
+
that shows the roster both as a set and as a single row declares two queries,
|
|
61
|
+
`rosterList` and `rosterRow`, and binds each with the attribute that matches
|
|
62
|
+
what it answers with.
|
|
51
63
|
|
|
52
64
|
Bind an element to a cell by putting `lb-cell` on the element that shows the
|
|
53
|
-
value.
|
|
54
|
-
|
|
65
|
+
value. Its value is a column name and never a cell name: a cell has no name
|
|
66
|
+
of its own, because it is identified by its row and its column, and the row
|
|
67
|
+
arrives from scope. The scope's own root counts as a cell if it carries one,
|
|
68
|
+
which is how an `<option>` — whose content model is text — displays the value
|
|
69
|
+
it is.
|
|
55
70
|
|
|
56
71
|
Name the same query on more than one subtree to display it in more than one
|
|
57
72
|
place. Every subtree gets the result.
|
|
58
73
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
74
|
+
One name is the hub's rather than the server's: `lb-navigation`, one row
|
|
75
|
+
landed on every navigation with the columns `page-label` and `page-uri`. It binds the
|
|
76
|
+
same way. Its name is reserved, as every value beginning with `lb-` is in
|
|
77
|
+
every `lb-` attribute: the server refuses a page that declares a query so
|
|
78
|
+
named — see [Where the page is](./chrome.md#where-the-page-is).
|
|
79
|
+
|
|
80
|
+
A key has a name and a value, and they are two attributes. `lb-key` on a row
|
|
81
|
+
template names the column that identifies a row: it is a property of the
|
|
82
|
+
list, since a list without a fixed key column is meaningless, written where
|
|
83
|
+
the rows land. `lb-key-value` on a row that is showing carries that row's
|
|
84
|
+
value of it. A developer writes the first and never the second.
|
|
62
85
|
|
|
63
86
|
### Where a bound value lands
|
|
64
87
|
|
|
@@ -77,40 +100,71 @@ observing `lb-value`.
|
|
|
77
100
|
Nothing an application writes ever sets `lb-value`. It is written by
|
|
78
101
|
Loadbare and read by the widget it is written on.
|
|
79
102
|
|
|
80
|
-
###
|
|
81
|
-
|
|
82
|
-
A query that answers with many rows is delivered to a list widget, which
|
|
83
|
-
clones its row template once per row and binds each clone to one row. A
|
|
84
|
-
native element has one destination for a value and cannot acquire children,
|
|
85
|
-
so a query that answers with rows must be bound to a widget that accepts
|
|
86
|
-
them.
|
|
103
|
+
### Lists
|
|
87
104
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
where a row goes.
|
|
105
|
+
Write `lb-list` on any element, and a `<template>` inside it carrying
|
|
106
|
+
`lb-key`. The hub clones that template once per row, fills each clone, and
|
|
107
|
+
reconciles what is showing against what arrived. No widget is involved, and
|
|
108
|
+
none is needed.
|
|
93
109
|
|
|
94
|
-
|
|
110
|
+
An array is the whole set, so it decides membership and order: a row whose
|
|
111
|
+
key did not arrive is gone. A patch touches only the rows it names and leaves
|
|
112
|
+
every other row's contents and position alone.
|
|
95
113
|
|
|
96
|
-
|
|
114
|
+
A widget enters only where the rows need scaffolding or placement that only
|
|
115
|
+
it can decide — a `<select>` that builds an `<optgroup>` per distinct value, a
|
|
116
|
+
table that sections and sorts. Such a widget carries `lb-list` itself and
|
|
117
|
+
implements one or both of two optional methods; see
|
|
118
|
+
[Decorating a list](./custom-elements.md#decorating-a-list) and
|
|
119
|
+
[The Basic Widget Library](./widgets.md).
|
|
97
120
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
| `lb-delete` | `tupleDelete` | `query`, `key` |
|
|
102
|
-
| `lb-insert` on a `<form>` | `tupleInsert` | `query`, `values` |
|
|
103
|
-
| `lb-update` on a `<form>` | `tupleUpdate` | `query`, `key`, `values` |
|
|
121
|
+
A scope with `lb-list` and no row template displays nothing, and that is not
|
|
122
|
+
an error. It is bound to the list without showing it, which is what an insert
|
|
123
|
+
form naming the list it adds a row to already is.
|
|
104
124
|
|
|
105
|
-
|
|
106
|
-
`query`, `key`, `cell`, and `value`. It sends one when the page applies
|
|
107
|
-
`data-fire-on-change` to it, and stays quiet otherwise — an input inside an
|
|
108
|
-
`lb-insert` or `lb-update` form is read again by the form on submit, so a
|
|
109
|
-
widget that sent on its own would write the same edit twice.
|
|
125
|
+
## Requests
|
|
110
126
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
127
|
+
One attribute turns an interaction into a request. `lb-action` names what the
|
|
128
|
+
server is asked for: an action the page declared, or one of Loadbare's
|
|
129
|
+
reserved names, which are the CRUD operations.
|
|
130
|
+
|
|
131
|
+
One attribute name, one wire field, one set of values. The request field
|
|
132
|
+
`action` carries this attribute's value verbatim, so nothing is translated
|
|
133
|
+
between the markup and the server, and the CRUD key is the same value with
|
|
134
|
+
the prefix stripped and the rest camel-cased.
|
|
135
|
+
|
|
136
|
+
| Written | Asks for | Carries |
|
|
137
|
+
|-------------------------------------------|--------------|-------------------------------|
|
|
138
|
+
| `lb-action="name"` | That action | The scope, and what is in it |
|
|
139
|
+
| `lb-action="lb-row-delete"` | `rowDelete` | `list`, `key` |
|
|
140
|
+
| `lb-action="lb-row-insert"` on a `<form>` | `rowInsert` | `list`, `values` |
|
|
141
|
+
| `lb-action="lb-row-update"` on a `<form>` | `rowUpdate` | `list`, `key`, `values` |
|
|
142
|
+
| `lb-action="lb-cell-change"` on a widget | `cellChange` | `list`, `key`, `cell`, `value`|
|
|
143
|
+
|
|
144
|
+
All four operations are list operations. Each needs a key, and a key exists
|
|
145
|
+
only on a live row the hub stamped inside a list, so a single-row scope is
|
|
146
|
+
read-only and a declared action is the only thing it can send. An application
|
|
147
|
+
that wants a writable single row declares a list that answers with one row.
|
|
148
|
+
|
|
149
|
+
A name beginning with `lb-` is reserved, in `lb-action` and in either scope
|
|
150
|
+
attribute alike. The server refuses a page that declares an action or a query
|
|
151
|
+
so named, which is what lets a reserved name be added later without colliding
|
|
152
|
+
with one an application already uses. That reservation is also the whole of
|
|
153
|
+
the wire discriminant: a value beginning with `lb-` is an operation, and
|
|
154
|
+
anything else is a name the page declared.
|
|
155
|
+
|
|
156
|
+
The hub sends the first four from a native element on the element's own
|
|
157
|
+
event: a form on submit, anything else on click. A cell change needs a
|
|
158
|
+
widget to say what a change is, so only a widget sends it: the shipped
|
|
159
|
+
`<lb-input>` does when it carries `lb-action="lb-cell-change"`, and stays
|
|
160
|
+
quiet otherwise — an input inside an `lb-row-insert` or `lb-row-update` form is read
|
|
161
|
+
again by the form on submit, so a widget that sent on its own would write
|
|
162
|
+
the same edit twice.
|
|
163
|
+
|
|
164
|
+
Declare every action on the server, and permit every operation on its list. A
|
|
165
|
+
name the page has not declared, and an operation a list does not permit, are
|
|
166
|
+
refused; see
|
|
167
|
+
[requests, actions, CRUD](./page-files.md#requests-actions-crud).
|
|
114
168
|
|
|
115
169
|
### Actions
|
|
116
170
|
|
|
@@ -121,10 +175,10 @@ one of the four CRUD operations:
|
|
|
121
175
|
<button lb-action="mailRoster">Mail the roster</button>
|
|
122
176
|
```
|
|
123
177
|
|
|
124
|
-
An action carries whatever binding is in scope at the element — `
|
|
125
|
-
`
|
|
126
|
-
carries no value, so the server computes the whole
|
|
127
|
-
page displays only what came back.
|
|
178
|
+
An action carries whatever binding is in scope at the element — `list` or
|
|
179
|
+
`row`, whichever scoped it, plus `key` and `cell` — and nothing else. There is
|
|
180
|
+
no argument list. A button carries no value, so the server computes the whole
|
|
181
|
+
of the new state and the page displays only what came back.
|
|
128
182
|
|
|
129
183
|
Write `lb-action` on a widget to have the widget decide what performing the
|
|
130
184
|
action means. Loadbare turns a click into a request for a native element
|
|
@@ -134,30 +188,31 @@ on change, not on click.
|
|
|
134
188
|
### Deleting a row
|
|
135
189
|
|
|
136
190
|
```html
|
|
137
|
-
<button lb-delete>Remove</button>
|
|
191
|
+
<button lb-action="lb-row-delete">Remove</button>
|
|
138
192
|
```
|
|
139
193
|
|
|
140
|
-
`lb-delete` needs
|
|
141
|
-
scope, and they are all the server needs to know which row is
|
|
142
|
-
whether the
|
|
194
|
+
`lb-row-delete` needs nothing declared. The row's `lb-list` and `lb-key-value`
|
|
195
|
+
are already in scope, and they are all the server needs to know which row is
|
|
196
|
+
meant and whether the list permits deleting it.
|
|
143
197
|
|
|
144
|
-
|
|
145
|
-
sends its own request.
|
|
198
|
+
The hub sends it from a native element. A widget sends its own request.
|
|
146
199
|
|
|
147
200
|
### Forms
|
|
148
201
|
|
|
149
|
-
`lb-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
202
|
+
A `<form>` performs its `lb-action` on submit. `lb-row-insert` and `lb-row-update`
|
|
203
|
+
both gather every `lb-cell` inside the form into one values map, read from
|
|
204
|
+
the control each cell is or wraps. They differ in one thing: `lb-row-update`
|
|
205
|
+
also carries the key of the row it is inside, and `lb-row-insert` carries none,
|
|
206
|
+
because there is no row yet. A declared name on a form sends that action on
|
|
207
|
+
submit, carrying the binding and no values.
|
|
153
208
|
|
|
154
|
-
Put an `lb-update` form inside the row it edits, so it has that row's key
|
|
209
|
+
Put an `lb-row-update` form inside the row it edits, so it has that row's key
|
|
155
210
|
from the same ancestor a delete button reads:
|
|
156
211
|
|
|
157
212
|
```html
|
|
158
213
|
<template lb-key="id">
|
|
159
214
|
<li>
|
|
160
|
-
<form lb-update>
|
|
215
|
+
<form lb-action="lb-row-update">
|
|
161
216
|
<input lb-cell="name" />
|
|
162
217
|
<button type="submit">Save</button>
|
|
163
218
|
</form>
|
|
@@ -188,31 +243,34 @@ HTML.
|
|
|
188
243
|
|
|
189
244
|
### An empty list
|
|
190
245
|
|
|
191
|
-
|
|
246
|
+
The hub stamps every list scope with `lb-row-count`, the number of rows it is
|
|
192
247
|
showing. It is the one conditional a page cannot be sent, because the server
|
|
193
|
-
answers with rows and says nothing about how many survived. It makes an
|
|
194
|
-
|
|
248
|
+
answers with rows and says nothing about how many survived. It makes an empty
|
|
249
|
+
list a stylesheet rule rather than code anywhere:
|
|
195
250
|
|
|
196
251
|
```html
|
|
197
|
-
<lb-list
|
|
252
|
+
<div lb-list="roster">
|
|
198
253
|
<ul>
|
|
199
254
|
<template lb-key="id">
|
|
200
255
|
<li lb-cell="name"></li>
|
|
201
256
|
</template>
|
|
202
257
|
</ul>
|
|
203
258
|
<p class="roster-empty">No members yet.</p>
|
|
204
|
-
</
|
|
259
|
+
</div>
|
|
205
260
|
```
|
|
206
261
|
|
|
207
262
|
```css
|
|
208
263
|
.roster-empty {
|
|
209
264
|
display: none;
|
|
210
265
|
}
|
|
211
|
-
lb-
|
|
266
|
+
[lb-row-count="0"] .roster-empty {
|
|
212
267
|
display: revert;
|
|
213
268
|
}
|
|
214
269
|
```
|
|
215
270
|
|
|
271
|
+
The scope is a `<div>` here rather than the `<ul>`, so that the empty message
|
|
272
|
+
is inside it and the same rule can reach both.
|
|
273
|
+
|
|
216
274
|
## Request state
|
|
217
275
|
|
|
218
276
|
Loadbare stamps two attributes on the element a request came from — the
|
|
@@ -220,11 +278,11 @@ button, the form, or the widget itself:
|
|
|
220
278
|
|
|
221
279
|
| Attribute | Means |
|
|
222
280
|
|-------------------|-------------------------------------------|
|
|
223
|
-
| `
|
|
224
|
-
| `
|
|
281
|
+
| `lb-pending` | The request is in flight |
|
|
282
|
+
| `lb-error` | The last request from this element failed |
|
|
225
283
|
|
|
226
|
-
`
|
|
227
|
-
settles. `
|
|
284
|
+
`lb-pending` is set when the request goes out and removed when it
|
|
285
|
+
settles. `lb-error` is set on a failed response, a network failure, or
|
|
228
286
|
a timeout alike, and cleared when that element sends its next request.
|
|
229
287
|
|
|
230
288
|
Neither one carries any meaning beyond the fact it states. Dim a pending
|
|
@@ -26,7 +26,7 @@ serves, and the widgets those pages are made of.
|
|
|
26
26
|
|---------------------------------------------------------|--------------------------------|
|
|
27
27
|
| [`<name>.page.html`](./page-files.md#html) | The page's HTML |
|
|
28
28
|
| [`<name>.queries.ts`](./page-files.md#queries) | The data the page displays |
|
|
29
|
-
| [`<name>.
|
|
29
|
+
| [`<name>.requests.ts`](./page-files.md#requests-actions-crud) | Data Channel handlers |
|
|
30
30
|
| [Data Binding](./data-binding.md) | Connecting HTML to server data |
|
|
31
31
|
|
|
32
32
|
## Widgets
|