@loadbare/app 0.5.6 → 0.7.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 +81 -7
- package/dist/build/assemble.js.map +1 -0
- package/dist/build/cli.d.ts +2 -2
- package/dist/build/cli.js +3 -2
- package/dist/build/cli.js.map +1 -0
- package/dist/build/elements.js +1 -0
- package/dist/build/elements.js.map +1 -0
- package/dist/build/expand.d.ts.map +1 -1
- package/dist/build/expand.js +20 -32
- package/dist/build/expand.js.map +1 -0
- package/dist/build/format.js +1 -0
- package/dist/build/format.js.map +1 -0
- package/dist/build/locations.d.ts +4 -4
- package/dist/build/locations.d.ts.map +1 -1
- package/dist/build/locations.js +16 -5
- package/dist/build/locations.js.map +1 -0
- package/dist/build/origins.d.ts +0 -13
- package/dist/build/origins.d.ts.map +1 -1
- package/dist/build/origins.js +33 -8
- package/dist/build/origins.js.map +1 -0
- package/dist/build/package-root.js +1 -0
- package/dist/build/package-root.js.map +1 -0
- package/dist/build/pages.d.ts +7 -3
- package/dist/build/pages.d.ts.map +1 -1
- package/dist/build/pages.js +14 -7
- package/dist/build/pages.js.map +1 -0
- package/dist/build/styles.js +1 -0
- package/dist/build/styles.js.map +1 -0
- 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 +104 -53
- package/dist/core/lb-constants.js.map +1 -0
- 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 +12 -3
- package/dist/core/lb-types.js.map +1 -0
- package/dist/hub/lb-apply.d.ts +28 -4
- package/dist/hub/lb-apply.d.ts.map +1 -1
- package/dist/hub/lb-apply.js +227 -40
- package/dist/hub/lb-apply.js.map +1 -0
- 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 -110
- package/dist/hub/lb-hub.browser.js.map +1 -0
- package/dist/server/lb-express.d.ts +8 -5
- package/dist/server/lb-express.d.ts.map +1 -1
- package/dist/server/lb-express.js +46 -33
- package/dist/server/lb-express.js.map +1 -0
- 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 +56 -18
- package/dist/server/lb-server.js.map +1 -0
- package/docs/TECHREF-1.0.md +1107 -0
- package/docs/analysis-accidental-complexity.md +149 -0
- package/docs/reference/builder.md +4 -4
- package/docs/reference/chrome.md +10 -9
- package/docs/reference/custom-elements.md +54 -46
- package/docs/reference/data-binding.md +179 -97
- package/docs/reference/overview.md +1 -1
- package/docs/reference/page-files.md +64 -49
- package/docs/reference/server.md +25 -6
- package/docs/reference/widgets.md +22 -30
- package/docs/roadmap.md +68 -22
- package/docs/testing.md +47 -17
- package/docs/theory.md +116 -3
- 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 +18 -15
- 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 +22 -35
- package/docs/tutorials/090-using-widget-libraries.md +1 -1
- package/package.json +2 -3
- 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/assemble.test.d.ts +0 -8
- package/dist/tests/assemble.test.d.ts.map +0 -1
- package/dist/tests/assemble.test.js +0 -58
- package/dist/tests/elements.test.d.ts +0 -8
- package/dist/tests/elements.test.d.ts.map +0 -1
- package/dist/tests/elements.test.js +0 -118
- package/dist/tests/expand.test.d.ts +0 -10
- package/dist/tests/expand.test.d.ts.map +0 -1
- package/dist/tests/expand.test.js +0 -250
- package/dist/tests/fixtures/elements/collision/imports.d.ts +0 -3
- package/dist/tests/fixtures/elements/collision/imports.d.ts.map +0 -1
- package/dist/tests/fixtures/elements/collision/imports.js +0 -1
- package/dist/tests/fixtures/elements/collision/widgets/acme-widget.browser.d.ts +0 -2
- package/dist/tests/fixtures/elements/collision/widgets/acme-widget.browser.d.ts.map +0 -1
- package/dist/tests/fixtures/elements/collision/widgets/acme-widget.browser.js +0 -1
- package/dist/tests/fixtures/elements/local/widgets/app-box.browser.d.ts +0 -2
- package/dist/tests/fixtures/elements/local/widgets/app-box.browser.d.ts.map +0 -1
- package/dist/tests/fixtures/elements/local/widgets/app-box.browser.js +0 -1
- package/dist/tests/fixtures/elements/manifest/imports.d.ts +0 -3
- package/dist/tests/fixtures/elements/manifest/imports.d.ts.map +0 -1
- package/dist/tests/fixtures/elements/manifest/imports.js +0 -1
- package/dist/tests/fixtures/elements/manifest-bad-entry/imports.d.ts +0 -3
- package/dist/tests/fixtures/elements/manifest-bad-entry/imports.d.ts.map +0 -1
- package/dist/tests/fixtures/elements/manifest-bad-entry/imports.js +0 -1
- package/dist/tests/fixtures/elements/manifest-not-array/imports.d.ts +0 -5
- package/dist/tests/fixtures/elements/manifest-not-array/imports.d.ts.map +0 -1
- package/dist/tests/fixtures/elements/manifest-not-array/imports.js +0 -1
- package/dist/tests/fixtures/elements/pkg/acme-widget.browser.d.ts +0 -2
- package/dist/tests/fixtures/elements/pkg/acme-widget.browser.d.ts.map +0 -1
- package/dist/tests/fixtures/elements/pkg/acme-widget.browser.js +0 -1
- package/dist/tests/fixtures/elements/unmarked/widgets/app-box.d.ts +0 -6
- package/dist/tests/fixtures/elements/unmarked/widgets/app-box.d.ts.map +0 -1
- package/dist/tests/fixtures/elements/unmarked/widgets/app-box.js +0 -1
- package/dist/tests/helpers/console.d.ts +0 -20
- package/dist/tests/helpers/console.d.ts.map +0 -1
- package/dist/tests/helpers/console.js +0 -28
- package/dist/tests/helpers/dom.d.ts +0 -18
- package/dist/tests/helpers/dom.d.ts.map +0 -1
- package/dist/tests/helpers/dom.js +0 -22
- package/dist/tests/lb-apply.test.d.ts +0 -8
- package/dist/tests/lb-apply.test.d.ts.map +0 -1
- package/dist/tests/lb-apply.test.js +0 -153
- package/dist/tests/lb-express.test.d.ts +0 -14
- package/dist/tests/lb-express.test.d.ts.map +0 -1
- package/dist/tests/lb-express.test.js +0 -238
- package/dist/tests/lb-rows.test.d.ts +0 -12
- package/dist/tests/lb-rows.test.d.ts.map +0 -1
- package/dist/tests/lb-rows.test.js +0 -336
- package/dist/tests/lb-server.test.d.ts +0 -9
- package/dist/tests/lb-server.test.d.ts.map +0 -1
- package/dist/tests/lb-server.test.js +0 -495
- package/dist/tests/origins.test.d.ts +0 -10
- package/dist/tests/origins.test.d.ts.map +0 -1
- package/dist/tests/origins.test.js +0 -369
- package/dist/tests/pages.test.d.ts +0 -6
- package/dist/tests/pages.test.d.ts.map +0 -1
- package/dist/tests/pages.test.js +0 -98
- package/dist/tests/styles.test.d.ts +0 -7
- package/dist/tests/styles.test.d.ts.map +0 -1
- package/dist/tests/styles.test.js +0 -76
|
@@ -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,23 @@ 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
|
-
<
|
|
19
|
-
<
|
|
20
|
-
|
|
21
|
-
</
|
|
22
|
+
<section lb-list="roster">
|
|
23
|
+
<form lb-action="lb-row-insert">
|
|
24
|
+
<input lb-cell="name" placeholder="Name" />
|
|
25
|
+
<button type="submit">Add member</button>
|
|
26
|
+
</form>
|
|
22
27
|
|
|
23
|
-
<lb-list lb-query="roster">
|
|
24
28
|
<ul>
|
|
25
29
|
<template lb-key="id">
|
|
26
30
|
<li>
|
|
27
31
|
<lb-input lb-cell="name"></lb-input>
|
|
28
|
-
<button lb-delete>Remove</button>
|
|
32
|
+
<button lb-action="lb-row-delete">Remove</button>
|
|
29
33
|
</li>
|
|
30
34
|
</template>
|
|
31
35
|
</ul>
|
|
32
|
-
</
|
|
36
|
+
</section>
|
|
33
37
|
```
|
|
34
38
|
|
|
35
39
|
The queries named here — `dues` and `roster` — and the operations the page
|
|
@@ -37,85 +41,135 @@ asks for are declared on the server; see [page files](./page-files.md).
|
|
|
37
41
|
|
|
38
42
|
## Binding
|
|
39
43
|
|
|
40
|
-
| Attribute | Names
|
|
41
|
-
|
|
42
|
-
| `lb-
|
|
43
|
-
| `lb-
|
|
44
|
-
| `lb-
|
|
45
|
-
| `lb-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
44
|
+
| Attribute | Written by | Names |
|
|
45
|
+
|----------------|-------------|-------------------------------------------|
|
|
46
|
+
| `lb-list` | a developer | The set of rows a subtree displays |
|
|
47
|
+
| `lb-row` | a developer | The one row a subtree displays |
|
|
48
|
+
| `lb-key` | a developer | The column that identifies a row |
|
|
49
|
+
| `lb-cell` | a developer | The column an element displays |
|
|
50
|
+
| `lb-key-value` | the hub | A live row's own key |
|
|
51
|
+
| `lb-value` | the hub | The value that landed on a cell |
|
|
52
|
+
|
|
53
|
+
A binding is scoped by ancestry. Either scope attribute scopes its DOM
|
|
54
|
+
children, and a nested one of either kind begins a new scope, so an element
|
|
55
|
+
binds to the name on its nearest ancestor carrying one, and to the row on its
|
|
56
|
+
nearest ancestor carrying `lb-key-value`. Nothing else establishes scope: an
|
|
57
|
+
element outside every scope is bound to nothing and displays nothing, and a
|
|
58
|
+
result never crosses into a nested scope.
|
|
59
|
+
|
|
60
|
+
Which of the two a subtree writes is not a choice about display. Cardinality
|
|
61
|
+
is a property of the name, so one name answers with one shape, always. A page
|
|
62
|
+
that shows the roster both as a set and as a single row declares two queries,
|
|
63
|
+
`rosterList` and `rosterRow`, and binds each with the attribute that matches
|
|
64
|
+
what it answers with.
|
|
51
65
|
|
|
52
66
|
Bind an element to a cell by putting `lb-cell` on the element that shows the
|
|
53
|
-
value.
|
|
54
|
-
|
|
67
|
+
value. Its value is a column name and never a cell name: a cell has no name
|
|
68
|
+
of its own, because it is identified by its row and its column, and the row
|
|
69
|
+
arrives from scope. The scope's own root counts as a cell if it carries one,
|
|
70
|
+
which is how an `<option>` — whose content model is text — displays the value
|
|
71
|
+
it is.
|
|
55
72
|
|
|
56
73
|
Name the same query on more than one subtree to display it in more than one
|
|
57
74
|
place. Every subtree gets the result.
|
|
58
75
|
|
|
59
|
-
One
|
|
60
|
-
every navigation with the
|
|
61
|
-
same way
|
|
62
|
-
|
|
76
|
+
One name is the hub's rather than the server's: `lb-navigation`, one row
|
|
77
|
+
landed on every navigation with the columns `page-label` and `page-uri`. It binds the
|
|
78
|
+
same way. Its name is reserved, as every value beginning with `lb-` is in
|
|
79
|
+
every `lb-` attribute: the server refuses a page that declares a query so
|
|
80
|
+
named — see [Where the page is](./chrome.md#where-the-page-is).
|
|
63
81
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
82
|
+
A key has a name and a value, and they are two attributes. `lb-key` on a row
|
|
83
|
+
template names the column that identifies a row: it is a property of the
|
|
84
|
+
list, since a list without a fixed key column is meaningless, written where
|
|
85
|
+
the rows land. `lb-key-value` on a row that is showing carries that row's
|
|
86
|
+
value of it. A developer writes the first and never the second.
|
|
67
87
|
|
|
68
88
|
### Where a bound value lands
|
|
69
89
|
|
|
70
|
-
A value lands on a bound element one of
|
|
90
|
+
A value lands on a bound element one of three ways.
|
|
71
91
|
|
|
72
|
-
| Element
|
|
73
|
-
|
|
74
|
-
| A
|
|
75
|
-
|
|
|
92
|
+
| Element | Receives the value as |
|
|
93
|
+
|----------------------------------------|-----------------------------------|
|
|
94
|
+
| A custom element | Its `lb-value` attribute |
|
|
95
|
+
| `<select>`, `<textarea>`, or `<input>` | Its `value`, and `lb-value` |
|
|
96
|
+
| Any other native element | Its `textContent`, and `lb-value` |
|
|
76
97
|
|
|
77
|
-
A
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
98
|
+
A widget owns whatever control it wraps, so it is handed the value and
|
|
99
|
+
renders it itself — see [Custom Elements](./custom-elements.md#code) for
|
|
100
|
+
observing `lb-value`. A form control shows its state as its `value`, so a
|
|
101
|
+
`<select>` keeps its options. Any other native element has no behavior of its
|
|
102
|
+
own, so its value is its text. Checkboxes, radio buttons and file inputs
|
|
103
|
+
receive nothing, not even `lb-value`, and the hub reports it to the console.
|
|
81
104
|
|
|
82
105
|
Nothing an application writes ever sets `lb-value`. It is written by
|
|
83
|
-
Loadbare and read by
|
|
106
|
+
Loadbare and read by a widget or a stylesheet.
|
|
84
107
|
|
|
85
|
-
###
|
|
108
|
+
### Lists
|
|
86
109
|
|
|
87
|
-
|
|
88
|
-
clones
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
them.
|
|
110
|
+
Write `lb-list` on any element, and a `<template>` inside it carrying
|
|
111
|
+
`lb-key`. The hub clones that template once per row, fills each clone, and
|
|
112
|
+
reconciles what is showing against what arrived. No widget is involved, and
|
|
113
|
+
none is needed.
|
|
92
114
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
rows, and for `lb-group` and `lb-sort`, which a list widget reads to decide
|
|
97
|
-
where a row goes.
|
|
115
|
+
An array is the whole set, so it decides membership and order: a row whose
|
|
116
|
+
key did not arrive is gone. A patch touches only the rows it names and leaves
|
|
117
|
+
every other row's contents and position alone.
|
|
98
118
|
|
|
99
|
-
|
|
119
|
+
A widget enters only where the rows need scaffolding or placement that only
|
|
120
|
+
it can decide — a `<select>` that builds an `<optgroup>` per distinct value, a
|
|
121
|
+
table that sections and sorts. Such a widget carries `lb-list` itself and
|
|
122
|
+
implements one or both of two optional methods; see
|
|
123
|
+
[Decorating a list](./custom-elements.md#decorating-a-list) and
|
|
124
|
+
[The Basic Widget Library](./widgets.md).
|
|
100
125
|
|
|
101
|
-
|
|
126
|
+
A scope with `lb-list` and no row template displays nothing, and that is not
|
|
127
|
+
an error. It is bound to the list without showing it, which is what an insert
|
|
128
|
+
form naming the list it adds a row to already is.
|
|
102
129
|
|
|
103
|
-
|
|
104
|
-
|---------------------------|------------------|------------------------------|
|
|
105
|
-
| `lb-action="name"` | The named action | `name`, and what is in scope |
|
|
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` |
|
|
109
|
-
|
|
110
|
-
The shipped `<lb-input>` widget asks for a fifth, `cellChange`, carrying
|
|
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.
|
|
130
|
+
## Requests
|
|
115
131
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
132
|
+
One attribute turns an interaction into a request. `lb-action` names what the
|
|
133
|
+
server is asked for: an action the page declared, or one of Loadbare's
|
|
134
|
+
reserved names, which are the CRUD operations.
|
|
135
|
+
|
|
136
|
+
One attribute name, one wire field, one set of values. The request field
|
|
137
|
+
`action` carries this attribute's value verbatim, so nothing is translated
|
|
138
|
+
between the markup and the server, and the CRUD key is the same value with
|
|
139
|
+
the prefix stripped and the rest camel-cased.
|
|
140
|
+
|
|
141
|
+
| Written | Asks for | Carries |
|
|
142
|
+
|-------------------------------------------|--------------|-------------------------------|
|
|
143
|
+
| `lb-action="name"` | That action | The scope, and what is in it |
|
|
144
|
+
| `lb-action="lb-row-delete"` | `rowDelete` | `list`, `key` |
|
|
145
|
+
| `lb-action="lb-row-insert"` on a `<form>` | `rowInsert` | `list`, `values` |
|
|
146
|
+
| `lb-action="lb-row-update"` on a `<form>` | `rowUpdate` | `list`, `key`, `values` |
|
|
147
|
+
| `lb-action="lb-cell-change"` on a widget | `cellChange` | `list`, `key`, `cell`, `value`|
|
|
148
|
+
|
|
149
|
+
All four operations are list operations. Each needs a key, and a key exists
|
|
150
|
+
only on a live row the hub stamped inside a list, so a single-row scope is
|
|
151
|
+
read-only and a declared action is the only thing it can send. An application
|
|
152
|
+
that wants a writable single row declares a list that answers with one row.
|
|
153
|
+
|
|
154
|
+
A name beginning with `lb-` is reserved, in `lb-action` and in either scope
|
|
155
|
+
attribute alike. The server refuses a page that declares an action or a query
|
|
156
|
+
so named, which is what lets a reserved name be added later without colliding
|
|
157
|
+
with one an application already uses. That reservation is also the whole of
|
|
158
|
+
the wire discriminant: a value beginning with `lb-` is an operation, and
|
|
159
|
+
anything else is a name the page declared.
|
|
160
|
+
|
|
161
|
+
The hub sends the first four from a native element on the element's own
|
|
162
|
+
event: a form on submit, anything else on click. A cell change needs a
|
|
163
|
+
widget to say what a change is, so only a widget sends it: the shipped
|
|
164
|
+
`<lb-input>` does when it carries `lb-action="lb-cell-change"`, and stays
|
|
165
|
+
quiet otherwise — an input inside an `lb-row-insert` or `lb-row-update` form is read
|
|
166
|
+
again by the form on submit, so a widget that sent on its own would write
|
|
167
|
+
the same edit twice.
|
|
168
|
+
|
|
169
|
+
Declare every action on the server, and permit every operation on its list. A
|
|
170
|
+
name the page has not declared, and an operation a list does not permit, are
|
|
171
|
+
refused; see
|
|
172
|
+
[requests, actions, CRUD](./page-files.md#requests-actions-crud).
|
|
119
173
|
|
|
120
174
|
### Actions
|
|
121
175
|
|
|
@@ -126,10 +180,10 @@ one of the four CRUD operations:
|
|
|
126
180
|
<button lb-action="mailRoster">Mail the roster</button>
|
|
127
181
|
```
|
|
128
182
|
|
|
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.
|
|
183
|
+
An action carries whatever binding is in scope at the element — `list` or
|
|
184
|
+
`row`, whichever scoped it, plus `key` and `cell` — and nothing else. There is
|
|
185
|
+
no argument list. A button carries no value, so the server computes the whole
|
|
186
|
+
of the new state and the page displays only what came back.
|
|
133
187
|
|
|
134
188
|
Write `lb-action` on a widget to have the widget decide what performing the
|
|
135
189
|
action means. Loadbare turns a click into a request for a native element
|
|
@@ -139,30 +193,31 @@ on change, not on click.
|
|
|
139
193
|
### Deleting a row
|
|
140
194
|
|
|
141
195
|
```html
|
|
142
|
-
<button lb-delete>Remove</button>
|
|
196
|
+
<button lb-action="lb-row-delete">Remove</button>
|
|
143
197
|
```
|
|
144
198
|
|
|
145
|
-
`lb-delete` needs
|
|
146
|
-
scope, and they are all the server needs to know which row is
|
|
147
|
-
whether the
|
|
199
|
+
`lb-row-delete` needs nothing declared. The row's `lb-list` and `lb-key-value`
|
|
200
|
+
are already in scope, and they are all the server needs to know which row is
|
|
201
|
+
meant and whether the list permits deleting it.
|
|
148
202
|
|
|
149
|
-
|
|
150
|
-
sends its own request.
|
|
203
|
+
The hub sends it from a native element. A widget sends its own request.
|
|
151
204
|
|
|
152
205
|
### Forms
|
|
153
206
|
|
|
154
|
-
`lb-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
207
|
+
A `<form>` performs its `lb-action` on submit. `lb-row-insert` and `lb-row-update`
|
|
208
|
+
both gather every `lb-cell` inside the form into one values map, read from
|
|
209
|
+
the control each cell is or wraps. They differ in one thing: `lb-row-update`
|
|
210
|
+
also carries the key of the row it is inside, and `lb-row-insert` carries none,
|
|
211
|
+
because there is no row yet. A declared name on a form sends that action on
|
|
212
|
+
submit, carrying the binding and no values.
|
|
158
213
|
|
|
159
|
-
Put an `lb-update` form inside the row it edits, so it has that row's key
|
|
214
|
+
Put an `lb-row-update` form inside the row it edits, so it has that row's key
|
|
160
215
|
from the same ancestor a delete button reads:
|
|
161
216
|
|
|
162
217
|
```html
|
|
163
218
|
<template lb-key="id">
|
|
164
219
|
<li>
|
|
165
|
-
<form lb-update>
|
|
220
|
+
<form lb-action="lb-row-update">
|
|
166
221
|
<input lb-cell="name" />
|
|
167
222
|
<button type="submit">Save</button>
|
|
168
223
|
</form>
|
|
@@ -180,9 +235,33 @@ Loadbare ships static HTML and hydrates elements that are already in the
|
|
|
180
235
|
document. There is no `if`, and none is needed: write every possibility into
|
|
181
236
|
the page, and control which of them is showing.
|
|
182
237
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
238
|
+
Every cell carries the value that landed on it as `lb-value`, so a
|
|
239
|
+
stylesheet can show or hide part of a page from a value the server sent. Bind
|
|
240
|
+
a column the page does not display to a hidden element:
|
|
241
|
+
|
|
242
|
+
```html
|
|
243
|
+
<template lb-key="id">
|
|
244
|
+
<tr>
|
|
245
|
+
<td lb-cell="name"></td>
|
|
246
|
+
<td lb-cell="locked" hidden></td>
|
|
247
|
+
<td><button lb-action="lb-row-delete">Remove</button></td>
|
|
248
|
+
</tr>
|
|
249
|
+
</template>
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
```css
|
|
253
|
+
tr:has([lb-cell="locked"][lb-value="true"]) button {
|
|
254
|
+
display: none;
|
|
255
|
+
}
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
`lb-value` holds the string the query sent, so the query decides the spelling
|
|
259
|
+
the selector matches. A stylesheet can hide a control but cannot disable one,
|
|
260
|
+
and hiding is presentation: the server still refuses what a request may not
|
|
261
|
+
do.
|
|
262
|
+
|
|
263
|
+
Where showing a case takes more than a selector, a widget receives the value
|
|
264
|
+
in `lb-value` and decides what to show:
|
|
186
265
|
|
|
187
266
|
```ts
|
|
188
267
|
for (const step of steps) step.hidden = step.dataset.step !== value;
|
|
@@ -193,31 +272,34 @@ HTML.
|
|
|
193
272
|
|
|
194
273
|
### An empty list
|
|
195
274
|
|
|
196
|
-
|
|
275
|
+
The hub stamps every list scope with `lb-row-count`, the number of rows it is
|
|
197
276
|
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
|
-
|
|
277
|
+
answers with rows and says nothing about how many survived. It makes an empty
|
|
278
|
+
list a stylesheet rule rather than code anywhere:
|
|
200
279
|
|
|
201
280
|
```html
|
|
202
|
-
<lb-list
|
|
281
|
+
<div lb-list="roster">
|
|
203
282
|
<ul>
|
|
204
283
|
<template lb-key="id">
|
|
205
284
|
<li lb-cell="name"></li>
|
|
206
285
|
</template>
|
|
207
286
|
</ul>
|
|
208
287
|
<p class="roster-empty">No members yet.</p>
|
|
209
|
-
</
|
|
288
|
+
</div>
|
|
210
289
|
```
|
|
211
290
|
|
|
212
291
|
```css
|
|
213
292
|
.roster-empty {
|
|
214
293
|
display: none;
|
|
215
294
|
}
|
|
216
|
-
lb-
|
|
295
|
+
[lb-row-count="0"] .roster-empty {
|
|
217
296
|
display: revert;
|
|
218
297
|
}
|
|
219
298
|
```
|
|
220
299
|
|
|
300
|
+
The scope is a `<div>` here rather than the `<ul>`, so that the empty message
|
|
301
|
+
is inside it and the same rule can reach both.
|
|
302
|
+
|
|
221
303
|
## Request state
|
|
222
304
|
|
|
223
305
|
Loadbare stamps two attributes on the element a request came from — the
|
|
@@ -225,11 +307,11 @@ button, the form, or the widget itself:
|
|
|
225
307
|
|
|
226
308
|
| Attribute | Means |
|
|
227
309
|
|-------------------|-------------------------------------------|
|
|
228
|
-
| `
|
|
229
|
-
| `
|
|
310
|
+
| `lb-pending` | The request is in flight |
|
|
311
|
+
| `lb-error` | The last request from this element failed |
|
|
230
312
|
|
|
231
|
-
`
|
|
232
|
-
settles. `
|
|
313
|
+
`lb-pending` is set when the request goes out and removed when it
|
|
314
|
+
settles. `lb-error` is set on a failed response, a network failure, or
|
|
233
315
|
a timeout alike, and cleared when that element sends its next request.
|
|
234
316
|
|
|
235
317
|
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
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
# Page Files
|
|
2
2
|
|
|
3
3
|
A page is a set of files sharing one base name. The application writes the
|
|
4
|
-
HTML, and adds queries and
|
|
4
|
+
HTML, and adds queries and requests when the page shows data.
|
|
5
5
|
|
|
6
|
-
| File
|
|
7
|
-
|
|
8
|
-
| `<name>.page.html`
|
|
9
|
-
| `<name>.queries.ts`
|
|
10
|
-
| `<name>.
|
|
6
|
+
| File | Holds |
|
|
7
|
+
|----------------------|-------------------------------|
|
|
8
|
+
| `<name>.page.html` | The page's HTML |
|
|
9
|
+
| `<name>.queries.ts` | The data the page displays |
|
|
10
|
+
| `<name>.requests.ts` | What the page does when asked |
|
|
11
11
|
|
|
12
12
|
Name the landing page `index.page.html`. A bare `/` resolves to `index`, and
|
|
13
13
|
every other path names the page of the same name.
|
|
@@ -25,12 +25,12 @@ is supplied by [chrome.html](./chrome.md).
|
|
|
25
25
|
<h1>About</h1>
|
|
26
26
|
<p>This is the about page.</p>
|
|
27
27
|
|
|
28
|
-
<div lb-
|
|
28
|
+
<div lb-row="visits">
|
|
29
29
|
<p>This page has been visited <span lb-cell="count"></span> times.</p>
|
|
30
30
|
</div>
|
|
31
31
|
```
|
|
32
32
|
|
|
33
|
-
Bind elements to data with `lb-
|
|
33
|
+
Bind elements to data with `lb-list` or `lb-row`, `lb-cell`, and the rest of the
|
|
34
34
|
attribute vocabulary in [Data Binding](./data-binding.md).
|
|
35
35
|
|
|
36
36
|
A page that displays no data needs no other file.
|
|
@@ -38,56 +38,64 @@ A page that displays no data needs no other file.
|
|
|
38
38
|
## Queries
|
|
39
39
|
|
|
40
40
|
Export `queries` from `<name>.queries.ts`. Each key is a name the HTML binds
|
|
41
|
-
to with `lb-
|
|
41
|
+
to with `lb-list` or `lb-row`, and each value takes the request context and returns
|
|
42
42
|
that query's result:
|
|
43
43
|
|
|
44
44
|
```ts
|
|
45
45
|
// src/pages/about.queries.ts
|
|
46
|
-
import { type Queries } from "@loadbare/app/server";
|
|
46
|
+
import { row, type Queries } from "@loadbare/app/server";
|
|
47
47
|
|
|
48
48
|
export const queries: Queries = {
|
|
49
|
-
visits: async (ctx) => ({ count: String(await ctx.db.visitCount()) }),
|
|
49
|
+
visits: row(async (ctx) => ({ count: String(await ctx.db.visitCount()) })),
|
|
50
50
|
};
|
|
51
51
|
```
|
|
52
52
|
|
|
53
|
-
|
|
54
|
-
|
|
53
|
+
Declare each query with `row()` or `list()`. Cardinality is a property of the
|
|
54
|
+
name rather than of any one answer, so one name answers with one shape,
|
|
55
|
+
always, and `createHub` refuses an answer that disagrees. A page that needs
|
|
56
|
+
the same data as one row and as a set declares two queries.
|
|
55
57
|
|
|
56
|
-
|
|
58
|
+
The hub hands each cell to the browser untouched and takes no position on
|
|
59
|
+
its type, so what a number, a date or a null looks like is decided here, in
|
|
60
|
+
the query. Formatting it here means the browser displays a value it never
|
|
61
|
+
computes.
|
|
62
|
+
|
|
63
|
+
Declare a query that answers with many rows using `list()`, and return the
|
|
64
|
+
array itself:
|
|
57
65
|
|
|
58
66
|
```ts
|
|
59
67
|
// src/pages/directory.queries.ts
|
|
60
|
-
import {
|
|
68
|
+
import { list, type Queries } from "@loadbare/app/server";
|
|
61
69
|
|
|
62
70
|
export const queries: Queries = {
|
|
63
|
-
directory:
|
|
71
|
+
directory: list((ctx) => ctx.db.directory()),
|
|
64
72
|
};
|
|
65
73
|
```
|
|
66
74
|
|
|
67
|
-
Give every row a
|
|
68
|
-
the HTML. Return the rows in the order the page shows them.
|
|
75
|
+
Give every row a column that identifies it, and name that column with
|
|
76
|
+
`lb-key` in the HTML. Return the rows in the order the page shows them.
|
|
69
77
|
|
|
70
|
-
Return the full result every time. Sending only what changed is a
|
|
78
|
+
Return the full result every time. Sending only what changed is a request's job —
|
|
71
79
|
see [refresh and patch](#refresh-and-patch).
|
|
72
80
|
|
|
73
|
-
##
|
|
81
|
+
## Requests, actions, CRUD
|
|
74
82
|
|
|
75
|
-
Export `
|
|
83
|
+
Export `requests` from `<name>.requests.ts`. It holds three keys, each optional:
|
|
76
84
|
|
|
77
|
-
| Key
|
|
78
|
-
|
|
79
|
-
| `
|
|
80
|
-
| `actions`
|
|
81
|
-
| `crud`
|
|
85
|
+
| Key | Runs |
|
|
86
|
+
|---------------|------------------------------------------------------|
|
|
87
|
+
| `onPageEnter` | Before the page's queries, on entering the page |
|
|
88
|
+
| `actions` | What the page may be asked to do, by name |
|
|
89
|
+
| `crud` | The four operations a list permits on its rows |
|
|
82
90
|
|
|
83
|
-
###
|
|
91
|
+
### onPageEnter
|
|
84
92
|
|
|
85
93
|
```ts
|
|
86
|
-
// src/pages/about.
|
|
87
|
-
import { type
|
|
94
|
+
// src/pages/about.requests.ts
|
|
95
|
+
import { type Requests } from "@loadbare/app/server";
|
|
88
96
|
|
|
89
|
-
export const
|
|
90
|
-
|
|
97
|
+
export const requests: Requests = {
|
|
98
|
+
onPageEnter: (ctx) => ctx.db.recordVisit(),
|
|
91
99
|
};
|
|
92
100
|
```
|
|
93
101
|
|
|
@@ -99,7 +107,7 @@ Declare an action under the name the HTML gives `lb-action`. Pair what it
|
|
|
99
107
|
does with the queries to re-run once it has:
|
|
100
108
|
|
|
101
109
|
```ts
|
|
102
|
-
export const
|
|
110
|
+
export const requests: Requests = {
|
|
103
111
|
actions: {
|
|
104
112
|
resetVisits: {
|
|
105
113
|
run: (ctx) => ctx.db.resetVisits(),
|
|
@@ -113,29 +121,36 @@ Declare every action the page allows. A name the page does not declare is
|
|
|
113
121
|
refused.
|
|
114
122
|
|
|
115
123
|
Read where the interaction happened from `run`'s second argument, which
|
|
116
|
-
carries `
|
|
117
|
-
the request had them.
|
|
124
|
+
carries `list` or `row`, whichever attribute scoped the element, plus `key`,
|
|
125
|
+
`cell` and `value` when the element that dispatched the request had them.
|
|
118
126
|
|
|
119
127
|
### crud
|
|
120
128
|
|
|
121
|
-
Declare CRUD operations under `crud`, keyed by the
|
|
122
|
-
|
|
129
|
+
Declare CRUD operations under `crud`, keyed by the list they operate on. All
|
|
130
|
+
four are list operations: each needs a key, and a key exists only on a live
|
|
131
|
+
row inside a list, so a single-row scope is read-only and a declared action is
|
|
132
|
+
the only thing it can send. Each operation takes the binding its trigger
|
|
133
|
+
supplies:
|
|
134
|
+
|
|
135
|
+
| Operation | The page writes | `run` receives |
|
|
136
|
+
|---------------|------------------------------------------|------------------------|
|
|
137
|
+
| `cellChange` | `<lb-input lb-action="lb-cell-change">` | `key`, `cell`, `value` |
|
|
138
|
+
| `rowDelete` | `lb-action="lb-row-delete"` | `key` |
|
|
139
|
+
| `rowInsert` | `<form lb-action="lb-row-insert">` | `values` |
|
|
140
|
+
| `rowUpdate` | `<form lb-action="lb-row-update">` | `key`, `values` |
|
|
123
141
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
| `tupleDelete` | `lb-delete` | `key` |
|
|
128
|
-
| `tupleInsert` | `<form lb-insert>` | `values` |
|
|
129
|
-
| `tupleUpdate` | `<form lb-update>` | `key`, `values` |
|
|
142
|
+
The operation names are reserved: a name beginning with `lb-` cannot be
|
|
143
|
+
declared under `actions` or as a query, and `createHub` refuses a page that
|
|
144
|
+
tries.
|
|
130
145
|
|
|
131
146
|
```ts
|
|
132
|
-
// src/pages/directory.
|
|
133
|
-
import { patch, type
|
|
147
|
+
// src/pages/directory.requests.ts
|
|
148
|
+
import { patch, type Requests } from "@loadbare/app/server";
|
|
134
149
|
|
|
135
|
-
export const
|
|
150
|
+
export const requests: Requests = {
|
|
136
151
|
crud: {
|
|
137
152
|
directory: {
|
|
138
|
-
|
|
153
|
+
rowInsert: {
|
|
139
154
|
run: async (ctx, { values }) => {
|
|
140
155
|
const entry = await ctx.db.addDirectoryEntry(values);
|
|
141
156
|
return { directory: patch({ rows: [entry] }) };
|
|
@@ -147,8 +162,8 @@ export const hooks: Hooks = {
|
|
|
147
162
|
};
|
|
148
163
|
```
|
|
149
164
|
|
|
150
|
-
Declare every operation the
|
|
151
|
-
declare is refused, and a
|
|
165
|
+
Declare every operation the list permits. An operation a list does not
|
|
166
|
+
declare is refused, and a name with no `crud` entry permits none.
|
|
152
167
|
|
|
153
168
|
### refresh and patch
|
|
154
169
|
|
|
@@ -159,7 +174,7 @@ would. What `run` returns is laid over the refreshed queries:
|
|
|
159
174
|
|
|
160
175
|
| Result | States |
|
|
161
176
|
|--------------------------|---------------------------------------------|
|
|
162
|
-
| `
|
|
177
|
+
| `[...]` | The entire set, and its order |
|
|
163
178
|
| `patch({ rows: [...] })` | These rows arrive or change; the rest stand |
|
|
164
179
|
| `patch({ drop: [...] })` | These keys are gone; the rest stand |
|
|
165
180
|
|