@loadbare/app 0.8.2 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. package/README.md +3 -3
  2. package/dist/build/assemble.d.ts.map +1 -1
  3. package/dist/build/assemble.js +74 -66
  4. package/dist/build/assemble.js.map +1 -1
  5. package/dist/build/expand.d.ts.map +1 -1
  6. package/dist/build/expand.js +20 -19
  7. package/dist/build/expand.js.map +1 -1
  8. package/dist/build/locations.d.ts +2 -3
  9. package/dist/build/locations.d.ts.map +1 -1
  10. package/dist/build/locations.js +2 -3
  11. package/dist/build/locations.js.map +1 -1
  12. package/dist/build/pages.d.ts +3 -4
  13. package/dist/build/pages.d.ts.map +1 -1
  14. package/dist/build/pages.js +3 -4
  15. package/dist/build/pages.js.map +1 -1
  16. package/dist/core/lb-constants.d.ts +25 -23
  17. package/dist/core/lb-constants.d.ts.map +1 -1
  18. package/dist/core/lb-constants.js +95 -158
  19. package/dist/core/lb-constants.js.map +1 -1
  20. package/dist/core/lb-types.d.ts +73 -75
  21. package/dist/core/lb-types.d.ts.map +1 -1
  22. package/dist/core/lb-types.js +58 -5
  23. package/dist/core/lb-types.js.map +1 -1
  24. package/dist/hub/lb-apply.d.ts +47 -37
  25. package/dist/hub/lb-apply.d.ts.map +1 -1
  26. package/dist/hub/lb-apply.js +174 -193
  27. package/dist/hub/lb-apply.js.map +1 -1
  28. package/dist/hub/lb-hub.browser.d.ts +1 -1
  29. package/dist/hub/lb-hub.browser.d.ts.map +1 -1
  30. package/dist/hub/lb-hub.browser.js +419 -415
  31. package/dist/hub/lb-hub.browser.js.map +1 -1
  32. package/dist/server/lb-express.d.ts +20 -13
  33. package/dist/server/lb-express.d.ts.map +1 -1
  34. package/dist/server/lb-express.js +50 -52
  35. package/dist/server/lb-express.js.map +1 -1
  36. package/dist/server/lb-server.d.ts +81 -116
  37. package/dist/server/lb-server.d.ts.map +1 -1
  38. package/dist/server/lb-server.js +151 -48
  39. package/dist/server/lb-server.js.map +1 -1
  40. package/docs/TECHREF-1.0.md +893 -558
  41. package/docs/comparison.md +222 -185
  42. package/docs/prior-art.md +15 -14
  43. package/docs/reference/builder.md +9 -3
  44. package/docs/reference/chrome.md +107 -56
  45. package/docs/reference/custom-elements.md +199 -173
  46. package/docs/reference/data-binding.md +375 -370
  47. package/docs/reference/overview.md +12 -10
  48. package/docs/reference/page-files.md +161 -86
  49. package/docs/reference/server.md +13 -7
  50. package/docs/reference/widgets.md +104 -110
  51. package/docs/roadmap.md +36 -31
  52. package/docs/terms-of-art.md +57 -0
  53. package/docs/testing.md +97 -68
  54. package/docs/theory.md +92 -58
  55. package/docs/tutorials/010-pages-and-navigation.md +20 -12
  56. package/docs/tutorials/020-css.md +6 -3
  57. package/docs/tutorials/030-html-decomposition.md +9 -7
  58. package/docs/tutorials/040-displaying-data.md +30 -13
  59. package/docs/tutorials/{050-actions.md → 050-requests.md} +25 -15
  60. package/docs/tutorials/060-custom-element-code.md +17 -16
  61. package/docs/tutorials/065-conditional-rendering.md +34 -23
  62. package/docs/tutorials/070-displaying-a-list.md +29 -21
  63. package/docs/tutorials/072-inserting-into-a-list.md +24 -16
  64. package/docs/tutorials/074-deleting-from-a-list.md +9 -7
  65. package/docs/tutorials/076-updating-a-list-item.md +11 -10
  66. package/docs/tutorials/080-widget-requests.md +71 -43
  67. package/docs/tutorials/090-using-widget-libraries.md +22 -22
  68. package/package.json +1 -1
  69. package/skills/loadbare-app/SKILL.md +178 -111
  70. package/skills/loadbare-app/references/TECHREF-1.0.md +893 -558
  71. package/skills/loadbare-app/references/builder.md +9 -3
  72. package/skills/loadbare-app/references/chrome.md +107 -56
  73. package/skills/loadbare-app/references/custom-elements.md +199 -173
  74. package/skills/loadbare-app/references/data-binding.md +375 -370
  75. package/skills/loadbare-app/references/overview.md +12 -10
  76. package/skills/loadbare-app/references/page-files.md +161 -86
  77. package/skills/loadbare-app/references/server.md +13 -7
  78. package/skills/loadbare-app/references/widgets.md +104 -110
  79. package/docs/analysis-accidental-complexity.md +0 -149
  80. package/docs/analysis-closed-set.md +0 -210
@@ -1,485 +1,490 @@
1
1
  # Data Binding
2
2
 
3
- A page binds its elements to server data with four attributes, and asks the
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
- it permits, and refuses anything it has not declared.
3
+ A page shows server data by naming queries and columns in its HTML, and asks
4
+ the server to change that data by naming requests. The developer writes
5
+ seven `lb-` attributes. The server declares every query a page may show and
6
+ every request it may send, 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.
8
+ Everything is a query. A query is a name for rows, of kind `row` or `rows`,
9
+ with a key column. The server declares all three, so the markup names the
10
+ query and the columns it shows, and repeats neither kind nor key.
11
11
 
12
12
  ## A page that binds data
13
13
 
14
- Here is a page that binds a scalar, a list, and three requests:
14
+ Here is a page that shows one row, lists rows, and sends three requests:
15
15
 
16
16
  ```html
17
17
  <!-- src/pages/members.page.html -->
18
+ <title>Members</title>
18
19
  <h1>Members</h1>
19
20
 
20
- <p lb-row="dues">Dues collected this year: <span lb-cell="total"></span></p>
21
+ <p lb-query="dues">Dues collected this year: <span lb-column="total"></span></p>
21
22
 
22
- <section lb-list="roster">
23
- <form lb-action="lb-row-insert">
24
- <input lb-cell="name" placeholder="Name" />
23
+ <section lb-query="roster">
24
+ <form lb-request="lb-row-insert">
25
+ <input lb-column="name" placeholder="Name" />
25
26
  <button type="submit">Add member</button>
26
27
  </form>
27
28
 
28
29
  <ul>
29
- <template lb-key="id">
30
+ <template>
30
31
  <li>
31
- <lb-input lb-cell="name"></lb-input>
32
- <button lb-action="lb-row-delete">Remove</button>
32
+ <input lb-column="name" lb-request="lb-row-update" />
33
+ <button lb-request="lb-row-delete">Remove</button>
33
34
  </li>
34
35
  </template>
35
36
  </ul>
36
37
  </section>
37
38
  ```
38
39
 
39
- The queries named here — `dues` and `roster` — and the operations the page
40
- asks for are declared on the server; see [page files](./page-files.md).
41
-
42
- ## Binding
43
-
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-show` | a developer | The column that decides whether an element is present |
51
- | `lb-key-value` | the hub | A live row's own key |
52
- | `lb-value` | the hub | The value that landed on a cell |
53
-
54
- A binding is scoped by ancestry. Either scope attribute scopes its DOM
55
- children, and a nested one of either kind begins a new scope, so an element
56
- binds to the name on its nearest ancestor carrying one, and to the row on its
57
- nearest ancestor carrying `lb-key-value`. Nothing else establishes scope: an
58
- element outside every scope is bound to nothing and displays nothing, and a
59
- result never crosses into a nested scope.
60
-
61
- Which of the two a subtree writes is not a choice about display. Cardinality
62
- is a property of the name, so one name answers with one shape, always. A page
63
- that shows the roster both as a set and as a single row declares two queries,
64
- `rosterList` and `rosterRow`, and binds each with the attribute that matches
65
- what it answers with.
66
-
67
- Bind an element to a cell by putting `lb-cell` on the element that shows the
68
- value. Its value is a column name and never a cell name: a cell has no name
69
- of its own, because it is identified by its row and its column, and the row
70
- arrives from scope. The scope's own root counts as a cell if it carries one,
71
- which is how an `<option>` — whose content model is text — displays the value
72
- it is.
73
-
74
- An element that carries a scope and `lb-cell` both is a cell of the scope
75
- around it. Its own `lb-list` or `lb-row` names what it displays, and its
76
- ancestors name where it belongs, so a `<select lb-list="accounts"
77
- lb-cell="account">` in a row displays the accounts and holds that row's
78
- `account`. The value lands on it and is gathered from it; the cells inside
79
- it are the accounts', and neither.
80
-
81
- Name the same query on more than one subtree to display it in more than one
82
- place. Every subtree gets the result.
83
-
84
- One name is the hub's rather than the server's: `lb-navigation`, one row
85
- landed on every navigation with the columns `page-label` and `page-uri`. It binds the
86
- same way. Its name is reserved, as every value beginning with `lb-` is in
87
- every `lb-` attribute: the server refuses a page that declares a query so
88
- named — see [Where the page is](./chrome.md#where-the-page-is).
89
-
90
- A key has a name and a value, and they are two attributes. `lb-key` on a row
91
- template names the column that identifies a row: it is a property of the
92
- list, since a list without a fixed key column is meaningless, written where
93
- the rows land. `lb-key-value` on a row that is showing carries that row's
94
- value of it. A developer writes the first and never the second.
95
-
96
- ### Where a bound value lands
97
-
98
- A value lands on a bound element one of three ways.
99
-
100
- | Element | Receives the value as |
101
- |----------------------------------------|-----------------------------------|
102
- | A custom element | Its `lb-value` attribute |
103
- | `<select>`, `<textarea>`, or `<input>` | Its `value`, and `lb-value` |
104
- | Any other native element | Its `textContent`, and `lb-value` |
105
-
106
- A widget owns whatever control it wraps, so it is handed the value and
107
- renders it itself — see [Custom Elements](./custom-elements.md#code) for
108
- observing `lb-value`. A form control shows its state as its `value`, so a
109
- `<select>` keeps its options. Any other native element has no behavior of its
110
- own, so its value is its text. Checkboxes, radio buttons and file inputs
111
- receive nothing, not even `lb-value`, and the hub reports it to the console.
112
-
113
- Nothing an application writes ever sets `lb-value`. It is written by
114
- Loadbare and read by a widget or a stylesheet.
115
-
116
- ### Lists
117
-
118
- Write `lb-list` on any element, and a `<template>` inside it carrying
119
- `lb-key`. The hub clones that template once per row, fills each clone, and
120
- reconciles what is showing against what arrived. No widget is involved, and
121
- none is needed.
122
-
123
- An array is the whole set, so it decides membership and order: a row whose
124
- key did not arrive is gone. A patch touches only the rows it names and leaves
125
- every other row's contents and position alone.
126
-
127
- A widget enters only where the rows need scaffolding or placement that only
128
- it can decide — a `<select>` that builds an `<optgroup>` per distinct value, a
129
- table that sections and sorts. Such a widget carries `lb-list` itself and
130
- implements one or both of two optional methods; see
131
- [Decorating a list](./custom-elements.md#decorating-a-list) and
132
- [The Basic Widget Library](./widgets.md).
133
-
134
- A scope with `lb-list` and no row template displays nothing, and that is not
135
- an error. It is bound to the list without showing it, which is what an insert
136
- form naming the list it adds a row to already is.
137
-
138
- ## Requests
139
-
140
- One attribute turns an interaction into a request. `lb-action` names what the
141
- server is asked for: an action the page declared, or one of Loadbare's
142
- reserved names, which are the CRUD operations.
143
-
144
- One attribute name, one wire field, one set of values. The request field
145
- `action` carries this attribute's value verbatim, so nothing is translated
146
- between the markup and the server, and the CRUD key is the same value with
147
- the prefix stripped and the rest camel-cased.
148
-
149
- | Written | Asks for | Carries |
150
- |-------------------------------------------|--------------|-------------------------------|
151
- | `lb-action="name"` | That action | The scope, and what is in it |
152
- | `lb-action="lb-row-delete"` | `rowDelete` | `list`, `key` |
153
- | `lb-action="lb-row-insert"` on a `<form>` | `rowInsert` | `list`, `values` |
154
- | `lb-action="lb-row-update"` on a `<form>` | `rowUpdate` | `list`, `key`, `values` |
155
- | `lb-action="lb-row-update"` on a widget cell | `rowUpdate` | `list`, `key`, `values` of one cell |
156
-
157
- All three operations are list operations. Each needs a key, and a key exists
158
- only on a live row the hub stamped inside a list, so a single-row scope is
159
- read-only and a declared action is the only thing it can send. An application
160
- that wants a writable single row declares a list that answers with one row.
161
-
162
- A name beginning with `lb-` is reserved, in `lb-action` and in either scope
163
- attribute alike. The server refuses a page that declares an action or a query
164
- so named, which is what lets a reserved name be added later without colliding
165
- with one an application already uses. That reservation is also the whole of
166
- the wire discriminant: a value beginning with `lb-` is an operation, and
167
- anything else is a name the page declared.
168
-
169
- The hub sends a request from a native element on the element's own event: a
170
- form on submit, anything else on click. An insert or update clicked from a
171
- button reads the form or row the button is in (see [Forms](#forms)). To
172
- commit one cell as it changes, put `lb-row-update` on a widget cell instead
173
- (see [Committing one cell](#committing-one-cell)).
174
-
175
- Declare every action on the server, and permit every operation on its list. A
176
- name the page has not declared, and an operation a list does not permit, are
177
- refused; see
178
- [requests, actions, CRUD](./page-files.md#requests-actions-crud).
179
-
180
- ### Actions
181
-
182
- Write `lb-action` on a button to ask the server to do something that is not
183
- one of the three CRUD operations:
40
+ The server declares `dues` as a `row` query and `roster` as a `rows` query,
41
+ each with its key, and permits the three requests on `roster`; see
42
+ [page files](./page-files.md).
43
+
44
+ | Attribute | Goes on | Names |
45
+ |------------------|-------------------------------|------------------------------------------------|
46
+ | `lb-query` | Any element | The query whose rows land in it |
47
+ | `lb-column` | Any element | The column it shows, and the column it gathers |
48
+ | `lb-show` | Any element but `<template>` | The column that decides whether it is present |
49
+ | `lb-request` | Any element | The request it sends when it commits |
50
+ | `lb-url-link` | An `<a>` | A link between pages; see [chrome](./chrome.md#links) |
51
+ | `lb-url-push` | An element with `lb-request` | A URL change that pushes a history entry |
52
+ | `lb-url-unknown` | A `<dialog>` in the chrome | The dialog for a URL that names no page |
53
+
54
+ Every other `lb-` attribute in the document is a stamp. The hub or the
55
+ builder writes a stamp, and a stylesheet or a custom element reads it.
56
+
57
+ | Stamp | Carries |
58
+ |----------------------|----------------------------------------------------|
59
+ | `lb-column-value` | The value the hub set from a column |
60
+ | `lb-key-value` | The key of a live row, or of a `row` landed on an element |
61
+ | `lb-query-row-count` | The number of live rows an element holds |
62
+ | `lb-request-pending` | The element's request is in flight |
63
+ | `lb-request-error` | The element's last request failed |
64
+ | `lb-page` | The stub of a page, on its `<template>` |
65
+ | `lb-page-title` | The text of a page file's `<title>` |
66
+
67
+ The application should never name anything with the `lb-` prefix anywhere:
68
+ no attribute, query, column, request, custom element or event.
69
+
70
+ ## Landing
71
+
72
+ Write `lb-query` on an element to show a query there. Every response from
73
+ the server carries response items, one per query, and the hub lands each on
74
+ every element whose `lb-query` names its query. Name a query on as many
75
+ elements as the page needs.
76
+
77
+ What lands depends on the query's kind and on whether the element holds a
78
+ row template, which is the first `<template>` among its descendants outside
79
+ any nested `lb-query`:
80
+
81
+ | Kind | Row template | The hub |
82
+ |--------|--------------|----------------------------------------|
83
+ | `row` | no | Lands the row on the element itself |
84
+ | `row` | yes | Lands one live row |
85
+ | `rows` | yes | Lands one live row per row |
86
+ | `rows` | no | Lands nothing |
87
+
88
+ A live row is an element the hub cloned from a row template for one row.
89
+ The hub stamps it with `lb-key-value`, the row's key. An element a `row`
90
+ lands on itself carries `lb-key-value` as well.
91
+
92
+ Write `lb-column` on each element that shows a column. The element reads
93
+ from its nearest ancestor row: the live row it is in, or the element a
94
+ `row` landed on. A nested `lb-query` begins a new query, and what is inside
95
+ it reads from that query's rows.
96
+
97
+ An element with `lb-query` and `lb-column` both shows the rows of its own
98
+ query and holds the column of the row around it. This
99
+ [`<lb-options>`](./widgets.md#lb-options) lists the accounts and holds its
100
+ row's `account`:
184
101
 
185
102
  ```html
186
- <button lb-action="mailRoster">Mail the roster</button>
103
+ <lb-options lb-query="accounts" lb-column="account">
104
+ <template><option lb-column="name"></option></template>
105
+ </lb-options>
187
106
  ```
188
107
 
189
- An action carries whatever binding is in scope at the element — `list` or
190
- `row`, whichever scoped it, plus `key` and `cell` — and nothing else. There is
191
- no argument list. A button carries no value, so the server computes the whole
192
- of the new state and the page displays only what came back.
108
+ `lb-column` on an element with `lb-query` is undefined unless the element is
109
+ a control.
193
110
 
194
- Write `lb-action` on a widget to have the widget decide what performing the
195
- action means. Loadbare turns a click into a request for a native element
196
- only, and leaves a widget to send its own — a `<select>` performs its action
197
- on change, not on click.
111
+ An element with `lb-column` and no ancestor row receives nothing. The hub
112
+ still gathers from it, so an insert form writes `lb-column` on controls that
113
+ no row fills.
198
114
 
199
- ### Deleting a row
115
+ ### Where a column lands
200
116
 
201
- ```html
202
- <button lb-action="lb-row-delete">Remove</button>
203
- ```
117
+ | Element | Receives the value as |
118
+ |------------------------------------------------------|-----------------------------------------|
119
+ | `<input>`, `<select>`, `<textarea>` | Its `value`, and `lb-column-value` |
120
+ | A form-associated custom element with `value` | Its `value`, and `lb-column-value` |
121
+ | Any other custom element | `lb-column-value` only |
122
+ | Any other element | Its text content, and `lb-column-value` |
204
123
 
205
- `lb-row-delete` needs nothing declared. The row's `lb-list` and `lb-key-value`
206
- are already in scope, and they are all the server needs to know which row is
207
- meant and whether the list permits deleting it.
124
+ A control is an `<input>`, `<select>` or `<textarea>`, or a form-associated
125
+ custom element with a `value` property that fires `change`. The hub sets a
126
+ control's `value`, and gathers it back under the same column. A custom
127
+ element that is not a control keeps the content the builder placed in it
128
+ from its element file, and renders `lb-column-value` itself; see
129
+ [Custom Elements](./custom-elements.md#receiving-a-value).
208
130
 
209
- The hub sends it from a native element. A widget sends its own request.
131
+ A checkbox, a radio button and a file input receive nothing, and the hub
132
+ reports it to the console.
210
133
 
211
- ### Forms
134
+ The hub hands every value to the browser as the server sent it. What a
135
+ number, a date or a null looks like is decided in the query.
212
136
 
213
- A `<form>` performs its `lb-action` on submit. `lb-row-insert` and `lb-row-update`
214
- both gather every `lb-cell` of the form into one values map, read from
215
- the control each cell is or wraps. A cell inside a scope nested in the form
216
- is that scope's and is not gathered, the same way a value landing on the
217
- form's row does not reach it. They differ in one thing: `lb-row-update`
218
- also carries the key of the row it is inside, and `lb-row-insert` carries none,
219
- because there is no row yet. A declared name on a form sends that action on
220
- submit, carrying the binding and no values.
137
+ ### Rows
221
138
 
222
- Put an `lb-row-update` form inside the row it edits, so it has that row's key
223
- from the same ancestor a delete button reads:
139
+ Write a `<template>` inside the element carrying `lb-query`, holding one
140
+ element: the row. The hub clones it once per row, fills each clone, and
141
+ places it immediately before the template:
224
142
 
225
143
  ```html
226
- <template lb-key="id">
227
- <li>
228
- <form lb-action="lb-row-update">
229
- <input lb-cell="name" />
230
- <button type="submit">Save</button>
231
- </form>
232
- </li>
233
- </template>
144
+ <ul lb-query="roster">
145
+ <template>
146
+ <li lb-column="name"></li>
147
+ </template>
148
+ </ul>
234
149
  ```
235
150
 
236
- When an `lb-row-insert` succeeds, the hub resets every control it gathered
237
- from to its default, as `form.reset()` would, so the form is ready for the
238
- next entry. A failed insert leaves the entry for the user to correct, and a
239
- control the user changed while the request was in flight keeps the change.
240
- An `lb-row-update` resets nothing: the row it sent lands back on its cells.
151
+ The hub matches each row to a live row by its key. All rows decide
152
+ membership and order: a live row whose key did not arrive is removed. A
153
+ patch changes only the rows it names, and every other live row keeps its
154
+ content and its place.
155
+
156
+ A live row's root counts as a column when it carries `lb-column`, which is
157
+ how an `<option>`, whose content is text, shows the column it is.
158
+
159
+ A custom element that carries `lb-query` and a row template may decide where
160
+ a row goes and add scaffolding around the rows; see
161
+ [Holding rows](./custom-elements.md#holding-rows) and
162
+ [The Basic Widget Library](./widgets.md).
241
163
 
242
- Give every `lb-cell` in a form a control to read. A cell that is neither an
243
- `<input>`, `<select>`, or `<textarea>` nor wraps one is left out of the
244
- values map, and a form with no cell to read sends nothing.
164
+ A `rows` query on an element with no row template lands nothing. That is the
165
+ insert form above: it sits inside `lb-query="roster"` so its request is for
166
+ `roster`, and it shows none of its rows.
245
167
 
246
- An insert or update gathers the row it belongs to: the nearest `<form>`,
247
- `<tr>` or live row around the element that sends it, inside its scope. A
248
- button submits its form the same way, whatever else sits beside it.
168
+ ### Counting rows
249
169
 
250
- A form cannot go around a table row's controls, so there the row is the
251
- form. Put the action on a button in the row, and it inserts every cell in
252
- the row, wherever in the row the button and the cells are:
170
+ The hub stamps every element that holds a row template with
171
+ `lb-query-row-count`, the number of live rows it holds. An empty query is a
172
+ stylesheet rule:
253
173
 
254
174
  ```html
255
- <table lb-list="roster">
256
- <tbody>
257
- <tr>
258
- <td><input lb-cell="name" /></td>
259
- <td>
260
- <input lb-cell="role" />
261
- <button lb-action="lb-row-insert">Add</button>
262
- </td>
263
- </tr>
264
- </tbody>
265
- </table>
175
+ <div lb-query="roster">
176
+ <ul>
177
+ <template>
178
+ <li lb-column="name"></li>
179
+ </template>
180
+ </ul>
181
+ <p class="roster-empty">No members yet.</p>
182
+ </div>
266
183
  ```
267
184
 
268
- An `lb-row-update` button in a live row saves that row and no other, and a
269
- form nested in a row gathers only the form. Anywhere else, write the form:
270
- cells and a button in a `<div>` belong to no row, so the hub refuses the
271
- request and says to put them in a `<form>`.
185
+ ```css
186
+ .roster-empty {
187
+ display: none;
188
+ }
189
+ [lb-query-row-count="0"] .roster-empty {
190
+ display: revert;
191
+ }
192
+ ```
272
193
 
273
- Put these two actions on a form or a button, not on an element that holds
274
- the cells. A click into one of its inputs would send the row, so the hub
275
- refuses it. A widget may dispatch either from any element, and the hub
276
- gathers the same way; see
277
- [Sending a request](./custom-elements.md#sending-a-request).
194
+ The element carrying `lb-query` is a `<div>` here rather than the `<ul>`, so
195
+ that the message is inside it and one rule reaches both.
278
196
 
279
- ### Committing one cell
197
+ ## Conditional rendering
280
198
 
281
- Put `lb-row-update` on a widget that carries `lb-cell` to save that one cell
282
- whenever it changes. The shipped `<lb-input>` sends it on `change`:
199
+ A page holds every element it can show. Write `lb-show` on an element,
200
+ naming the column that decides whether it is present:
283
201
 
284
202
  ```html
285
- <template lb-key="id">
203
+ <template>
286
204
  <tr>
287
- <td><lb-input lb-cell="name" lb-action="lb-row-update"></lb-input></td>
288
- <td><lb-input lb-cell="note" lb-action="lb-row-update"></lb-input></td>
205
+ <td lb-column="name"></td>
206
+ <td><button lb-request="lb-row-delete" lb-show="removable">Remove</button></td>
289
207
  </tr>
290
208
  </template>
291
209
  ```
292
210
 
293
- An element carrying `lb-cell` is a record of one cell, the way a control has
294
- a value and a form has values. Its update carries `values` holding that cell
295
- alone, so an edit in one input never sends the other. It reaches the same
296
- `rowUpdate` a form does. SQL has one UPDATE whether it sets one column or
297
- many, and the application writes one handler for both.
211
+ ```sql
212
+ (ledger_count = 0 AND system_behavior IS NULL) AS removable
213
+ ```
214
+
215
+ A value of `null` or `false` takes the element out of the page, and any other
216
+ value puts it back. The hub never reads a string, so `"false"` is present:
217
+ have the query answer with a boolean or a null. A row that does not carry
218
+ the column leaves the element as it is, so a query answers with the column
219
+ in every row.
298
220
 
299
- The widget decides when the cell has changed. A native `<input>` carrying
300
- `lb-cell` and `lb-row-update` has no such moment, since a click into it
301
- would send it, so the hub refuses it and says so.
221
+ `lb-show` reads from the nearest ancestor row, as `lb-column` does. On an
222
+ element that also carries `lb-query`, the column belongs to the row around
223
+ it, so this picker takes its choices from `groups` and whether it is present
224
+ from the account row:
302
225
 
303
- Leave `lb-action` off a widget inside an `lb-row-insert` or `lb-row-update`
304
- form. The form reads every `lb-cell` in it on submit, so a widget that also
305
- sent its own would write the same edit twice.
226
+ ```html
227
+ <lb-options lb-query="groups" lb-column="group_id" lb-show="group_choice">
228
+ <template><option lb-column="name"></option></template>
229
+ </lb-options>
230
+ ```
231
+
232
+ An element may show a column and be decided by it, which shows a note only
233
+ when there is one:
306
234
 
307
- ## Query parms
235
+ ```html
236
+ <span lb-column="note" lb-show="note"></span>
237
+ ```
308
238
 
309
- A control that narrows what the page shows writes its value into the query
310
- string instead of sending a request. `lb-query-parm` names the parm:
239
+ A condition that is only a style is a class on an element that is present or
240
+ not:
311
241
 
312
242
  ```html
313
- <select lb-query-parm="team">
314
- <option value="">Every team</option>
315
- <option value="Engines">Engines</option>
316
- </select>
243
+ <span lb-show="out_of_balance" class="danger">Out of balance</span>
317
244
  ```
318
245
 
319
- On `change` the hub sets that one parm in the URL, leaving every other parm
320
- alone, and takes it out when the value is empty. The write replaces the
321
- current history entry; add `lb-query-parm-push` to push one instead. The page
322
- then loads at the new URL, exactly as a cold load of it would, without
323
- replacing its DOM.
246
+ A condition that is not data, such as a collapsed section or an open menu,
247
+ has no column. Use `<details>`, a stylesheet, or a custom element. Every
248
+ element set from a column carries the value as `lb-column-value`, for a
249
+ stylesheet to select on.
324
250
 
325
- After every load the hub lands each parm on the control that writes it, and
326
- an absent parm lands empty, so the control shows what the address bar says.
251
+ An element that is not present cannot be clicked. The server still refuses
252
+ what a request may not do.
327
253
 
328
- A control that writes a query parm sends no request, and one that also
329
- carries `lb-action` has that request refused.
254
+ ### Where an absent element is
330
255
 
331
- The server reads the parms off `req.query` in `contextFor`; see
332
- [the Express server](./server.md#database-layer). See
333
- [Query parms](../TECHREF-1.0.md#query-parms) for the whole rule.
256
+ The hub moves an element whose column is off into a `<template lb-show>`
257
+ that stands where it stood, and moves it back out when the column turns on.
258
+ The builder ships every `lb-show` element already inside its template, so
259
+ nothing conditional shows until its row has landed. The developer never
260
+ writes that template.
334
261
 
335
- ## Conditional rendering
262
+ - Nothing renders it, whatever a stylesheet says.
263
+ - It cannot be focused or clicked, assistive technology does not announce it,
264
+ and the hub does not gather from it.
265
+ - It is moved rather than rebuilt, so a custom element keeps its instance and
266
+ a control keeps what was typed into it.
267
+ - Rows keep landing on it and on everything inside it while it is away, so it
268
+ returns current.
336
269
 
337
- Loadbare ships static HTML and hydrates elements that are already in the
338
- document. There is no `if`, and none is needed: write every possibility into
339
- the page, and let a column decide which of them is present.
270
+ An absent element keeps its place among its siblings, so a position selector
271
+ (`:first-child`, `:nth-child`, `:empty`, `+`, `~`) counts its template as a
272
+ sibling. A selector by tag, class or attribute is unaffected.
340
273
 
341
- Write `lb-show` on an element, naming the column that decides it:
274
+ ## Requests
275
+
276
+ Write `lb-request` on an element to send a request when the element commits.
277
+ Its value is the request name: one of the three requests Loadbare provides,
278
+ or a name the page declares.
342
279
 
343
280
  ```html
344
- <template lb-key="id">
345
- <tr>
346
- <td lb-cell="name"></td>
347
- <td><button lb-action="lb-row-delete" lb-show="removable">Remove</button></td>
348
- </tr>
349
- </template>
281
+ <button lb-request="mailRoster">Mail the roster</button>
350
282
  ```
351
283
 
352
- ```sql
353
- (ledger_count = 0 AND system_behavior IS NULL) AS removable
284
+ Every request has one shape:
285
+
286
+ ```json
287
+ { "name": "lb-row-update", "query": "roster", "key": "17", "values": { "name": "Ada" } }
354
288
  ```
355
289
 
356
- A value of `null` or `false` takes the element out of the page, and any other
357
- value puts it back. The hub never reads a string, so `"false"` is a value like
358
- any other: have the query answer with a boolean or a null. A row that does not
359
- carry the column leaves the element as it is, so a query that answers with
360
- whole rows returns the column in every row.
290
+ The hub takes `query`, `key` and `values` from where the element sits, and
291
+ sends each when it has one. The server runs the handler for the name and
292
+ answers with response items, which land like any others.
293
+
294
+ ### Committing
295
+
296
+ | Element | Commits on |
297
+ |------------------|--------------------------------------------|
298
+ | A `<form>` | `submit` |
299
+ | A control | `change` |
300
+ | Any other element | `click` |
361
301
 
362
- `lb-show` binds the way `lb-cell` does, to the row on its nearest scoped
363
- ancestor. On an element that is itself a scope, the column belongs to the
364
- row around it, so this picker takes its choices from `groups` and whether it
365
- is present from the account row:
302
+ A click inside an element carrying `lb-request` commits it unless the click
303
+ lands on interactive content between them: a link, a button, a control or a
304
+ label, as HTML defines interactive content. A click into an input inside a
305
+ deletable row focuses the input.
306
+
307
+ A submit button with a form owner commits with its form. On `submit`, the
308
+ hub sends the submitter's `lb-request` when the submitter carries one, and
309
+ the form's otherwise, the way `formaction` overrides `action`:
366
310
 
367
311
  ```html
368
- <select lb-list="groups" lb-cell="group_id" lb-show="group_choice">
312
+ <template>
313
+ <li>
314
+ <form lb-request="lb-row-update">
315
+ <input lb-column="name" />
316
+ <button type="submit">Save</button>
317
+ <button type="submit" lb-request="lb-row-delete">Remove</button>
318
+ </form>
319
+ </li>
320
+ </template>
369
321
  ```
370
322
 
371
- An element may show a column and be decided by it, which shows a note only
372
- when there is one:
323
+ A control carrying `lb-request` sends on every `change`, which for an
324
+ `<input>` is when the user leaves it with a new value.
325
+
326
+ ### Gathering
327
+
328
+ The hub gathers each control's value under the column its `lb-column` names.
329
+ It gathers from one group, the first of these that applies to the element
330
+ carrying `lb-request`:
331
+
332
+ 1. The element carries `lb-column`: the element alone.
333
+ 2. The element is a `<form>`: every control whose form owner is the form.
334
+ 3. The element is in a live row: every control in that live row.
335
+ 4. Otherwise: every control whose form owner is the element's form owner.
336
+
337
+ The hub gathers from controls only, and skips a control that belongs to a
338
+ query nested inside the group. A form owner includes a control outside the
339
+ form that names it with the HTML `form` attribute, which is how a table row,
340
+ where a form cannot go, sends one:
373
341
 
374
342
  ```html
375
- <span lb-cell="note" lb-show="note"></span>
343
+ <form id="add-member" lb-request="lb-row-insert"></form>
344
+ <table lb-query="roster">
345
+ <tbody>
346
+ <template>
347
+ <tr><td lb-column="name"></td><td lb-column="role"></td></tr>
348
+ </template>
349
+ </tbody>
350
+ <tfoot>
351
+ <tr>
352
+ <td><input lb-column="name" form="add-member" /></td>
353
+ <td>
354
+ <input lb-column="role" form="add-member" />
355
+ <button form="add-member">Add</button>
356
+ </td>
357
+ </tr>
358
+ </tfoot>
359
+ </table>
376
360
  ```
377
361
 
378
- An element that is not present cannot be clicked, but that is presentation:
379
- the server still refuses what a request may not do.
362
+ The hub sends the request for the nearest ancestor `lb-query` of the
363
+ controls it gathered, and takes `key` from their nearest ancestor row when
364
+ that row is of the same query. When it gathers nothing, it uses the nearest
365
+ ancestor `lb-query` and row of the element carrying `lb-request` instead.
366
+ Controls gathered from two different queries are an error, and the hub
367
+ sends nothing.
380
368
 
381
- ### Where an absent element is
369
+ ### Inserting, updating and deleting rows
382
370
 
383
- An element whose column is off is moved into a `<template lb-show>` that
384
- stands where it stood, and moved back out when the column turns on. The
385
- developer never writes that template.
371
+ Loadbare provides three requests. Each runs the handler the page declares
372
+ for its query under `crud`; see [page files](./page-files.md#crud).
386
373
 
387
- - Nothing renders it, whatever a stylesheet says, because a template's content
388
- is not its children.
389
- - It cannot be focused or clicked, assistive technology does not announce it,
390
- and a form does not gather it.
391
- - It is moved, never rebuilt, so a widget keeps its instance and a control
392
- keeps what was typed into it.
393
- - Values keep landing on it, and on every cell and scope inside it, while it
394
- is away, so it returns current.
374
+ | Request name | Needs | Runs |
375
+ |-----------------|--------------------------|-------------|
376
+ | `lb-row-insert` | `query`, `values` | `rowInsert` |
377
+ | `lb-row-update` | `query`, `key`, `values` | `rowUpdate` |
378
+ | `lb-row-delete` | `query`, `key` | `rowDelete` |
395
379
 
396
- The builder ships every `lb-show` element already inside its template, so
397
- nothing conditional shows until its row has landed.
398
-
399
- A condition never changes the structure of a page. An absent element keeps
400
- its place among its siblings, so a position selector (`:first-child`,
401
- `:nth-child`, `:empty`, `+`, `~`) counts its template as a sibling. A selector
402
- by tag, class or attribute is unaffected. Only a list changes a page's
403
- structure.
380
+ The hub sends one of these only when it has what the table lists, and
381
+ otherwise reports to the console. An insert or an update that gathers
382
+ nothing is not sent.
404
383
 
405
- A condition that is only a style is a class on an element that is present or
406
- not:
384
+ A key comes from a live row, or from an element a `row` landed on, so all
385
+ three work against either kind:
407
386
 
408
387
  ```html
409
- <span lb-show="out_of_balance" class="danger">Out of balance</span>
388
+ <form lb-query="profile" lb-request="lb-row-update">
389
+ <input lb-column="email" />
390
+ <button type="submit">Save</button>
391
+ </form>
410
392
  ```
411
393
 
412
- These are build errors:
394
+ An update from a single control sends that control's column alone. An
395
+ update from a form or a live row sends every control it gathers. The
396
+ application writes one `rowUpdate` for both, setting the columns `values`
397
+ names, as an SQL UPDATE does.
413
398
 
414
- - `lb-show` on a row template's root. A row that should not show is left out
415
- by the query.
416
- - `lb-show` with no row around it: outside every scope, on a scope with none
417
- around it, or in a list scope outside its row template, where nothing lands.
418
- - `lb-show` on a `<template>`.
399
+ After a successful insert gathered from a form, the hub resets the form. A
400
+ failed insert leaves the entry for the user to correct.
419
401
 
420
- A condition that is not data, such as a collapsed section or an open menu,
421
- has no column. Use `<details>`, a stylesheet, or a widget. Every cell still
422
- carries the value that landed on it as `lb-value`, for a widget to read or a
423
- stylesheet to select on.
402
+ Leave `lb-request` off a control inside a form or a live row that sends its
403
+ own update, or the same edit is written twice.
424
404
 
425
- ### An empty list
405
+ ### Declared requests
426
406
 
427
- The hub stamps every list scope with `lb-row-count`, the number of rows it is
428
- showing. It is the one conditional a page cannot be sent, because the server
429
- answers with rows and says nothing about how many survived. It makes an empty
430
- list a stylesheet rule rather than code anywhere:
407
+ A request name the page declares under `handlers` runs the application's
408
+ handler; see [page files](./page-files.md#handlers). It gathers the same way
409
+ and carries `query`, `key` and `values` when it finds them:
431
410
 
432
411
  ```html
433
- <div lb-list="roster">
434
- <ul>
435
- <template lb-key="id">
436
- <li lb-cell="name"></li>
437
- </template>
438
- </ul>
439
- <p class="roster-empty">No members yet.</p>
440
- </div>
412
+ <template>
413
+ <li>
414
+ <span lb-column="name"></span>
415
+ <button lb-request="sendReminder">Remind</button>
416
+ </li>
417
+ </template>
441
418
  ```
442
419
 
420
+ A button carries no value, so a declared request from one sends the values
421
+ of the live row or form it sits in, and the server computes the rest.
422
+
423
+ A request name beginning with `lb-` that is not one of the three is refused
424
+ by the builder.
425
+
426
+ ### Request state
427
+
428
+ The hub stamps the element that committed:
429
+
430
+ | Stamp | Means |
431
+ |----------------------|-----------------------------------------------|
432
+ | `lb-request-pending` | The round trip is in flight |
433
+ | `lb-request-error` | The round trip failed, until the next request |
434
+
435
+ `lb-request-error` covers a non-2xx response, a network failure and a
436
+ timeout alike. The hub aborts a round trip after ten seconds, and sets
437
+ `aria-busy="true"` for as long as `lb-request-pending` is present.
438
+
439
+ The hub ignores a commit on an element carrying `lb-request-pending`, so a
440
+ pending button is already disabled and a stylesheet only shows it:
441
+
443
442
  ```css
444
- .roster-empty {
445
- display: none;
443
+ [lb-request-pending] {
444
+ opacity: 0.5;
446
445
  }
447
- [lb-row-count="0"] .roster-empty {
448
- display: revert;
446
+ [lb-request-error] {
447
+ outline: 2px solid red;
449
448
  }
450
449
  ```
451
450
 
452
- The scope is a `<div>` here rather than the `<ul>`, so that the empty message
453
- is inside it and the same rule can reach both.
451
+ ### The request event
454
452
 
455
- ## Request state
453
+ The hub dispatches every request as a bubbling `lb-request` event from the
454
+ element that committed, with the request as its `detail`, before sending it.
455
+ An ancestor may stop the event, and the request is not sent. A custom
456
+ element may dispatch the event itself; see
457
+ [Sending a request](./custom-elements.md#sending-a-request).
456
458
 
457
- Loadbare stamps two attributes on the element a request came from — the
458
- button, the form, or the widget itself:
459
+ ## The URL
459
460
 
460
- | Attribute | Means |
461
- |-------------------|-------------------------------------------|
462
- | `lb-pending` | The request is in flight |
463
- | `lb-error` | The last request from this element failed |
461
+ The hub serves one query of its own, `lb-url`: one row holding the path, the
462
+ page's title, and every query parm as a column. A control inside
463
+ `lb-query="lb-url"` that sends `lb-row-update` sets a query parm, and the hub
464
+ answers it with no round trip:
464
465
 
465
- `lb-pending` is set when the request goes out and removed when it
466
- settles. `lb-error` is set on a failed response, a network failure, or
467
- a timeout alike, and cleared when that element sends its next request.
466
+ ```html
467
+ <div lb-query="lb-url">
468
+ <select lb-column="team" lb-request="lb-row-update">
469
+ <option value="">Every team</option>
470
+ <option value="Engines">Engines</option>
471
+ </select>
472
+ </div>
473
+ ```
468
474
 
469
- Neither one carries any meaning beyond the fact it states. Dim a pending
470
- button in a stylesheet, or have a widget watch its own attributes and
471
- disable itself. An application that styles neither behaves correctly and
472
- shows nothing.
475
+ See [The URL](./chrome.md#the-url) for its columns, links, history, and the
476
+ unknown-page dialog.
473
477
 
474
- A native button or form pressed again while it carries `lb-pending` is
475
- ignored, so a pending one is already disabled and the stylesheet only shows
476
- it. A widget is not held back, since one that sends on change must send its
477
- latest value. The hub also sets `aria-busy="true"` for as long as
478
- `lb-pending` is present.
478
+ ## The markup checks
479
479
 
480
- ## Sending a request from a widget
480
+ The developer writes `lb-` attributes in markup, and script never assigns
481
+ them. The builder checks the markup of the chrome and of every page, after
482
+ expansion, and refuses to build:
481
483
 
482
- A widget can build and dispatch a request itself instead of carrying one of
483
- the attributes above — which is what `<lb-input>` does, and what a widget
484
- carrying `lb-action` must do. See
485
- [Custom Elements](./custom-elements.md#code).
484
+ - an `lb-` attribute that is not one of the seven the developer writes
485
+ - `lb-request` naming an `lb-` request other than the three Loadbare provides
486
+ - `lb-url-link` on anything but an `<a>`
487
+ - `lb-url-push` on an element without `lb-request`
488
+ - `lb-url-unknown` on anything but a `<dialog>`, or outside `<lb-hub>`
489
+ - `lb-show` on a `<template>`, or on a row template's root
490
+ - `lb-show` with no `lb-query` around it