@loadbare/app 0.9.0 → 0.11.0

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