@loadbare/app 0.5.6 → 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 +19 -32
- 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 +15 -13
- package/dist/core/lb-constants.d.ts.map +1 -1
- package/dist/core/lb-constants.js +103 -53
- 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 +131 -84
- 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 +6 -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 +10 -9
- package/docs/reference/custom-elements.md +51 -36
- package/docs/reference/data-binding.md +141 -88
- 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 +22 -30
- 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 +8 -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 +1 -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,7 +24,7 @@ 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 lb-
|
|
27
|
+
<header lb-row="lb-navigation">
|
|
28
28
|
<h1>Membership Roster</h1>
|
|
29
29
|
<h2 lb-cell="page-label"></h2>
|
|
30
30
|
</header>
|
|
@@ -34,7 +34,7 @@ Here is a minimal but fully complaint chrome for a typical app:
|
|
|
34
34
|
<a href="https://example.org/">Our website</a>
|
|
35
35
|
</nav>
|
|
36
36
|
<main></main>
|
|
37
|
-
<dialog lb-unknown-page lb-
|
|
37
|
+
<dialog lb-unknown-page lb-row="lb-navigation">
|
|
38
38
|
The URL <span lb-cell="page-uri"></span> is not in this app.
|
|
39
39
|
</dialog>
|
|
40
40
|
</lb-hub>
|
|
@@ -60,7 +60,7 @@ Everything else is optional:
|
|
|
60
60
|
|-------------------------------------------|---------------------------------------------|
|
|
61
61
|
| `<link rel="stylesheet" href="/app.css">` | The bundled stylesheet |
|
|
62
62
|
| `<a lb-nav-link>` | Navigation between pages |
|
|
63
|
-
| `lb-
|
|
63
|
+
| `lb-row="lb-navigation"` | Where the page is — see below |
|
|
64
64
|
| `<dialog lb-unknown-page>` | A message when a URL matches no page |
|
|
65
65
|
| Custom elements | The chrome, decomposed into widget files |
|
|
66
66
|
| `<body hidden>` + a `<noscript>` fallback | Avoids a first-load blink — see below |
|
|
@@ -77,8 +77,9 @@ A navigation anchor's `href` is a path, and the path names a page:
|
|
|
77
77
|
landing page is the one named `index.page.html`. An anchor without
|
|
78
78
|
`lb-nav-link` is left alone and behaves like any other link.
|
|
79
79
|
|
|
80
|
-
The `lb-unknown-page` attribute, if used, must appear on a `<dialog
|
|
81
|
-
hub
|
|
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:
|
|
82
83
|
the dialog is a subtree like any other, and it displays where the page is by
|
|
83
84
|
naming the hub's own query, described next.
|
|
84
85
|
|
|
@@ -86,11 +87,11 @@ naming the hub's own query, described next.
|
|
|
86
87
|
|
|
87
88
|
On every navigation the hub lands a query of its own, `lb-navigation`, on
|
|
88
89
|
any subtree inside the hub that names it. It arrives the way a server's
|
|
89
|
-
|
|
90
|
+
row arrives — `lb-row` on the subtree, `lb-cell` on each element that
|
|
90
91
|
shows a value — so a chrome displays the current page with no code at all:
|
|
91
92
|
|
|
92
93
|
```html
|
|
93
|
-
<header lb-
|
|
94
|
+
<header lb-row="lb-navigation">
|
|
94
95
|
<h1>Membership Roster</h1>
|
|
95
96
|
<h2 lb-cell="page-label"></h2>
|
|
96
97
|
</header>
|
|
@@ -112,12 +113,12 @@ namespace: no server answers a query so named. It is landed only where a
|
|
|
112
113
|
subtree names it, so a chrome that displays no navigation is not warned
|
|
113
114
|
about a query with no scope.
|
|
114
115
|
|
|
115
|
-
The same
|
|
116
|
+
The same row is what an unknown-page dialog has to work with. It lands
|
|
116
117
|
before the page host is looked up, so a miss has it too. Name the query on
|
|
117
118
|
the dialog and show whichever cell fits:
|
|
118
119
|
|
|
119
120
|
```html
|
|
120
|
-
<dialog lb-unknown-page lb-
|
|
121
|
+
<dialog lb-unknown-page lb-row="lb-navigation">
|
|
121
122
|
The URL <span lb-cell="page-uri"></span> is not in this app.
|
|
122
123
|
</dialog>
|
|
123
124
|
```
|
|
@@ -223,12 +223,15 @@ as a string literal.
|
|
|
223
223
|
|------------------|------------|
|
|
224
224
|
| `ATTR_VALUE` | `lb-value` |
|
|
225
225
|
| `ATTR_CELL` | `lb-cell` |
|
|
226
|
-
| `
|
|
226
|
+
| `ATTR_LIST` | `lb-list` |
|
|
227
|
+
| `ATTR_ROW` | `lb-row` |
|
|
227
228
|
| `ATTR_KEY` | `lb-key` |
|
|
228
|
-
| `
|
|
229
|
-
| `ATTR_SORT` | `lb-sort` |
|
|
229
|
+
| `ATTR_KEY_VALUE` | `lb-key-value` |
|
|
230
230
|
| `ATTR_ACTION` | `lb-action`|
|
|
231
|
-
| `
|
|
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`|
|
|
232
235
|
| `LB_EVENT_NAME` | `lb-request` |
|
|
233
236
|
|
|
234
237
|
### Receiving a value
|
|
@@ -266,20 +269,20 @@ click — builds its own request and dispatches it as a bubbling
|
|
|
266
269
|
as its `detail`:
|
|
267
270
|
|
|
268
271
|
```ts
|
|
269
|
-
import { ATTR_CELL,
|
|
272
|
+
import { ATTR_CELL, ATTR_KEY_VALUE, ATTR_LIST, LB_EVENT_NAME } from "@loadbare/app/constants";
|
|
270
273
|
import type { HubRequest } from "@loadbare/app/types";
|
|
271
274
|
|
|
272
275
|
const detail: HubRequest = {
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
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)!,
|
|
276
279
|
cell: this.getAttribute(ATTR_CELL)!,
|
|
277
280
|
value: input.value,
|
|
278
281
|
};
|
|
279
282
|
this.dispatchEvent(new CustomEvent(LB_EVENT_NAME, { bubbles: true, detail }));
|
|
280
283
|
```
|
|
281
284
|
|
|
282
|
-
Find
|
|
285
|
+
Find the scope name and `key` by walking up with `closest()`, the same way the hub
|
|
283
286
|
finds the binding for a native element. See
|
|
284
287
|
[Data Binding](./data-binding.md#requests) for the request vocabulary and
|
|
285
288
|
what each operation carries.
|
|
@@ -288,54 +291,66 @@ Let the event bubble, so an ancestor widget can intercept and stop it before
|
|
|
288
291
|
the hub sees it. A hand-written widget and a native element carrying
|
|
289
292
|
`lb-action` produce the same event.
|
|
290
293
|
|
|
291
|
-
###
|
|
294
|
+
### Decorating a list
|
|
292
295
|
|
|
293
|
-
|
|
294
|
-
|
|
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.
|
|
300
|
+
|
|
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:
|
|
296
304
|
|
|
297
305
|
```ts
|
|
298
|
-
import {
|
|
299
|
-
|
|
306
|
+
import type { ListHost, Row } from "@loadbare/app/types";
|
|
307
|
+
|
|
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
|
+
}
|
|
300
313
|
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
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.
|
|
304
318
|
}
|
|
305
319
|
}
|
|
306
320
|
```
|
|
307
321
|
|
|
308
|
-
|
|
309
|
-
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:
|
|
310
323
|
|
|
311
|
-
| Concern | What
|
|
324
|
+
| Concern | What the hub does |
|
|
312
325
|
|-------------|---------------------------------------------------------|
|
|
313
|
-
| Cloning | Clones the `<template lb-key="...">` in the
|
|
326
|
+
| Cloning | Clones the `<template lb-key="...">` in the scope |
|
|
314
327
|
| Matching | Updates the row already showing that key, or clones one |
|
|
315
328
|
| Reconciling | Removes the rows the response says are gone |
|
|
316
|
-
| Counting | Stamps `
|
|
329
|
+
| Counting | Stamps `lb-row-count` with the number of rows showing |
|
|
317
330
|
|
|
318
|
-
|
|
319
|
-
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
|
|
320
333
|
every other row's contents and position alone.
|
|
321
334
|
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
order.
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
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.
|
|
329
344
|
|
|
330
|
-
Style an empty list against `
|
|
345
|
+
Style an empty list against `lb-row-count` rather than carrying an empty-state
|
|
331
346
|
conditional in the widget — see
|
|
332
347
|
[Conditional rendering](./data-binding.md#conditional-rendering).
|
|
333
348
|
|
|
334
349
|
### Filling a scope by hand
|
|
335
350
|
|
|
336
|
-
`@loadbare/app
|
|
337
|
-
|
|
338
|
-
|
|
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`
|
|
339
354
|
itself when `root` carries a matching `lb-cell`, and every matching
|
|
340
355
|
descendant.
|
|
341
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,33 +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
|
-
One
|
|
60
|
-
every navigation with the
|
|
61
|
-
same way
|
|
62
|
-
|
|
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).
|
|
63
79
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
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.
|
|
67
85
|
|
|
68
86
|
### Where a bound value lands
|
|
69
87
|
|
|
@@ -82,40 +100,71 @@ observing `lb-value`.
|
|
|
82
100
|
Nothing an application writes ever sets `lb-value`. It is written by
|
|
83
101
|
Loadbare and read by the widget it is written on.
|
|
84
102
|
|
|
85
|
-
###
|
|
86
|
-
|
|
87
|
-
A query that answers with many rows is delivered to a list widget, which
|
|
88
|
-
clones its row template once per row and binds each clone to one row. A
|
|
89
|
-
native element has one destination for a value and cannot acquire children,
|
|
90
|
-
so a query that answers with rows must be bound to a widget that accepts
|
|
91
|
-
them.
|
|
103
|
+
### Lists
|
|
92
104
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
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.
|
|
98
109
|
|
|
99
|
-
|
|
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.
|
|
100
113
|
|
|
101
|
-
|
|
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).
|
|
102
120
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
| `lb-delete` | `tupleDelete` | `query`, `key` |
|
|
107
|
-
| `lb-insert` on a `<form>` | `tupleInsert` | `query`, `values` |
|
|
108
|
-
| `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.
|
|
109
124
|
|
|
110
|
-
|
|
111
|
-
`query`, `key`, `cell`, and `value`. It sends one when the page applies
|
|
112
|
-
`data-fire-on-change` to it, and stays quiet otherwise — an input inside an
|
|
113
|
-
`lb-insert` or `lb-update` form is read again by the form on submit, so a
|
|
114
|
-
widget that sent on its own would write the same edit twice.
|
|
125
|
+
## Requests
|
|
115
126
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
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).
|
|
119
168
|
|
|
120
169
|
### Actions
|
|
121
170
|
|
|
@@ -126,10 +175,10 @@ one of the four CRUD operations:
|
|
|
126
175
|
<button lb-action="mailRoster">Mail the roster</button>
|
|
127
176
|
```
|
|
128
177
|
|
|
129
|
-
An action carries whatever binding is in scope at the element — `
|
|
130
|
-
`
|
|
131
|
-
carries no value, so the server computes the whole
|
|
132
|
-
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.
|
|
133
182
|
|
|
134
183
|
Write `lb-action` on a widget to have the widget decide what performing the
|
|
135
184
|
action means. Loadbare turns a click into a request for a native element
|
|
@@ -139,30 +188,31 @@ on change, not on click.
|
|
|
139
188
|
### Deleting a row
|
|
140
189
|
|
|
141
190
|
```html
|
|
142
|
-
<button lb-delete>Remove</button>
|
|
191
|
+
<button lb-action="lb-row-delete">Remove</button>
|
|
143
192
|
```
|
|
144
193
|
|
|
145
|
-
`lb-delete` needs
|
|
146
|
-
scope, and they are all the server needs to know which row is
|
|
147
|
-
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.
|
|
148
197
|
|
|
149
|
-
|
|
150
|
-
sends its own request.
|
|
198
|
+
The hub sends it from a native element. A widget sends its own request.
|
|
151
199
|
|
|
152
200
|
### Forms
|
|
153
201
|
|
|
154
|
-
`lb-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
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.
|
|
158
208
|
|
|
159
|
-
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
|
|
160
210
|
from the same ancestor a delete button reads:
|
|
161
211
|
|
|
162
212
|
```html
|
|
163
213
|
<template lb-key="id">
|
|
164
214
|
<li>
|
|
165
|
-
<form lb-update>
|
|
215
|
+
<form lb-action="lb-row-update">
|
|
166
216
|
<input lb-cell="name" />
|
|
167
217
|
<button type="submit">Save</button>
|
|
168
218
|
</form>
|
|
@@ -193,31 +243,34 @@ HTML.
|
|
|
193
243
|
|
|
194
244
|
### An empty list
|
|
195
245
|
|
|
196
|
-
|
|
246
|
+
The hub stamps every list scope with `lb-row-count`, the number of rows it is
|
|
197
247
|
showing. It is the one conditional a page cannot be sent, because the server
|
|
198
|
-
answers with rows and says nothing about how many survived. It makes an
|
|
199
|
-
|
|
248
|
+
answers with rows and says nothing about how many survived. It makes an empty
|
|
249
|
+
list a stylesheet rule rather than code anywhere:
|
|
200
250
|
|
|
201
251
|
```html
|
|
202
|
-
<lb-list
|
|
252
|
+
<div lb-list="roster">
|
|
203
253
|
<ul>
|
|
204
254
|
<template lb-key="id">
|
|
205
255
|
<li lb-cell="name"></li>
|
|
206
256
|
</template>
|
|
207
257
|
</ul>
|
|
208
258
|
<p class="roster-empty">No members yet.</p>
|
|
209
|
-
</
|
|
259
|
+
</div>
|
|
210
260
|
```
|
|
211
261
|
|
|
212
262
|
```css
|
|
213
263
|
.roster-empty {
|
|
214
264
|
display: none;
|
|
215
265
|
}
|
|
216
|
-
lb-
|
|
266
|
+
[lb-row-count="0"] .roster-empty {
|
|
217
267
|
display: revert;
|
|
218
268
|
}
|
|
219
269
|
```
|
|
220
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
|
+
|
|
221
274
|
## Request state
|
|
222
275
|
|
|
223
276
|
Loadbare stamps two attributes on the element a request came from — the
|
|
@@ -225,11 +278,11 @@ button, the form, or the widget itself:
|
|
|
225
278
|
|
|
226
279
|
| Attribute | Means |
|
|
227
280
|
|-------------------|-------------------------------------------|
|
|
228
|
-
| `
|
|
229
|
-
| `
|
|
281
|
+
| `lb-pending` | The request is in flight |
|
|
282
|
+
| `lb-error` | The last request from this element failed |
|
|
230
283
|
|
|
231
|
-
`
|
|
232
|
-
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
|
|
233
286
|
a timeout alike, and cleared when that element sends its next request.
|
|
234
287
|
|
|
235
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
|