@loadbare/app 0.19.0 → 0.19.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/docs/comparison.md +7 -6
- package/docs/opinions.md +2 -2
- package/docs/reference/builder.md +11 -2
- package/docs/reference/chrome.md +43 -9
- package/docs/reference/css.md +0 -1
- package/docs/reference/custom-elements.md +2 -24
- package/docs/reference/data-binding.md +232 -42
- package/docs/reference/names.md +250 -0
- package/docs/reference/overview.md +6 -0
- package/docs/reference/page-files.md +45 -9
- package/docs/reference/server.md +54 -1
- package/docs/tutorials/040-displaying-data.md +1 -1
- package/package.json +1 -1
- package/skills/loadbare-app/SKILL.md +12 -10
- package/skills/loadbare-app/references/builder.md +11 -2
- package/skills/loadbare-app/references/chrome.md +43 -9
- package/skills/loadbare-app/references/css.md +0 -1
- package/skills/loadbare-app/references/custom-elements.md +2 -24
- package/skills/loadbare-app/references/data-binding.md +232 -42
- package/skills/loadbare-app/references/names.md +250 -0
- package/skills/loadbare-app/references/overview.md +6 -0
- package/skills/loadbare-app/references/page-files.md +45 -9
- package/skills/loadbare-app/references/server.md +54 -1
- package/docs/TECHREF-1.0.md +0 -2267
- package/skills/loadbare-app/references/TECHREF-1.0.md +0 -2267
package/docs/TECHREF-1.0.md
DELETED
|
@@ -1,2267 +0,0 @@
|
|
|
1
|
-
# What 1.0 release means
|
|
2
|
-
|
|
3
|
-
This is the complete technical reference to the upcoming Release 1.0.
|
|
4
|
-
|
|
5
|
-
Method-of-work is to continually revise this document to what we want
|
|
6
|
-
to be true, then revise the code, docs, and tests to ensure it is true.
|
|
7
|
-
|
|
8
|
-
## The build
|
|
9
|
-
|
|
10
|
-
### Running the builder
|
|
11
|
-
|
|
12
|
-
```
|
|
13
|
-
loadbare-app-build [--src src] [--out dist] [--watch] [--minify]
|
|
14
|
-
```
|
|
15
|
-
|
|
16
|
-
`--src` names the tree the builder scans, and is defined under
|
|
17
|
-
[What the builder scans](#what-the-builder-scans).
|
|
18
|
-
|
|
19
|
-
`--out` names the directory the builder writes into, and defaults to `dist`.
|
|
20
|
-
Every output path in this document is written relative to it, so an
|
|
21
|
-
application that passes `--out` reads `dist/app.html` here as
|
|
22
|
-
`<out>/app.html`. See [Build outputs](#build-outputs).
|
|
23
|
-
|
|
24
|
-
`--watch` builds once, then rebuilds on every change to an `.html`, `.ts` or
|
|
25
|
-
`.css` file under `--src`. A build that throws is reported and the watch
|
|
26
|
-
continues. There is no dev server and no browser reload.
|
|
27
|
-
|
|
28
|
-
`--minify` minifies `client.js` and `app.css`. It leaves `app.html` alone,
|
|
29
|
-
which stays markup an application can read.
|
|
30
|
-
|
|
31
|
-
The builder bundles `client.js` from a `client-entry.ts` it generates into
|
|
32
|
-
`--out` and leaves there. Nothing reads it after the bundle, and an
|
|
33
|
-
application neither writes nor imports it.
|
|
34
|
-
|
|
35
|
-
Every `.css` file in every origin joins `app.css`. There is no custom element, page
|
|
36
|
-
or chrome stylesheet: the builder concatenates them all, with nothing added,
|
|
37
|
-
removed or scoped, so what ships is what was authored. Within one origin
|
|
38
|
-
they are ordered by filename, the full path breaking a tie, and a
|
|
39
|
-
subdirectory never affects the order. Origins follow the cascade — this
|
|
40
|
-
package's own custom elements, then each package named in `imports.ts`, then `--src`
|
|
41
|
-
last — so an application's own stylesheet always lands after the ones it
|
|
42
|
-
imported. A file named `00-global.css` sorts first by saying so.
|
|
43
|
-
|
|
44
|
-
### What the builder scans
|
|
45
|
-
|
|
46
|
-
#### The current project
|
|
47
|
-
|
|
48
|
-
The builder scans one directory named by its `--src` parameter,
|
|
49
|
-
which defaults to `src/`.
|
|
50
|
-
|
|
51
|
-
The sections below often state something like, "anywhere in
|
|
52
|
-
the `--src` tree", because Loadbare/app assigns semantic meaning
|
|
53
|
-
to file names, not subdir names.
|
|
54
|
-
|
|
55
|
-
#### Imported widget libraries
|
|
56
|
-
|
|
57
|
-
Any number of widget libraries can be used. They are named
|
|
58
|
-
in the file `imports.ts`, located anywhere in builder `--src`.
|
|
59
|
-
A project can leave out `imports.ts`, or have exactly one, but
|
|
60
|
-
two or more is an error.
|
|
61
|
-
|
|
62
|
-
```ts
|
|
63
|
-
// imports.ts, anywhere in --src
|
|
64
|
-
export default ["@namespace/widget-library"];
|
|
65
|
-
```
|
|
66
|
-
|
|
67
|
-
If the named package's `package.json` declares where the widgets
|
|
68
|
-
are, Loadbare scans that. Otherwise it scans the entire installed
|
|
69
|
-
directory. The declaration, if present, would look like this:
|
|
70
|
-
|
|
71
|
-
```json
|
|
72
|
-
"loadbare": { "widgets": "./dist" }
|
|
73
|
-
```
|
|
74
|
-
|
|
75
|
-
### File names and the builder
|
|
76
|
-
|
|
77
|
-
The builder finds a file by its name. A subdirectory carries no meaning, so
|
|
78
|
-
the whole of `--src` is one flat namespace, as stated in
|
|
79
|
-
[What the builder scans](#what-the-builder-scans).
|
|
80
|
-
|
|
81
|
-
Three kinds of file hold HTML.
|
|
82
|
-
|
|
83
|
-
| File | Holds | Found in |
|
|
84
|
-
| ------------------ | ----------------------------------------- | ------------ |
|
|
85
|
-
| `chrome.html` | The one HTML document | `--src` |
|
|
86
|
-
| `<stub>.page.html` | One page's markup, as a fragment | `--src` |
|
|
87
|
-
| `<tag-name>.html` | One custom element's markup, as a fragment | Every origin |
|
|
88
|
-
|
|
89
|
-
As stated in [Chrome](#chrome), `chrome.html` is a required singleton.
|
|
90
|
-
|
|
91
|
-
As stated in [Pages](#pages), the `<stub>` of a `.page.html` is the path the
|
|
92
|
-
page maps to. The builder puts all pages into `dist/app.html` as HTML `<template>` objects
|
|
93
|
-
that carry `lb-page="<stub>"`, and `lb-page-title` when the page file has a
|
|
94
|
-
`<title>`.
|
|
95
|
-
|
|
96
|
-
An element file is exactly `<kebab-case-name>.html`. A
|
|
97
|
-
[custom element](#custom-elements) is suitable for organizing large HTML
|
|
98
|
-
trees, or adding custom behavior, or both. An element file is named for the
|
|
99
|
-
tag it expands.
|
|
100
|
-
|
|
101
|
-
Any other HTML file, one that is not `chrome.html`, or `<stub>.page.html` or
|
|
102
|
-
`<kebab-case-name>.html` will either:
|
|
103
|
-
- be an error if it looks like an element file with capitalization
|
|
104
|
-
- be skipped
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
## HTML
|
|
108
|
-
|
|
109
|
-
In a Loadbare/app application, HTML is static after the build, and once
|
|
110
|
-
it is sent to the browser on initial page load, no HTML is ever sent
|
|
111
|
-
again.
|
|
112
|
-
|
|
113
|
-
The developer writes `lb-*` attributes in markup. Script never assigns
|
|
114
|
-
them. The hub and the builder write only their stamps, apart from the
|
|
115
|
-
few exceptions listed in [Miswarts](./miswarts.md). See
|
|
116
|
-
[The markup checks](#the-markup-checks).
|
|
117
|
-
|
|
118
|
-
### Expansion
|
|
119
|
-
|
|
120
|
-
The builder executes a process we call "expansion". When it finds
|
|
121
|
-
a custom element in the HTML it is processing, such as `<my-element>`,
|
|
122
|
-
it looks for the element file `my-element.html`, and follows the expansion
|
|
123
|
-
rules to place its contents.
|
|
124
|
-
|
|
125
|
-
A custom element with no element file is left alone. A custom element
|
|
126
|
-
with neither an element file nor a `.browser.ts` script is a build error, as
|
|
127
|
-
stated in [Custom elements](#custom-elements).
|
|
128
|
-
|
|
129
|
-
Each file is parsed on its own, and expansion joins them through the DOM,
|
|
130
|
-
which applies none of HTML's rules for what an element may hold. The browser
|
|
131
|
-
reads the shipped text by those rules, so a join it would rearrange, such as
|
|
132
|
-
a definition's `<dialog>` inside a page's `<p>`, is a build error naming the
|
|
133
|
-
file, the custom element and the path to what would move.
|
|
134
|
-
|
|
135
|
-
#### Build time parameters
|
|
136
|
-
|
|
137
|
-
A build time parameter is supplied as an attribute on a custom element,
|
|
138
|
-
and is converted to a fixed value within the HTML by the builder.
|
|
139
|
-
|
|
140
|
-
The attribute name carries an `exp-` prefix, as in `exp-label`, and
|
|
141
|
-
the substitution locations use `{{label}}` (no prefix) inside the
|
|
142
|
-
element file:
|
|
143
|
-
|
|
144
|
-
```html
|
|
145
|
-
<!-- note-field.html, the element file -->
|
|
146
|
-
<label>{{label}} <input readonly="{{readonly}}" /></label>
|
|
147
|
-
```
|
|
148
|
-
|
|
149
|
-
```html
|
|
150
|
-
<!-- a page -->
|
|
151
|
-
<note-field exp-label="Name"></note-field>
|
|
152
|
-
```
|
|
153
|
-
|
|
154
|
-
```html
|
|
155
|
-
<!-- dist/app.html -->
|
|
156
|
-
<note-field exp-label="Name"
|
|
157
|
-
><label>Name <input /></label
|
|
158
|
-
></note-field>
|
|
159
|
-
```
|
|
160
|
-
|
|
161
|
-
An element file states a default with `{{name|default}}`, as in
|
|
162
|
-
`{{button-label|OK}}`. Whitespace around the name and around the default is
|
|
163
|
-
discarded. The default is a literal.
|
|
164
|
-
|
|
165
|
-
| The attribute is assigned | Expansion defines | Result |
|
|
166
|
-
| ------------------------- | ------------------- | -------------------------------- |
|
|
167
|
-
| `exp-name="text"` | `{{name}}` | `text` |
|
|
168
|
-
| `exp-name=""` | `{{name}}` | An empty string |
|
|
169
|
-
| Nothing | `{{name\|default}}` | `default` |
|
|
170
|
-
| Nothing | `{{name}}` | Nothing, and the attribute drops |
|
|
171
|
-
| `exp-name` | No `{{name}}` | A build error |
|
|
172
|
-
|
|
173
|
-
Attributes without the `exp-` prefix are ignored during expansion.
|
|
174
|
-
|
|
175
|
-
A placeholder is the whole of an attribute value or the whole of a text node.
|
|
176
|
-
`title="Hello {{name}}"` ships literally.
|
|
177
|
-
|
|
178
|
-
A placeholder name carrying a capital is a build error.
|
|
179
|
-
|
|
180
|
-
A parameter value cannot become markup.
|
|
181
|
-
|
|
182
|
-
#### Slots and templates
|
|
183
|
-
|
|
184
|
-
An element file may contain any number of named templates and optionally
|
|
185
|
-
one slot.
|
|
186
|
-
|
|
187
|
-
| Attribute | The builder |
|
|
188
|
-
| ----------------- | ------------------------------------------------------- |
|
|
189
|
-
| `lb-exp-slot` | Puts in it the rest of the custom element's content |
|
|
190
|
-
| `lb-exp-template` | Puts in it the content of the template of the same name |
|
|
191
|
-
| `exp-<name>` | Replaces `{{name}}` in the element file with its value |
|
|
192
|
-
|
|
193
|
-
- `lb-exp-slot` goes on one element in an element file.
|
|
194
|
-
- `lb-exp-template` goes on an element in an element file, and on a
|
|
195
|
-
`<template>` in the custom element's content. Both name the same
|
|
196
|
-
template.
|
|
197
|
-
- `exp-<name>` goes on a custom element.
|
|
198
|
-
|
|
199
|
-
This simplified element file for `lb-table` names two templates, `head` and
|
|
200
|
-
`foot`, and marks `<tbody>` as the slot. A page supplies whichever templates
|
|
201
|
-
it wants, and everything else it writes goes to the slot.
|
|
202
|
-
|
|
203
|
-
```html
|
|
204
|
-
<!-- lb-table.html, the element file -->
|
|
205
|
-
<table>
|
|
206
|
-
<caption>
|
|
207
|
-
{{caption}}
|
|
208
|
-
</caption>
|
|
209
|
-
<thead lb-exp-template="head"></thead>
|
|
210
|
-
<tbody lb-exp-slot></tbody>
|
|
211
|
-
<tfoot lb-exp-template="foot"></tfoot>
|
|
212
|
-
</table>
|
|
213
|
-
```
|
|
214
|
-
|
|
215
|
-
The page below supplies the `head` template and a row template. The row
|
|
216
|
-
template names no destination, so it goes to the slot. This page writes no
|
|
217
|
-
footer.
|
|
218
|
-
|
|
219
|
-
```html
|
|
220
|
-
<!-- a page -->
|
|
221
|
-
<lb-table lb-query="staff" exp-caption="Everyone, by team">
|
|
222
|
-
<template lb-exp-template="head">
|
|
223
|
-
<tr><th>Name</th><th>Role</th></tr>
|
|
224
|
-
</template>
|
|
225
|
-
<template>
|
|
226
|
-
<tr><td lb-column="name"></td><td lb-column="role"></td></tr>
|
|
227
|
-
</template>
|
|
228
|
-
</lb-table>
|
|
229
|
-
```
|
|
230
|
-
|
|
231
|
-
When a named template is expanded, the `<template>` tag is discarded and its
|
|
232
|
-
contents are placed as children of the element that names it. Both
|
|
233
|
-
`lb-exp-slot` and `lb-exp-template` are gone from what ships. The built
|
|
234
|
-
document holds:
|
|
235
|
-
|
|
236
|
-
```html
|
|
237
|
-
<!-- dist/app.html -->
|
|
238
|
-
<lb-table lb-query="staff" exp-caption="Everyone, by team"
|
|
239
|
-
><table>
|
|
240
|
-
<caption>
|
|
241
|
-
Everyone, by team
|
|
242
|
-
</caption>
|
|
243
|
-
<thead>
|
|
244
|
-
<tr>
|
|
245
|
-
<th>Name</th>
|
|
246
|
-
<th>Role</th>
|
|
247
|
-
</tr>
|
|
248
|
-
</thead>
|
|
249
|
-
<tbody>
|
|
250
|
-
<template>
|
|
251
|
-
<tr>
|
|
252
|
-
<td lb-column="name"></td>
|
|
253
|
-
<td lb-column="role"></td>
|
|
254
|
-
</tr>
|
|
255
|
-
</template>
|
|
256
|
-
</tbody>
|
|
257
|
-
<tfoot></tfoot></table
|
|
258
|
-
></lb-table>
|
|
259
|
-
```
|
|
260
|
-
|
|
261
|
-
These are build errors:
|
|
262
|
-
|
|
263
|
-
- An element file with two destinations of one name
|
|
264
|
-
- An element file with more than one `lb-exp-slot`
|
|
265
|
-
- A page naming a template the element file does not have
|
|
266
|
-
- A page giving two templates for one name
|
|
267
|
-
- Content written in a custom element whose element file has no slot
|
|
268
|
-
|
|
269
|
-
### Names
|
|
270
|
-
|
|
271
|
-
Loadbare owns the `lb-` prefix in several namespaces: attribute names, the
|
|
272
|
-
values of its attributes, query names, the column names of its own queries,
|
|
273
|
-
request names, custom element names and custom event names. It also owns
|
|
274
|
-
custom element method names beginning with `lb`.
|
|
275
|
-
|
|
276
|
-
The application should never name anything with the `lb-` prefix anywhere.
|
|
277
|
-
|
|
278
|
-
`createHub` refuses, at startup, a page that declares a query or a handler
|
|
279
|
-
whose name begins with `lb-`.
|
|
280
|
-
|
|
281
|
-
### Landing
|
|
282
|
-
|
|
283
|
-
A query is a name for rows, of kind `row` or `rows`, with a key. The server
|
|
284
|
-
declares a page's queries in `<stub>.queries.ts`, and answers every request,
|
|
285
|
-
a page load included, with response items. A response item carries a query
|
|
286
|
-
name, its kind, its key's column name, and one of a row, all rows, or a
|
|
287
|
-
patch. See [The wire](#the-wire).
|
|
288
|
-
|
|
289
|
-
The hub lands a response item on every element whose `lb-query` names its
|
|
290
|
-
query.
|
|
291
|
-
|
|
292
|
-
| Attribute | Names | The hub |
|
|
293
|
-
| ----------- | -------- | -------------------------------------------------- |
|
|
294
|
-
| `lb-query` | a query | Puts its rows in the element's content |
|
|
295
|
-
| `lb-column` | a column | Sets the element from that column |
|
|
296
|
-
| `lb-show` | a column | Removes it while the named column is null or false; `!column` reverses it |
|
|
297
|
-
|
|
298
|
-
| Stamp | The hub stamps it with |
|
|
299
|
-
| -------------------- | -------------------------------- |
|
|
300
|
-
| `lb-column-value` | The value it set |
|
|
301
|
-
| `lb-key-value` | The row's key |
|
|
302
|
-
| `lb-row-live` | Nothing; it marks a live row |
|
|
303
|
-
| `lb-query-row-count` | The number of live rows it shows |
|
|
304
|
-
|
|
305
|
-
The markup names a query and the columns it shows. The kind and the key are
|
|
306
|
-
the server's, and the markup states neither.
|
|
307
|
-
|
|
308
|
-
The row template is the first `<template>` among the descendants of an
|
|
309
|
-
element with `lb-query`, outside any nested `lb-query`, or, when that
|
|
310
|
-
template is a [group template](#groups), the innermost template inside it.
|
|
311
|
-
A live row is an element the hub cloned from a row template for one row,
|
|
312
|
-
and carries `lb-row-live`.
|
|
313
|
-
|
|
314
|
-
| Kind | Row template | The hub |
|
|
315
|
-
| ------ | ------------ | ----------------------------------- |
|
|
316
|
-
| `row` | no | Lands the row on the element itself |
|
|
317
|
-
| `row` | yes | Lands one live row |
|
|
318
|
-
| `rows` | yes | Lands one live row per row |
|
|
319
|
-
| `rows` | no | Lands nothing |
|
|
320
|
-
|
|
321
|
-
A `rows` query with no row template names the query without showing it,
|
|
322
|
-
which is what an insert form naming the query it adds to is.
|
|
323
|
-
|
|
324
|
-
The nearest ancestor row of an element is the nearest of: the element
|
|
325
|
-
itself when it is a live row, an ancestor that is a live row, and an
|
|
326
|
-
ancestor that carries `lb-query` of kind `row`. The hub reads `lb-column`
|
|
327
|
-
and `lb-show` from the nearest ancestor row. A nested `lb-query` begins a
|
|
328
|
-
new query, and what is inside it is that query's.
|
|
329
|
-
|
|
330
|
-
An element's own `lb-query` names what the element holds, and its own
|
|
331
|
-
`lb-column` reads from the row around it. So a
|
|
332
|
-
`<select lb-query="statuses" lb-column="status">` in a live row of
|
|
333
|
-
`accounts` holds the statuses as its options and shows that account's
|
|
334
|
-
`status`. `lb-column` on an element with `lb-query` is undefined unless the
|
|
335
|
-
element is a control.
|
|
336
|
-
|
|
337
|
-
Name one query on more than one element to show it in more than one place.
|
|
338
|
-
Every one of them receives it.
|
|
339
|
-
|
|
340
|
-
A query that arrives with nowhere to land is reported to the console,
|
|
341
|
-
unless its name begins with `lb-`.
|
|
342
|
-
|
|
343
|
-
#### How a column lands
|
|
344
|
-
|
|
345
|
-
| Element | The hub sets |
|
|
346
|
-
| ------------------------------------------------ | ------------------------------ |
|
|
347
|
-
| A control | Its `value` |
|
|
348
|
-
| A custom element that is not a control | `lb-column-value` only |
|
|
349
|
-
| Any other element | Its text content |
|
|
350
|
-
|
|
351
|
-
A control is an `<input>`, `<select>` or `<textarea>`, or a form-associated
|
|
352
|
-
custom element with a `value` property that fires `change`. A checkbox and
|
|
353
|
-
a radio button are controls too, by the rules below. A
|
|
354
|
-
form-associated custom element declares `static formAssociated = true`.
|
|
355
|
-
|
|
356
|
-
The hub stamps `lb-column-value` with the value on every element it sets.
|
|
357
|
-
It never replaces the content of a custom element, which the builder placed
|
|
358
|
-
there from its element file. A custom element that is not a control
|
|
359
|
-
renders `lb-column-value` itself.
|
|
360
|
-
|
|
361
|
-
The hub gathers from the same controls it lands on, so a value read back is
|
|
362
|
-
the one that landed.
|
|
363
|
-
|
|
364
|
-
A checkbox's state is `checked`, not `value`: HTML's quirk, and so the
|
|
365
|
-
hub's, so that every other part of the vocabulary treats it as any control.
|
|
366
|
-
The hub checks it by the rule `lb-show` is on by: `null` and `false` are
|
|
367
|
-
unchecked, and every other value is checked, with no string read. It
|
|
368
|
-
gathers `"true"` or `"false"`, so an unchecked box still says so, where a
|
|
369
|
-
native form submission leaves it out. The checkbox's own `value`
|
|
370
|
-
attribute means nothing to the hub.
|
|
371
|
-
|
|
372
|
-
```html
|
|
373
|
-
<input type="checkbox" lb-column="active" lb-request="lb-row-update" />
|
|
374
|
-
```
|
|
375
|
-
|
|
376
|
-
A radio button's state is `checked` as well. The hub checks it when its
|
|
377
|
-
`value` attribute equals the value as a string, and unchecks it otherwise,
|
|
378
|
-
`null` included. Of the radio buttons naming one column, the hub gathers
|
|
379
|
-
the `value` of the one checked, or `""` when none is.
|
|
380
|
-
|
|
381
|
-
```html
|
|
382
|
-
<input type="radio" lb-column="shift" value="day" lb-request="lb-row-update" />
|
|
383
|
-
<input type="radio" lb-column="shift" value="night" lb-request="lb-row-update" />
|
|
384
|
-
```
|
|
385
|
-
|
|
386
|
-
An `<input>` of type `file` receives nothing, not even `lb-column-value`,
|
|
387
|
-
and the hub reports it to the console. The hub does not gather one either.
|
|
388
|
-
|
|
389
|
-
A `<select>` with no option for the value shows no selection.
|
|
390
|
-
|
|
391
|
-
An element with `lb-column` and no ancestor row receives nothing. The hub
|
|
392
|
-
still gathers from it.
|
|
393
|
-
|
|
394
|
-
The value arrives as the query produced it, with no conversion, so the
|
|
395
|
-
browser decides what a non-string looks like.
|
|
396
|
-
|
|
397
|
-
A column holds one value. A set of values is a query of its own, never an
|
|
398
|
-
array in a column.
|
|
399
|
-
|
|
400
|
-
A custom element sees `attributeChangedCallback` for an `lb-column-value`
|
|
401
|
-
already present when it upgrades, so it cannot tell a first landing from a
|
|
402
|
-
refresh, and does not need to.
|
|
403
|
-
|
|
404
|
-
#### Rows
|
|
405
|
-
|
|
406
|
-
The hub matches each row to a live row by its key. It stamps
|
|
407
|
-
`lb-key-value` on each live row, and on an element a `row` lands on itself.
|
|
408
|
-
It stamps `lb-row-live` on each live row and nowhere else, so a `row`
|
|
409
|
-
landed inside another query, such as a total in a table's foot, is not one
|
|
410
|
-
of that query's rows. A stylesheet or a custom element selects live rows
|
|
411
|
-
with `[lb-row-live]`, and never with `[lb-key-value]`.
|
|
412
|
-
|
|
413
|
-
A `row` query answering `null` lands every column its element names as
|
|
414
|
-
null, and the element loses `lb-key-value`. Under a row template, its live
|
|
415
|
-
row leaves.
|
|
416
|
-
|
|
417
|
-
A key is unique within a query. Two rows with one key in the same answer,
|
|
418
|
-
or two live rows showing one key, are reported on the console.
|
|
419
|
-
|
|
420
|
-
All rows decide membership: a live row whose key did not arrive leaves —
|
|
421
|
-
see [What happened to a row](#what-happened-to-a-row). A patch touches only
|
|
422
|
-
the rows it names, and leaves every other live row's contents alone.
|
|
423
|
-
|
|
424
|
-
The hub places every live row by the query's [order](#order), immediately
|
|
425
|
-
before the row template, or before its clone in the row's
|
|
426
|
-
[group](#groups). A row moves only when the order puts it somewhere else,
|
|
427
|
-
so all rows landing again unchanged move nothing. A live row already in the
|
|
428
|
-
markup when the first answer lands is one of the query's rows.
|
|
429
|
-
|
|
430
|
-
After every landing the hub stamps the element with `lb-query-row-count`,
|
|
431
|
-
the number of live rows it shows. A row a [filter](#filters) hides is not
|
|
432
|
-
counted. The server answers with rows and says
|
|
433
|
-
nothing about how many survived, so this is the one fact a page cannot be
|
|
434
|
-
sent. It makes an empty query a stylesheet rule:
|
|
435
|
-
|
|
436
|
-
```css
|
|
437
|
-
[lb-query-row-count="0"] .roster-empty {
|
|
438
|
-
display: revert;
|
|
439
|
-
}
|
|
440
|
-
```
|
|
441
|
-
|
|
442
|
-
A row missing its key column is reported to the console and not landed.
|
|
443
|
-
|
|
444
|
-
When the focus is in a live row that placing moves, the hub puts it back on
|
|
445
|
-
the same element and scrolls it into view. When that row leaves, or a
|
|
446
|
-
filter hides it, the focus goes to the first element the user can operate
|
|
447
|
-
in the first row showing.
|
|
448
|
-
|
|
449
|
-
#### Order
|
|
450
|
-
|
|
451
|
-
A `rows` query's order is a rule over its columns. The server declares it
|
|
452
|
-
with the query, and the user may choose another:
|
|
453
|
-
|
|
454
|
-
```ts
|
|
455
|
-
accounts: rows("id", (ctx) => ctx.db.accounts(), {
|
|
456
|
-
order: "category_order,account_name",
|
|
457
|
-
}),
|
|
458
|
-
```
|
|
459
|
-
|
|
460
|
-
An order is columns separated by commas. A column written after `-` sorts
|
|
461
|
-
descending. `lb-order`, on the element carrying `lb-query`, names a column
|
|
462
|
-
of [`lb-choices`](#choices) holding the user's order, and while that column
|
|
463
|
-
is not empty it replaces the declared order. With neither, rows show in the
|
|
464
|
-
order the server sent them, and a patch's new rows go last.
|
|
465
|
-
|
|
466
|
-
| Values | Compare |
|
|
467
|
-
| -------------------- | ------------------------------------------ |
|
|
468
|
-
| Two numbers | Numerically |
|
|
469
|
-
| Two strings | As the user's language orders them |
|
|
470
|
-
| Two booleans | `false` before `true` |
|
|
471
|
-
| Two different types | Booleans, then numbers, then strings |
|
|
472
|
-
| `null` or missing | After everything ascending, first descending |
|
|
473
|
-
|
|
474
|
-
A number sent as a string sorts as a string, so a column the order reads
|
|
475
|
-
numerically arrives as a JSON number, or its query declares its type.
|
|
476
|
-
`types` names a type for any of a `rows` query's columns, one of `number`,
|
|
477
|
-
`boolean` and `text`, and the column compares read as that type whatever
|
|
478
|
-
JSON type its values arrive as:
|
|
479
|
-
|
|
480
|
-
```ts
|
|
481
|
-
accounts: rows("id", (ctx) => ctx.db.accounts(), {
|
|
482
|
-
order: "-balance",
|
|
483
|
-
types: { balance: "number" },
|
|
484
|
-
}),
|
|
485
|
-
```
|
|
486
|
-
|
|
487
|
-
A value that is no value of its declared type compares as `null` does.
|
|
488
|
-
`createHub` refuses at startup a type it does not know.
|
|
489
|
-
|
|
490
|
-
The declared order and types travel once, on the answer that loads the
|
|
491
|
-
page, and the hub keeps them with the query's answer for every answer after
|
|
492
|
-
it, all rows or a patch. `readAs` in
|
|
493
|
-
`@loadbare/app/types` is the rule the hub applies.
|
|
494
|
-
|
|
495
|
-
Rows that compare equal keep the order they arrived in, and a patch's new
|
|
496
|
-
rows come after the rows already showing.
|
|
497
|
-
|
|
498
|
-
The hub sorts the rows it has, so a choice of order places them again with
|
|
499
|
-
no round trip:
|
|
500
|
-
|
|
501
|
-
```html
|
|
502
|
-
<div lb-query="lb-choices">
|
|
503
|
-
<select lb-column="accounts_order" lb-request="lb-row-update">
|
|
504
|
-
<option value="">By category</option>
|
|
505
|
-
<option value="account_name">By name</option>
|
|
506
|
-
</select>
|
|
507
|
-
</div>
|
|
508
|
-
|
|
509
|
-
<table lb-query="accounts" lb-order="accounts_order">
|
|
510
|
-
...
|
|
511
|
-
</table>
|
|
512
|
-
```
|
|
513
|
-
|
|
514
|
-
#### Filters
|
|
515
|
-
|
|
516
|
-
`lb-filter`, on the element carrying `lb-query`, names the columns of its
|
|
517
|
-
rows a choice may filter, separated by commas. The column of
|
|
518
|
-
[`lb-choices`](#choices) of the same name holds the filter. A row that does
|
|
519
|
-
not pass every filter is `hidden`, and a ticked `lb-select` in it is
|
|
520
|
-
cleared. A group whose rows are all hidden is hidden. An empty choice
|
|
521
|
-
filters nothing.
|
|
522
|
-
|
|
523
|
-
```html
|
|
524
|
-
<table lb-query="accounts" lb-filter="account_name, balance">
|
|
525
|
-
```
|
|
526
|
-
|
|
527
|
-
A filter is terms, each an operator and a value. A term begins wherever
|
|
528
|
-
whitespace comes before an operator, so a value may hold spaces. Text with
|
|
529
|
-
no operator is `*=`. A term with no value is left out. Every term must
|
|
530
|
-
pass: `>=2026-01-01 <=2026-03-31`.
|
|
531
|
-
|
|
532
|
-
| Operator | Compares |
|
|
533
|
-
| -------------------------- | ------------------------------------------------- |
|
|
534
|
-
| `^=`, `$=`, `*=` | As text, ignoring case: begins with, ends with, contains |
|
|
535
|
-
| `=`, `!=`, `<`, `<=`, `>`, `>=` | As the [order](#order) compares, with the value read as the column's type |
|
|
536
|
-
|
|
537
|
-
The column's type is its declared [type](#order), if its query declares
|
|
538
|
-
one. Otherwise it is a number for a JSON number, `true` or `false` for a
|
|
539
|
-
boolean, and text otherwise. A value that is not of the column's type, and
|
|
540
|
-
a column that is null or missing, pass only `!=`.
|
|
541
|
-
|
|
542
|
-
`parseFilter`, `passes` and `passesTerm` in `@loadbare/app/types` are the
|
|
543
|
-
rule the hub applies.
|
|
544
|
-
|
|
545
|
-
#### Groups
|
|
546
|
-
|
|
547
|
-
A group is a run of rows sharing the order's leading term. A group
|
|
548
|
-
template is a `<template lb-group>` in the row template's place, holding the
|
|
549
|
-
group's heading and exactly one nested `<template>`, which is where the
|
|
550
|
-
group's contents go: the next group template, or the row template. The nth
|
|
551
|
-
group template breaks on the order's nth term, so groups nest to any depth.
|
|
552
|
-
|
|
553
|
-
```html
|
|
554
|
-
<table lb-query="accounts">
|
|
555
|
-
<thead><tr><th>Account</th></tr></thead>
|
|
556
|
-
<template lb-group>
|
|
557
|
-
<tbody>
|
|
558
|
-
<tr><th scope="rowgroup" lb-column="category"></th></tr>
|
|
559
|
-
<template><tr><td lb-column="account_name"></td></tr></template>
|
|
560
|
-
</tbody>
|
|
561
|
-
</template>
|
|
562
|
-
</table>
|
|
563
|
-
```
|
|
564
|
-
|
|
565
|
-
The hub clones the group template where the order breaks and puts the
|
|
566
|
-
group's contents immediately before the clone of its nested template. The
|
|
567
|
-
markup's nesting decides the shape:
|
|
568
|
-
|
|
569
|
-
| The nested template is | The group's contents land | Example |
|
|
570
|
-
| --------------------------------- | ----------------------------------- | -------------------------------- |
|
|
571
|
-
| Inside the heading's element | Inside it | `<tbody>`, `<li>`, `<details>` |
|
|
572
|
-
| Beside the heading | After the heading, as its siblings | A heading `<tr>` in one `<tbody>` |
|
|
573
|
-
|
|
574
|
-
`<tbody>` and `<optgroup>` do not nest, so a table's levels below the first
|
|
575
|
-
are heading rows beside their rows. Content after the nested template lands
|
|
576
|
-
after the group's rows, which is where a group's footer goes.
|
|
577
|
-
|
|
578
|
-
A group's heading is filled from its first row, the same way a live row is
|
|
579
|
-
filled, so it shows any column of the row. The example breaks on
|
|
580
|
-
`category_order` and shows `category`. The hub fills it from no other row.
|
|
581
|
-
|
|
582
|
-
The hub stamps each top-level element of a group's clone with
|
|
583
|
-
`lb-group-live`, `lb-group-column`, the column it breaks on, and
|
|
584
|
-
`lb-group-value`, its value there. A row creates its group, and a group
|
|
585
|
-
leaves with its last row. When the order changes, a group whose run goes
|
|
586
|
-
on is kept and moved, and a group whose run ended leaves.
|
|
587
|
-
|
|
588
|
-
A group template with no nested template is reported to the console. More
|
|
589
|
-
group templates than the order has terms show each extra level as one
|
|
590
|
-
group, and are reported once.
|
|
591
|
-
|
|
592
|
-
#### Aggregates
|
|
593
|
-
|
|
594
|
-
An aggregate attribute sets an element from the rows of the nearest group
|
|
595
|
-
around it, or of the whole list outside any group.
|
|
596
|
-
|
|
597
|
-
| Attribute | Sets the element to |
|
|
598
|
-
| --------------- | --------------------------------------------- |
|
|
599
|
-
| `lb-count` | The number of rows |
|
|
600
|
-
| `lb-sum="col"` | The sum of `col` |
|
|
601
|
-
| `lb-avg="col"` | The average of `col` |
|
|
602
|
-
| `lb-min="col"` | The least value of `col`, by the [order](#order)'s comparison |
|
|
603
|
-
| `lb-max="col"` | The greatest, likewise |
|
|
604
|
-
|
|
605
|
-
```html
|
|
606
|
-
<tr><td><span lb-count></span> accounts</td><td lb-sum="balance"></td></tr>
|
|
607
|
-
```
|
|
608
|
-
|
|
609
|
-
It lands as a column does, with `lb-column-value`. A sum and an average
|
|
610
|
-
are exact decimals, read from JSON numbers and from strings holding a plain
|
|
611
|
-
decimal, as Postgres sends `numeric`. A sum has as many places as the most
|
|
612
|
-
precise value, and an average up to two more. `null` and empty values are
|
|
613
|
-
left out, as SQL leaves them out. Any other value is left out and reported
|
|
614
|
-
once. The hub does not format the result.
|
|
615
|
-
|
|
616
|
-
An aggregate covers the rows showing, so a row a [filter](#filters) hides
|
|
617
|
-
is left out. Where the true total
|
|
618
|
-
matters beyond what the page shows, it is a row the server answers.
|
|
619
|
-
|
|
620
|
-
#### What happened to a row
|
|
621
|
-
|
|
622
|
-
Every change the hub makes to a list is one a stylesheet can see, so an
|
|
623
|
-
application animates with CSS alone and the hub starts no animation.
|
|
624
|
-
|
|
625
|
-
| Attribute | On | Means |
|
|
626
|
-
| ------------------ | -------------------- | -------------------------------------------------- |
|
|
627
|
-
| `lb-row-created` | A live row | Created when the hub last touched it |
|
|
628
|
-
| `lb-row-changed` | A live row | Its values changed when the hub last touched it |
|
|
629
|
-
| `lb-row-moved` | A live row | Its values changed, and it now stands elsewhere |
|
|
630
|
-
| `lb-row-requested` | A live row | `created`, `changed` or `moved` by this page's request |
|
|
631
|
-
| `lb-row-leaving` | A row on its way out | No longer live |
|
|
632
|
-
| `lb-group-leaving` | A group on its way out | No longer live |
|
|
633
|
-
|
|
634
|
-
The hub touches a row when an answer names it. `lb-row-created`,
|
|
635
|
-
`lb-row-changed` and `lb-row-moved` are cleared when it next does, and one
|
|
636
|
-
applied again is removed and added as two changes, so an animation keyed to
|
|
637
|
-
it plays again. `lb-row-requested` is cleared at every landing of the
|
|
638
|
-
row's query, and set only by a landing that answers a request from the page.
|
|
639
|
-
|
|
640
|
-
A row or a group that leaves loses `lb-row-live` or `lb-group-live`, takes
|
|
641
|
-
`lb-row-leaving` or `lb-group-leaving` and `inert`, and is removed once the
|
|
642
|
-
animations running on it have finished, at once when there are none. A
|
|
643
|
-
row leaves when all rows arrive without its key, or a patch drops it.
|
|
644
|
-
|
|
645
|
-
A row is moved when its values changed and the nearest rows whose values did
|
|
646
|
-
not change, before and after it, are not the ones it stood between. A row
|
|
647
|
-
it passed is not stamped, and a change of the user's order, which changes no
|
|
648
|
-
values, stamps none. A moved row stays live and moves.
|
|
649
|
-
|
|
650
|
-
```css
|
|
651
|
-
[lb-row-created] { animation: arrive 200ms; }
|
|
652
|
-
[lb-row-leaving] { animation: depart 200ms forwards; }
|
|
653
|
-
```
|
|
654
|
-
|
|
655
|
-
A row never travels between the two places, since that takes measuring, and
|
|
656
|
-
measuring takes script. Scrolling to a row is script too: a custom element
|
|
657
|
-
reads `lb-row-requested` in [`lbRowsLanded`](#row-hooks).
|
|
658
|
-
|
|
659
|
-
#### Displaying by condition
|
|
660
|
-
|
|
661
|
-
`lb-show` names the column that decides whether an element is present.
|
|
662
|
-
`null` and `false` are off, and every other value is on. No string is read,
|
|
663
|
-
so `"false"` is on, and a query spells a condition as a boolean or a null.
|
|
664
|
-
|
|
665
|
-
```html
|
|
666
|
-
<template>
|
|
667
|
-
<tr>
|
|
668
|
-
<td lb-column="name"></td>
|
|
669
|
-
<td><button lb-request="lb-row-delete" lb-show="removable">Remove</button></td>
|
|
670
|
-
</tr>
|
|
671
|
-
</template>
|
|
672
|
-
```
|
|
673
|
-
|
|
674
|
-
Write `!` before the column to reverse it: `lb-show="!chosen"` is present
|
|
675
|
-
while `chosen` is null or false. One column then decides both of two
|
|
676
|
-
elements, rather than a column and its opposite, which could disagree.
|
|
677
|
-
|
|
678
|
-
It reads from the nearest ancestor row, as `lb-column` does, and on an
|
|
679
|
-
element that carries `lb-query` the column belongs to the row around it. A
|
|
680
|
-
row that does not carry the column leaves the element as it is.
|
|
681
|
-
`lb-show` with no ancestor row is undefined.
|
|
682
|
-
|
|
683
|
-
An element that is off is moved into a `<template lb-show="column">`
|
|
684
|
-
standing where it stood, and moved back out when the column turns on. It is
|
|
685
|
-
not rendered, focused, clicked, announced or gathered. It is moved and
|
|
686
|
-
never rebuilt, so a custom element keeps its instance and a control keeps
|
|
687
|
-
what was typed into it. Landing reaches into that template, so the element
|
|
688
|
-
and everything inside it return current. A custom element hears nothing
|
|
689
|
-
going in or coming out, and still receives `lb-column-value` while it is
|
|
690
|
-
away.
|
|
691
|
-
|
|
692
|
-
The builder ships every `lb-show` element already inside its template, so
|
|
693
|
-
nothing conditional shows until its row lands. An absent element's template
|
|
694
|
-
keeps its place among its siblings, and a position selector counts it.
|
|
695
|
-
|
|
696
|
-
These are build errors: `lb-show` on a row template's root, `lb-show` with
|
|
697
|
-
no `lb-query` around it, `lb-show` on a `<template>`, and `lb-show="!"` or
|
|
698
|
-
`lb-show="!!column"`.
|
|
699
|
-
|
|
700
|
-
Hiding is presentation, and the server still refuses what a request may not
|
|
701
|
-
do.
|
|
702
|
-
|
|
703
|
-
#### Master-detail
|
|
704
|
-
|
|
705
|
-
A master is a `row` query and its detail is a `rows` query, as two names
|
|
706
|
-
answered side by side. A column never holds rows.
|
|
707
|
-
|
|
708
|
-
```html
|
|
709
|
-
<section lb-query="invoice">
|
|
710
|
-
<h2 lb-column="number"></h2>
|
|
711
|
-
<span lb-column="customer"></span>
|
|
712
|
-
</section>
|
|
713
|
-
|
|
714
|
-
<table>
|
|
715
|
-
<tbody lb-query="invoiceLines">
|
|
716
|
-
<template><tr><td lb-column="item"></td><td lb-column="amount"></td></tr></template>
|
|
717
|
-
</tbody>
|
|
718
|
-
</table>
|
|
719
|
-
```
|
|
720
|
-
|
|
721
|
-
Many masters, each with its own detail, is one `rows` query of joined rows:
|
|
722
|
-
each detail row carries its master's columns. A [group
|
|
723
|
-
template](#groups) shows each master as a group, ordered by the master's
|
|
724
|
-
key first.
|
|
725
|
-
|
|
726
|
-
A query nested in another query's row template receives the same rows in
|
|
727
|
-
every live row. That serves a picker offering the same choices on every
|
|
728
|
-
row, and is not a way to show a different detail per row. A live row added
|
|
729
|
-
later is filled from the nested query's last answer, so a request that adds
|
|
730
|
-
one does not refresh the nested query to fill it.
|
|
731
|
-
|
|
732
|
-
Which master a page shows is a query parm, which a query reads off `ctx`
|
|
733
|
-
since it takes no argument from the browser — see [Query parms](#query-parms).
|
|
734
|
-
When a write creates or removes the master, the server response moves the
|
|
735
|
-
URL — see
|
|
736
|
-
[When a server response moves the URL](#when-a-server-response-moves-the-url).
|
|
737
|
-
|
|
738
|
-
### Requests
|
|
739
|
-
|
|
740
|
-
---- UNEDITED ----
|
|
741
|
-
|
|
742
|
-
| Attribute | Names | The hub |
|
|
743
|
-
| ------------ | --------- | -------------------------------------------- |
|
|
744
|
-
| `lb-request` | a request | Issues that request when the element commits |
|
|
745
|
-
|
|
746
|
-
| Stamp | The hub stamps it |
|
|
747
|
-
| -------------------- | ------------------------------------------ |
|
|
748
|
-
| `lb-request-pending` | While the round trip is in flight |
|
|
749
|
-
| `lb-request-error` | When the round trip fails, until the next |
|
|
750
|
-
|
|
751
|
-
The request name is the attribute value of `lb-request`. It names either a
|
|
752
|
-
request Loadbare provides or a declared request: a name the page declares
|
|
753
|
-
under `handlers`, which runs application code.
|
|
754
|
-
|
|
755
|
-
Every request has one shape, on the event and on the wire:
|
|
756
|
-
|
|
757
|
-
```
|
|
758
|
-
{ name: "lb-row-insert", query: "roster", values: { name: "Ann" } }
|
|
759
|
-
{ name: "lb-row-update", query: "roster", key: "42", values: { name: "Ann" } }
|
|
760
|
-
{ name: "lb-row-delete", query: "roster", key: "42" }
|
|
761
|
-
{ name: "mailRoster", query: "roster", key: "42" }
|
|
762
|
-
{ name: "lb-row-update", query: "roster", keys: ["42", "43"], values: { role: "lead" } }
|
|
763
|
-
{ name: "lb-row-insert", query: "invoice", values: { memo: "May" }, rows: { lines: [{ key: "1", values: { qty: "3" } }] } }
|
|
764
|
-
```
|
|
765
|
-
|
|
766
|
-
The hub sends `query`, `key` and `values` as present, and `keys` in place of
|
|
767
|
-
`key` for a request acting on the rows selected — see
|
|
768
|
-
[Selecting rows](#selecting-rows).
|
|
769
|
-
|
|
770
|
-
#### Committing
|
|
771
|
-
|
|
772
|
-
An element commits on `submit` when it is a form, on `change` when it is a
|
|
773
|
-
control, and on `click` otherwise.
|
|
774
|
-
|
|
775
|
-
A submit button with a form owner never commits on `click`. On a form's
|
|
776
|
-
`submit`, the hub issues the submitter's `lb-request` when it carries one,
|
|
777
|
-
and the form's otherwise, the way `formaction` replaces `action`.
|
|
778
|
-
|
|
779
|
-
A click on a descendant of an element carrying `lb-request` commits that
|
|
780
|
-
element, as long as the element is inside `<lb-hub>` and no interactive
|
|
781
|
-
content lies between them. Interactive content is HTML's: `<a href>`,
|
|
782
|
-
`<button>`, `<input>` other than hidden, `<select>`, `<textarea>`, `<label>`,
|
|
783
|
-
`<details>`, `<iframe>`, `<embed>`, `<audio controls>`, `<video controls>`
|
|
784
|
-
and `<img usemap>`, plus any form-associated custom element. A click on
|
|
785
|
-
one of them belongs to it: a click into an input inside a deletable row
|
|
786
|
-
focuses the input and deletes nothing.
|
|
787
|
-
|
|
788
|
-
The hub ignores a commit on an element carrying `lb-request-pending`.
|
|
789
|
-
|
|
790
|
-
#### Gathering
|
|
791
|
-
|
|
792
|
-
The hub gathers values when it issues a request. It gathers each control
|
|
793
|
-
value under the column its `lb-column` names.
|
|
794
|
-
|
|
795
|
-
| Attribute | The hub |
|
|
796
|
-
| -------------- | ------------------------------------------- |
|
|
797
|
-
| `lb-query` | Issues the request for it |
|
|
798
|
-
| `lb-column` | Gathers the control value under that column |
|
|
799
|
-
| `lb-key-value` | Takes `key` from it |
|
|
800
|
-
|
|
801
|
-
- When the element carrying `lb-request` has `lb-column`, the hub gathers
|
|
802
|
-
its value alone, and sends `values` with one member.
|
|
803
|
-
- Otherwise, when the element is a `<form>`, the hub gathers every
|
|
804
|
-
`lb-column` whose form owner is the element.
|
|
805
|
-
- Otherwise, when the element is in a live row, the hub gathers every
|
|
806
|
-
`lb-column` in that live row.
|
|
807
|
-
- Otherwise the hub gathers every `lb-column` whose form owner is the
|
|
808
|
-
element's form owner.
|
|
809
|
-
|
|
810
|
-
The form owner includes a control that names the form with the HTML `form`
|
|
811
|
-
attribute from anywhere in the document, which is how a table row's
|
|
812
|
-
controls belong to a form that cannot wrap a `<tr>`:
|
|
813
|
-
|
|
814
|
-
```html
|
|
815
|
-
<form id="add-member" lb-request="lb-row-insert"></form>
|
|
816
|
-
<table lb-query="roster">
|
|
817
|
-
<tbody>
|
|
818
|
-
<template>
|
|
819
|
-
<tr><td lb-column="name"></td><td lb-column="role"></td></tr>
|
|
820
|
-
</template>
|
|
821
|
-
</tbody>
|
|
822
|
-
<tfoot>
|
|
823
|
-
<tr>
|
|
824
|
-
<td><input lb-column="name" form="add-member" /></td>
|
|
825
|
-
<td>
|
|
826
|
-
<input lb-column="role" form="add-member" />
|
|
827
|
-
<button form="add-member">Add</button>
|
|
828
|
-
</td>
|
|
829
|
-
</tr>
|
|
830
|
-
</tfoot>
|
|
831
|
-
</table>
|
|
832
|
-
```
|
|
833
|
-
|
|
834
|
-
The hub gathers from controls only. It skips a control whose nearest
|
|
835
|
-
ancestor `lb-query` is a descendant of the live row or form, since that
|
|
836
|
-
control belongs to a nested query, except that a form gathers the rows of a
|
|
837
|
-
query nested in its own — see [A form over a set of rows](#a-form-over-a-set-of-rows). An element carrying `lb-query` and
|
|
838
|
-
`lb-column` both, such as a picker, belongs to the row around it and is
|
|
839
|
-
gathered.
|
|
840
|
-
|
|
841
|
-
The hub issues the request for the nearest ancestor `lb-query` of the
|
|
842
|
-
controls it gathered, and takes `key` from their nearest ancestor row when
|
|
843
|
-
that row is of the same query. When the gathered controls have different
|
|
844
|
-
nearest ancestor `lb-query`, the hub reports an error and sends nothing.
|
|
845
|
-
|
|
846
|
-
When it gathers nothing, the hub issues the request for the nearest
|
|
847
|
-
ancestor `lb-query` of the element carrying `lb-request`, and takes `key`
|
|
848
|
-
from that element's nearest ancestor row when that row is of the same
|
|
849
|
-
query.
|
|
850
|
-
|
|
851
|
-
#### A form over a set of rows
|
|
852
|
-
|
|
853
|
-
A form gathers the live rows of a query nested directly in its own query,
|
|
854
|
-
besides its own controls. Each goes as its key and the values of the
|
|
855
|
-
controls in it, under the nested query's name:
|
|
856
|
-
|
|
857
|
-
```
|
|
858
|
-
{ name: "lb-row-insert", query: "paychecks", values: { pay_date: "2026-10-02" },
|
|
859
|
-
rows: { deduction_types: [{ key: "401k", values: { amount: "50" } },
|
|
860
|
-
{ key: "dental", values: { amount: "" } }] } }
|
|
861
|
-
```
|
|
862
|
-
|
|
863
|
-
The form's own query is the one it carries, or else the nearest around it.
|
|
864
|
-
A nested query is gathered when its nearest ancestor `lb-query` is the
|
|
865
|
-
form's own, so one level only: a query nested deeper shows the same rows in
|
|
866
|
-
every row around it, and its keys would repeat. Every live row of it goes,
|
|
867
|
-
whatever its values, and only controls inside a live row of it.
|
|
868
|
-
|
|
869
|
-
An insert, an update and a declared request carry `rows`; a delete does
|
|
870
|
-
not. A form whose only controls are in nested rows sends for its own query,
|
|
871
|
-
keyed by the row landed on it or the row it is pointed at, with empty
|
|
872
|
-
`values`. A live row gathers nothing nested.
|
|
873
|
-
|
|
874
|
-
The handler for the form's query receives `rows` beside `values`, so the
|
|
875
|
-
master and its details are written together. The server checks that
|
|
876
|
-
`rows` holds string keys and string values by query, and ignores a request
|
|
877
|
-
whose `rows` does not.
|
|
878
|
-
|
|
879
|
-
A nested query answers once for the page, as anywhere — see
|
|
880
|
-
[Master-detail](#master-detail). The form enters a new master with its
|
|
881
|
-
details, or edits the master the page shows by a query parm, whose details
|
|
882
|
-
the nested query reads from the same parm. A pointed form fills its own
|
|
883
|
-
controls from the row it points at, and its nested rows stay the nested
|
|
884
|
-
query's answer.
|
|
885
|
-
|
|
886
|
-
#### A row not yet entered
|
|
887
|
-
|
|
888
|
-
A row whose key is the empty string is a new one, not yet entered. It
|
|
889
|
-
arises two ways: a `row` query answers with an empty key, as one reading
|
|
890
|
-
the master's key from an empty query parm does, or `--lb-new` points a form
|
|
891
|
-
(see [A form pointed at a row](#a-form-pointed-at-a-row)). Either way the
|
|
892
|
-
element carries `lb-key-value=""`.
|
|
893
|
-
|
|
894
|
-
The hub sends no `key` for it, whether the key came from where the element
|
|
895
|
-
sits or from a request a custom element dispatched. An `lb-row-insert` and
|
|
896
|
-
a declared request are sent without one. An `lb-row-update` and an
|
|
897
|
-
`lb-row-delete` are not sent, because nothing is on record to change, and
|
|
898
|
-
nothing is reported: it is the row's state, not a mistake. So a control
|
|
899
|
-
saving its own column on `change` saves on a row on record, and on a new
|
|
900
|
-
row only holds what a submit will gather. No ancestor sees a request not
|
|
901
|
-
sent, and it gets no `lb-request-done`.
|
|
902
|
-
|
|
903
|
-
An update or a delete with no row around it at all has no key either, and
|
|
904
|
-
is reported to the console, as any request missing what it needs is.
|
|
905
|
-
|
|
906
|
-
A field the request already carries is its issuer's and is kept. A custom
|
|
907
|
-
element that dispatches its own request with `values` is not gathered over.
|
|
908
|
-
|
|
909
|
-
#### The request names
|
|
910
|
-
|
|
911
|
-
| Request name | Needs | Runs under `crud` |
|
|
912
|
-
| --------------- | ------------------------ | ----------------- |
|
|
913
|
-
| `lb-row-insert` | `query`, `values` | `rowInsert` |
|
|
914
|
-
| `lb-row-update` | `query`, `key`, `values` | `rowUpdate` |
|
|
915
|
-
| `lb-row-delete` | `query`, `key` | `rowDelete` |
|
|
916
|
-
|
|
917
|
-
`keys` stands in for `key`. The hub issues one of these only when it has
|
|
918
|
-
what the table lists. An
|
|
919
|
-
`lb-row-insert` or `lb-row-update` that gathers nothing is not issued. A
|
|
920
|
-
request that is not issued is reported to the console.
|
|
921
|
-
|
|
922
|
-
Loadbare provides the handlers for these names: each runs the page's `crud`
|
|
923
|
-
entry for the request's query. A declared request runs the application's
|
|
924
|
-
handler under `handlers`, and the hub issues it with whatever it found.
|
|
925
|
-
|
|
926
|
-
A request name beginning with `lb-` that is none of these three is refused
|
|
927
|
-
by the builder in markup, and by the hub when a script dispatches it.
|
|
928
|
-
|
|
929
|
-
For a query the hub serves, the hub answers the request itself, with no
|
|
930
|
-
round trip. The hub serves `lb-url`, and answers `lb-row-update` for it —
|
|
931
|
-
see [Query parms](#query-parms).
|
|
932
|
-
|
|
933
|
-
#### A form pointed at a row
|
|
934
|
-
|
|
935
|
-
A set of rows the user edits or adds to has one form on the page, naming
|
|
936
|
-
the same query, outside every row. A button points it with HTML's
|
|
937
|
-
`commandfor`, naming the form's `id`, and `command`:
|
|
938
|
-
|
|
939
|
-
| Command | On a button | The hub |
|
|
940
|
-
| -------------- | --------------------------------- | ----------------------------------------- |
|
|
941
|
-
| `--lb-edit` | In a live row of the form's query | Fills the form from that row, keyed by it |
|
|
942
|
-
| `--lb-new` | Anywhere | Starts the form empty, for an insert |
|
|
943
|
-
| `--lb-release` | Anywhere | Lets the form go without saving |
|
|
944
|
-
|
|
945
|
-
HTML requires a custom command to begin with `--`, and fires it as a
|
|
946
|
-
`command` event on the form, which the hub hears.
|
|
947
|
-
|
|
948
|
-
```html
|
|
949
|
-
<tbody lb-query="paychecks">
|
|
950
|
-
<template><tr>
|
|
951
|
-
<td lb-column="pay_date"></td>
|
|
952
|
-
<td><button type="button" commandfor="paycheck" command="--lb-edit">Edit</button></td>
|
|
953
|
-
</tr></template>
|
|
954
|
-
</tbody>
|
|
955
|
-
<button type="button" commandfor="paycheck" command="--lb-new">New</button>
|
|
956
|
-
|
|
957
|
-
<dialog>
|
|
958
|
-
<form id="paycheck" lb-query="paychecks" lb-request="lb-row-update">
|
|
959
|
-
<input lb-column="pay_date" type="date" />
|
|
960
|
-
<button type="submit">Save</button>
|
|
961
|
-
<button type="submit" lb-request="lb-row-insert">Save new</button>
|
|
962
|
-
<button type="button" commandfor="paycheck" command="--lb-release">Cancel</button>
|
|
963
|
-
</form>
|
|
964
|
-
</dialog>
|
|
965
|
-
```
|
|
966
|
-
|
|
967
|
-
Which row a form edits is interaction state, not a view, so it is not a
|
|
968
|
-
query parm. The user edits only a row they can see, so Edit fills the form
|
|
969
|
-
from the row as the hub holds it, every column the query answered, with no
|
|
970
|
-
round trip. A row that must be fetched to be edited is another page, with
|
|
971
|
-
that row as its master.
|
|
972
|
-
|
|
973
|
-
Pointing a form first empties it: every column it names lands as null, and
|
|
974
|
-
the form resets, so each control returns to its default. Edit then lands
|
|
975
|
-
the row on the form as on any element a `row` lands on, stamps the form
|
|
976
|
-
`lb-key-value` with the row's key, and stamps the row `lb-row-editing`.
|
|
977
|
-
Gathering then takes the key from the form. New stamps an empty key, making
|
|
978
|
-
the form [a row not yet entered](#a-row-not-yet-entered). Both focus the
|
|
979
|
-
form's first control.
|
|
980
|
-
|
|
981
|
-
| The form's `lb-key-value` | The form is |
|
|
982
|
-
| ------------------------- | ------------------ |
|
|
983
|
-
| A key | Editing that row |
|
|
984
|
-
| Empty | Entering a new row |
|
|
985
|
-
| Absent | Not in use |
|
|
986
|
-
|
|
987
|
-
A stylesheet shows the form's buttons by it, such as Save while it has a
|
|
988
|
-
key and Save new while the key is empty.
|
|
989
|
-
|
|
990
|
-
A request from the form that succeeds lets it go, as `--lb-release` does:
|
|
991
|
-
the form empties, and both stamps go. One that fails leaves the form
|
|
992
|
-
pointed, as the user had it.
|
|
993
|
-
|
|
994
|
-
When the form is inside a `<dialog>`, Edit and New call `showModal()` on it,
|
|
995
|
-
and letting go closes it. The dialog closing by any of HTML's means,
|
|
996
|
-
Escape, `command="close"` or `method="dialog"`, lets the form go. A form in
|
|
997
|
-
no dialog opens nothing; it is always showing, or a stylesheet shows it
|
|
998
|
-
while it carries `lb-key-value`.
|
|
999
|
-
|
|
1000
|
-
The form names a `rows` query and holds no row template, so that query's
|
|
1001
|
-
answers land nothing on it, and what the user typed survives a refresh.
|
|
1002
|
-
|
|
1003
|
-
A command the hub cannot carry out is reported to the console: Edit from
|
|
1004
|
-
outside a live row of the form's query, Edit for a key the hub holds no row
|
|
1005
|
-
for, and any command aimed at an element that is not a form carrying
|
|
1006
|
-
`lb-query`.
|
|
1007
|
-
|
|
1008
|
-
#### Selecting rows
|
|
1009
|
-
|
|
1010
|
-
A checkbox carrying `lb-select` in a row template selects the live row it
|
|
1011
|
-
sits in, and the hub stamps the row `lb-row-selected` while it is ticked.
|
|
1012
|
-
It is not a column: it carries no `lb-column` or `lb-request`, lands
|
|
1013
|
-
nothing, and is not gathered.
|
|
1014
|
-
|
|
1015
|
-
A request for the query that names no one row, neither from inside one of
|
|
1016
|
-
its rows nor from a form pointed at one, acts on the rows selected. When
|
|
1017
|
-
any `lb-select` checkbox of that query is showing, the hub adds the keys of
|
|
1018
|
-
those ticked as `keys`:
|
|
1019
|
-
|
|
1020
|
-
```
|
|
1021
|
-
{ name: "lb-row-update", query: "transactions", keys: ["4", "9"], values: { acct: "12" } }
|
|
1022
|
-
```
|
|
1023
|
-
|
|
1024
|
-
- `lb-row-update` and `lb-row-delete` are sent with `keys`, and with none
|
|
1025
|
-
ticked are not sent, silently, as for [a row not yet
|
|
1026
|
-
entered](#a-row-not-yet-entered).
|
|
1027
|
-
- A declared request is sent with `keys`, empty when none is ticked, and the
|
|
1028
|
-
handler decides.
|
|
1029
|
-
- `lb-row-insert` adds a row, and the selection is not its.
|
|
1030
|
-
- With no `lb-select` checkbox of the query showing, the request is an
|
|
1031
|
-
ordinary one, and carries no `keys`.
|
|
1032
|
-
|
|
1033
|
-
The server runs the page's `rowUpdate` or `rowDelete` once for each key,
|
|
1034
|
-
with that key, as for each row alone, so a handler written for one row
|
|
1035
|
-
serves several. It lays the patches the runs return together, and runs the
|
|
1036
|
-
refresh set once. A run returning `url()` is reported and ignored, since
|
|
1037
|
-
several rows cannot each choose one view. A declared request receives
|
|
1038
|
-
`keys` as sent.
|
|
1039
|
-
|
|
1040
|
-
After the request succeeds, the hub clears every `lb-select` checkbox of the
|
|
1041
|
-
query and the stamps with them. A failed request leaves the selection.
|
|
1042
|
-
|
|
1043
|
-
Only a checkbox that is showing counts: one parked by `lb-show` is in
|
|
1044
|
-
template content, out of reach. Parking one also clears its tick, so a
|
|
1045
|
-
selection never waits out of sight to return stale.
|
|
1046
|
-
|
|
1047
|
-
Which rows are selected is interaction state, as which row a form edits is.
|
|
1048
|
-
A selecting mode the page keeps in the URL reaches the rows as a column the
|
|
1049
|
-
query computes from the parm, which `lb-show` reads, since `lb-show` reads
|
|
1050
|
-
from the row it sits in.
|
|
1051
|
-
|
|
1052
|
-
#### The request event
|
|
1053
|
-
|
|
1054
|
-
The hub dispatches every request as the bubbling `lb-request` event before
|
|
1055
|
-
sending it, with the request as its `detail`. The event is dispatched from
|
|
1056
|
-
the element that committed.
|
|
1057
|
-
|
|
1058
|
-
A custom element uses the same event to issue a request of its own, with at
|
|
1059
|
-
least `name` in its `detail`. A native element and a custom element
|
|
1060
|
-
therefore produce identical events.
|
|
1061
|
-
|
|
1062
|
-
The hub completes the request on the event's way down, so every ancestor
|
|
1063
|
-
sees it whole on the way up. A request that cannot be issued is stopped
|
|
1064
|
-
there. An ancestor may stop it too: the event is not cancelable, so an
|
|
1065
|
-
interceptor calls `stopPropagation`, not `preventDefault`. This is what
|
|
1066
|
-
makes a confirmation wrapper possible without the wrapped element knowing
|
|
1067
|
-
about it.
|
|
1068
|
-
|
|
1069
|
-
Once what the request brought back has landed, or its round trip has failed,
|
|
1070
|
-
the hub calls `lbRequestDone(detail, origin)` on the committing element and
|
|
1071
|
-
every custom element around it, innermost first (see
|
|
1072
|
-
[Lifecycle](#lifecycle)), then dispatches the bubbling `lb-request-done`
|
|
1073
|
-
event from the same element. Its `detail`, `HubRequestDone` in `@loadbare/app/types`, holds
|
|
1074
|
-
the request as the hub sent it and `items`, every response item that landed
|
|
1075
|
-
because of it, in order. An answer that moved the URL contributes its
|
|
1076
|
-
`lb-url` item and the page load. `error` is set instead when the round trip
|
|
1077
|
-
failed. By then `lb-request-pending` is gone, and a listener sees the page
|
|
1078
|
-
as the answer left it. A request the hub or an ancestor stopped was never
|
|
1079
|
-
sent, and gets no `lb-request-done`. An element the answer removed from the
|
|
1080
|
-
document, such as the row a delete took away, dispatches the event where it
|
|
1081
|
-
now is, and no ancestor it had on the page hears it.
|
|
1082
|
-
|
|
1083
|
-
#### Request state
|
|
1084
|
-
|
|
1085
|
-
The hub stamps `lb-request-pending` on the element that issued a request
|
|
1086
|
-
while its round trip is in flight, together with `aria-busy="true"`, and
|
|
1087
|
-
removes both when it settles. It stamps `lb-request-error` when the round
|
|
1088
|
-
trip fails, and clears it when that element issues its next request.
|
|
1089
|
-
|
|
1090
|
-
A custom element observes them by naming them in `observedAttributes`. On
|
|
1091
|
-
plain HTML a stylesheet is the only consumer: dim a pending button, mark a
|
|
1092
|
-
failed one.
|
|
1093
|
-
|
|
1094
|
-
The hub ignores a commit on an element carrying `lb-request-pending`, so a
|
|
1095
|
-
pending element is disabled in fact and a stylesheet only has to show it.
|
|
1096
|
-
|
|
1097
|
-
#### Refusals
|
|
1098
|
-
|
|
1099
|
-
A handler refuses a request by throwing `refusal()`, exported by
|
|
1100
|
-
`@loadbare/app/server`, with words written for the user: a message for the
|
|
1101
|
-
request as a whole, a message per column, or both.
|
|
1102
|
-
|
|
1103
|
-
```ts
|
|
1104
|
-
throw refusal("That series already has paychecks entered.");
|
|
1105
|
-
throw refusal({ amount: "Must be more than zero" });
|
|
1106
|
-
throw refusal("Two fields need attention.", { amount: "…", pay_date: "…" });
|
|
1107
|
-
```
|
|
1108
|
-
|
|
1109
|
-
It is thrown rather than returned, so it may come from anything the handler
|
|
1110
|
-
calls, and rolls back whatever the application has open as any throw does.
|
|
1111
|
-
`hubRoutes` answers it with status 422 and `{ refusal: { message?, columns? } }`.
|
|
1112
|
-
Anything else thrown is a failure, answered 500 with no body, so no stack
|
|
1113
|
-
or database message reaches the browser.
|
|
1114
|
-
|
|
1115
|
-
The hub shows a refusal where it belongs:
|
|
1116
|
-
|
|
1117
|
-
- It stamps the element that committed `lb-request-refused`, whose value is
|
|
1118
|
-
the whole-request message, empty when there is none. A stylesheet shows
|
|
1119
|
-
it with `attr()`.
|
|
1120
|
-
- For each column, it calls `setCustomValidity(message)` on the control the
|
|
1121
|
-
request gathered that column from, and `reportValidity()` on the first,
|
|
1122
|
-
so the browser shows the message there and moves focus to it. A column
|
|
1123
|
-
the request gathered no such control for is reported to the console.
|
|
1124
|
-
- `lb-request-done` carries the refusal as `refusal`, beside `error`.
|
|
1125
|
-
|
|
1126
|
-
A refusal is not a failure: `lb-request-error` is not stamped and nothing is
|
|
1127
|
-
reported. It stands until the user edits any control the request gathered,
|
|
1128
|
-
a form holding them resets, or the element that committed sends again;
|
|
1129
|
-
then every message it put up is withdrawn at once. While a control carries
|
|
1130
|
-
one, the browser holds its form's submit, as for any invalid control.
|
|
1131
|
-
|
|
1132
|
-
A form-associated custom element takes a column's message when it defines
|
|
1133
|
-
`setCustomValidity` and `reportValidity`; one wrapping a native control
|
|
1134
|
-
passes both to it. A request for several rows takes the whole-request
|
|
1135
|
-
message only.
|
|
1136
|
-
|
|
1137
|
-
#### The round trip
|
|
1138
|
-
|
|
1139
|
-
A request the hub sends has ten seconds to come back. Past that the hub
|
|
1140
|
-
aborts it and treats it as a failure, since `fetch` imposes no deadline of
|
|
1141
|
-
its own. An application cannot change the deadline.
|
|
1142
|
-
|
|
1143
|
-
A round trip fails on a server error, on a network failure, or on that
|
|
1144
|
-
deadline, and all three stamp `lb-request-error` on the element that issued
|
|
1145
|
-
the request — see [Request state](#request-state). A refusal is not a
|
|
1146
|
-
failure — see [Refusals](#refusals).
|
|
1147
|
-
|
|
1148
|
-
After a successful `lb-row-insert` gathered from a form, the hub resets the
|
|
1149
|
-
form, after it lands the response. A failed insert resets nothing, so the
|
|
1150
|
-
entry can be corrected. An `lb-row-update` resets nothing: the row it sent
|
|
1151
|
-
lands back on its controls.
|
|
1152
|
-
|
|
1153
|
-
A reset of any form, whether the hub's, a reset button's or a script's,
|
|
1154
|
-
returns its controls to their defaults, and the hub removes
|
|
1155
|
-
`lb-column-value` from each of them. A cancelled reset keeps both.
|
|
1156
|
-
|
|
1157
|
-
A page load that fails lands nothing and is reported to the console. A load
|
|
1158
|
-
started by a request for `lb-url` stamps the element that issued it, as any
|
|
1159
|
-
request stamps its element.
|
|
1160
|
-
|
|
1161
|
-
A response answers for the URL its request was sent from, path and query
|
|
1162
|
-
string both. One that returns after the URL has moved lands nothing, since
|
|
1163
|
-
it describes what the user is no longer looking at: a request's answer,
|
|
1164
|
-
including any `url()` it carries, and a page load overtaken by a newer one.
|
|
1165
|
-
A write whose answer is dropped this way has still happened. Its
|
|
1166
|
-
`lb-request-done` carries no items and no error, and an insert still resets
|
|
1167
|
-
its form.
|
|
1168
|
-
|
|
1169
|
-
### The URL
|
|
1170
|
-
|
|
1171
|
-
---- UNEDITED ----
|
|
1172
|
-
|
|
1173
|
-
The hub serves the query `lb-url`, of kind `row`, keyed by `lb-path`. It
|
|
1174
|
-
lands wherever an element names it, like any server query.
|
|
1175
|
-
|
|
1176
|
-
| Column | The hub sets it to |
|
|
1177
|
-
| ----------------- | ---------------------------------------------- |
|
|
1178
|
-
| `lb-path` | The path. The row's key |
|
|
1179
|
-
| `lb-page-label` | The page's title, or its stub when it has none |
|
|
1180
|
-
| `lb-page-unknown` | Whether no page's stub matches the path |
|
|
1181
|
-
| any other name | That query parm |
|
|
1182
|
-
|
|
1183
|
-
| Attribute | The hub |
|
|
1184
|
-
| ---------------- | -------------------------------------------------------- |
|
|
1185
|
-
| `lb-url-link` | On a plain primary click, sets `lb-path` from the `href` |
|
|
1186
|
-
| `lb-url-push` | Pushes a history entry for this element's update |
|
|
1187
|
-
| `lb-url-unknown` | Calls `showModal()` when `lb-page-unknown` is `true` |
|
|
1188
|
-
|
|
1189
|
-
- `lb-url-link` goes on an `<a>`.
|
|
1190
|
-
- `lb-url-push` goes on an element with `lb-request`.
|
|
1191
|
-
- `lb-url-unknown` goes on a `<dialog>` inside `<lb-hub>`.
|
|
1192
|
-
|
|
1193
|
-
The hub shows in `<main>` the page whose stub is `lb-path`. `/` is
|
|
1194
|
-
`index`. When `lb-path` takes a new value, the hub loads the page: the
|
|
1195
|
-
server runs its `onPageEnter`, then its queries. When a query parm takes a
|
|
1196
|
-
new value, the hub reloads the page's queries, and keeps the page's DOM, so
|
|
1197
|
-
live rows that come back keep their place.
|
|
1198
|
-
|
|
1199
|
-
Once an entered page's queries have landed, the hub focuses the first
|
|
1200
|
-
element in `<main>` the user can operate: not disabled, not in a closed
|
|
1201
|
-
`<dialog>`, not `inert`, not `hidden`, not in an absent `lb-show` branch,
|
|
1202
|
-
and one that takes the focus. Entering is a cold load, a new `lb-path`,
|
|
1203
|
-
and Back or Forward to another page. A change of query parm enters no
|
|
1204
|
-
page, and focus stays where it is, as it does when it is already in
|
|
1205
|
-
`<main>`.
|
|
1206
|
-
|
|
1207
|
-
The page's title is the text of the page file's `<title>`, which the builder
|
|
1208
|
-
stamps on the page as `lb-page-title`. The hub sets the document title to
|
|
1209
|
-
`lb-page-label` when it is not null.
|
|
1210
|
-
|
|
1211
|
-
```html
|
|
1212
|
-
<header lb-query="lb-url">
|
|
1213
|
-
<h1>Membership Roster</h1>
|
|
1214
|
-
<h2 lb-column="lb-page-label"></h2>
|
|
1215
|
-
</header>
|
|
1216
|
-
```
|
|
1217
|
-
|
|
1218
|
-
#### What a URL names
|
|
1219
|
-
|
|
1220
|
-
The path names a page, a place in the application. It never names a
|
|
1221
|
-
resource, and Loadbare/app does not reproduce REST: `/accounts/42` is not a
|
|
1222
|
-
way to reach account 42.
|
|
1223
|
-
|
|
1224
|
-
The query string describes what that page has on screen:
|
|
1225
|
-
`/accounts?acct=23&from=2026-09-09`. It never commands, and the server
|
|
1226
|
-
remembers nothing, so a URL is a reproducible view. Reload it, bookmark it,
|
|
1227
|
-
or mail it to someone, and what they see is what the sender saw.
|
|
1228
|
-
|
|
1229
|
-
A page declares what it shows, its queries, and what it allows, its
|
|
1230
|
-
requests. Loading a page runs its queries. A request names a query and,
|
|
1231
|
-
where it has one, a key, taken from where the element sits, which is how the
|
|
1232
|
-
database already names a row. Nothing is fetched by URL, so an application
|
|
1233
|
-
designs no endpoints.
|
|
1234
|
-
|
|
1235
|
-
That last is where query parms move Loadbare's position. A query still takes
|
|
1236
|
-
no argument from the browser, and a parm arrives the way the session cookie
|
|
1237
|
-
does, as part of the request the context is built from. But a user who
|
|
1238
|
-
types `?acct=99999` influences what a query returns. A query parm is user
|
|
1239
|
-
input, validated like any other where the application reads it, and a
|
|
1240
|
-
per-session database role means an id outside the caller's reach finds
|
|
1241
|
-
nothing.
|
|
1242
|
-
|
|
1243
|
-
Loadbare is for applications, not sites. Every route is answered with the
|
|
1244
|
-
same document, and a path that names no page is found out in the browser —
|
|
1245
|
-
see [An unknown page](#an-unknown-page).
|
|
1246
|
-
|
|
1247
|
-
#### Links
|
|
1248
|
-
|
|
1249
|
-
An `<a>` carrying `lb-url-link` moves within the application. On a plain
|
|
1250
|
-
primary click — the first button, no modifier key, not already handled — the
|
|
1251
|
-
hub pushes a history entry for the `href`'s path and query string, and loads
|
|
1252
|
-
the page there. Any other click, and any anchor without `lb-url-link`,
|
|
1253
|
-
behaves like any other link, so leaving the application is the default and
|
|
1254
|
-
staying in it is the opt-in.
|
|
1255
|
-
|
|
1256
|
-
```html
|
|
1257
|
-
<nav>
|
|
1258
|
-
<a href="/" lb-url-link>Home</a>
|
|
1259
|
-
<a href="/members" lb-url-link>Members</a>
|
|
1260
|
-
<a href="https://example.com/docs">Docs</a>
|
|
1261
|
-
</nav>
|
|
1262
|
-
```
|
|
1263
|
-
|
|
1264
|
-
The attribute takes no value.
|
|
1265
|
-
|
|
1266
|
-
Path space is flat. A path such as `/members` links to the `members.*` files
|
|
1267
|
-
on the server.
|
|
1268
|
-
|
|
1269
|
-
The query string is kept. `/transactions?date_begin=2026-09-01` opens the
|
|
1270
|
-
transactions page narrowed to those dates.
|
|
1271
|
-
|
|
1272
|
-
The browser's Back and Forward load the page at the URL they arrive at, the
|
|
1273
|
-
same way.
|
|
1274
|
-
|
|
1275
|
-
#### Query parms
|
|
1276
|
-
|
|
1277
|
-
A control that narrows what a page shows updates `lb-url` rather than
|
|
1278
|
-
sending a request. Put it inside an element naming `lb-url`, with
|
|
1279
|
-
`lb-column` naming the parm and `lb-request="lb-row-update"`:
|
|
1280
|
-
|
|
1281
|
-
```html
|
|
1282
|
-
<div lb-query="lb-url">
|
|
1283
|
-
<select lb-column="team" lb-request="lb-row-update">
|
|
1284
|
-
<option value="">Every team</option>
|
|
1285
|
-
<option value="Engines">Engines</option>
|
|
1286
|
-
</select>
|
|
1287
|
-
</div>
|
|
1288
|
-
```
|
|
1289
|
-
|
|
1290
|
-
The hub answers the request itself. It sets the parms the request names in
|
|
1291
|
-
the URL and leaves every other parm as it is, since one may have arrived by
|
|
1292
|
-
link with no control on screen to say it again. An empty value takes the
|
|
1293
|
-
parm out, so a URL is as long as the user has narrowed the page. A request
|
|
1294
|
-
that leaves the URL as it was does nothing. `lb-page-label` and
|
|
1295
|
-
`lb-page-unknown` are the hub's, and a request never sets them.
|
|
1296
|
-
|
|
1297
|
-
The update replaces the current history entry: changing what a page shows is
|
|
1298
|
-
not going anywhere, so Back leaves the page rather than walking back through
|
|
1299
|
-
every choice. `lb-url-push` on the element carrying `lb-request` makes the
|
|
1300
|
-
update push an entry instead.
|
|
1301
|
-
|
|
1302
|
-
When `lb-path` takes a new value, the hub pushes a history entry, and the
|
|
1303
|
-
new URL carries only the query parms the request names.
|
|
1304
|
-
|
|
1305
|
-
Every load lands `lb-url`, and a parm the URL does not carry lands empty on
|
|
1306
|
-
every element naming it as a column, so a control shows what the address
|
|
1307
|
-
bar says after Back as well as after a reload
|
|
1308
|
-
([miswart](./miswarts.md#lb-url-lands-every-parm-it-names)): the hub reads the
|
|
1309
|
-
column names under `lb-query="lb-url"` and names each one in the row it
|
|
1310
|
-
lands.
|
|
1311
|
-
|
|
1312
|
-
The hub sends the query parms with every round trip, a page load and a
|
|
1313
|
-
request alike. The server passes them to `contextFor` — see
|
|
1314
|
-
[The Express server](#the-express-server). Loadbare assigns no parm a
|
|
1315
|
-
meaning: a column of `lb-url` whose name begins with `lb-` is never a query
|
|
1316
|
-
parm. An order or a filter the hub applies to rows already on the page is
|
|
1317
|
-
a [choice](#choices), not a parm.
|
|
1318
|
-
|
|
1319
|
-
#### An unknown page
|
|
1320
|
-
|
|
1321
|
-
When no page's stub matches the path, `lb-page-unknown` is `true` and
|
|
1322
|
-
`lb-page-label` is null. The hub calls `showModal()` on the
|
|
1323
|
-
`<dialog lb-url-unknown>`, where the chrome has one, and reports the path to
|
|
1324
|
-
the console. A chrome with none shows nothing.
|
|
1325
|
-
|
|
1326
|
-
`lb-url` lands before the page is looked up, so the dialog shows it by
|
|
1327
|
-
naming it like any other element:
|
|
1328
|
-
|
|
1329
|
-
```html
|
|
1330
|
-
<dialog lb-url-unknown lb-query="lb-url">
|
|
1331
|
-
The URL <span lb-column="lb-path"></span> is not in this app.
|
|
1332
|
-
</dialog>
|
|
1333
|
-
```
|
|
1334
|
-
|
|
1335
|
-
A path that names no page is detected in the browser, after a successful
|
|
1336
|
-
200: every route gets the same document, so there is no server-delivered
|
|
1337
|
-
404.
|
|
1338
|
-
|
|
1339
|
-
#### When a server response moves the URL
|
|
1340
|
-
|
|
1341
|
-
Some query parms can only be known once a write has run. After an insert,
|
|
1342
|
-
the key of the new row exists only on the server. After a delete, only the
|
|
1343
|
-
server knows that the row the page was showing is gone.
|
|
1344
|
-
|
|
1345
|
-
The usual web answer is Post/Redirect/Get: the server answers the write with
|
|
1346
|
-
a redirect to a URL naming the result, and the browser makes a second request
|
|
1347
|
-
to load it. Loadbare/app does the same work in one round trip.
|
|
1348
|
-
|
|
1349
|
-
A handler returns `url()`, a new row for `lb-url`. The server loads the page
|
|
1350
|
-
at the resulting URL, as a cold load of it would: `onPageEnter`, then every
|
|
1351
|
-
query. The response carries the `lb-url` item followed by that load. The
|
|
1352
|
-
hub writes the URL and then lands the load.
|
|
1353
|
-
|
|
1354
|
-
```ts
|
|
1355
|
-
rowInsert: {
|
|
1356
|
-
run: async (ctx, { values }) => {
|
|
1357
|
-
const id = await ctx.db.addAccount(values);
|
|
1358
|
-
return url({ acct: String(id) });
|
|
1359
|
-
},
|
|
1360
|
-
refresh: [],
|
|
1361
|
-
},
|
|
1362
|
-
```
|
|
1363
|
-
|
|
1364
|
-
- A column other than `lb-path` is a query parm. Parms the row does not
|
|
1365
|
-
name are kept, and an empty value removes its parm.
|
|
1366
|
-
- A row carrying `lb-path` for another page enters that page with only the
|
|
1367
|
-
parms the row names, and pushes a history entry. Otherwise the update
|
|
1368
|
-
replaces the history entry, or pushes one when the element that issued the
|
|
1369
|
-
request carries `lb-url-push`.
|
|
1370
|
-
- The row names at least one column, and every value is a string. Otherwise
|
|
1371
|
-
the server warns, ignores the row, and runs the refresh set as usual.
|
|
1372
|
-
- The refresh set does not run, and anything else the handler returned is
|
|
1373
|
-
dropped. Both were answers for the URL the page is leaving.
|
|
1374
|
-
- If loading the page fails, the write has still happened. The response
|
|
1375
|
-
carries the `lb-url` item alone, and the hub loads the page itself. A
|
|
1376
|
-
failure there is a page load's, and is not stamped on the element that
|
|
1377
|
-
issued the request.
|
|
1378
|
-
- A user who has moved by the time the response arrives, to another page or
|
|
1379
|
-
to other query parms, keeps the URL they are on.
|
|
1380
|
-
|
|
1381
|
-
`onPageEnter` may return `url()` the same way, to move the page before
|
|
1382
|
-
anything shows, as an application restoring the parms the user last had
|
|
1383
|
-
does. The page loads at the new URL in the same round trip, and
|
|
1384
|
-
that load does not follow `onPageEnter` again. A `url()` leading to the
|
|
1385
|
-
page and parms already in hand is ignored, so the page loads once. When to
|
|
1386
|
-
restore is the application's rule, such as only when the URL carries no
|
|
1387
|
-
parms of its own.
|
|
1388
|
-
|
|
1389
|
-
```ts
|
|
1390
|
-
// The application put the parms on ctx in contextFor.
|
|
1391
|
-
onPageEnter: async (ctx) => {
|
|
1392
|
-
if (ctx.parms.size > 0) return;
|
|
1393
|
-
const saved = await ctx.db.savedView("accounts");
|
|
1394
|
-
if (saved) return url(saved);
|
|
1395
|
-
},
|
|
1396
|
-
```
|
|
1397
|
-
|
|
1398
|
-
Saving what the user has on screen is a declared request with
|
|
1399
|
-
`refresh: []`, whose handler each page passes to a routine the
|
|
1400
|
-
application's pages share. A custom element naming `lb-url` sees every
|
|
1401
|
-
change of the URL and issues it.
|
|
1402
|
-
|
|
1403
|
-
The page is loaded with a second context, built by `contextFor` from the new
|
|
1404
|
-
parms — see [The Express server](#the-express-server).
|
|
1405
|
-
|
|
1406
|
-
### Choices
|
|
1407
|
-
|
|
1408
|
-
The hub serves the query `lb-choices`, of kind `row`, keyed by `lb-path`,
|
|
1409
|
-
with a column for each choice the user made for how rows show: an
|
|
1410
|
-
[order](#order) or a [filter](#filters). The application names the
|
|
1411
|
-
columns. It lands wherever an element names it, like any server query.
|
|
1412
|
-
|
|
1413
|
-
A control inside `lb-query="lb-choices"` that sends `lb-row-update` makes a
|
|
1414
|
-
choice. A request with `lb-row-update` for `lb-choices` or `lb-url` from
|
|
1415
|
-
anywhere on the page is keyed by the path, so a custom element may
|
|
1416
|
-
dispatch one without sitting inside the query.
|
|
1417
|
-
|
|
1418
|
-
A choice joins the ones already made, and needs no round trip to show:
|
|
1419
|
-
every element whose `lb-order` or `lb-filter` reads a column that changed
|
|
1420
|
-
lands its query's last answer again, so it shows what a round trip carrying
|
|
1421
|
-
the choice would. Then the hub sends `lb-row-update` for `lb-choices`,
|
|
1422
|
-
keyed by the path, with every choice made on the page as `values`, and no
|
|
1423
|
-
element pending. The server runs the page's `onChoices` with them, when it
|
|
1424
|
-
has one, and otherwise answers nothing. Whatever `onChoices` returns lands,
|
|
1425
|
-
and by convention it returns nothing.
|
|
1426
|
-
|
|
1427
|
-
Choices hold until the page changes, and a change of query parm keeps them.
|
|
1428
|
-
On entering a page, every column named under `lb-query="lb-choices"` lands
|
|
1429
|
-
empty. `onPageEnter` restores them by returning `choices()`, alone or
|
|
1430
|
-
spread beside `url()`:
|
|
1431
|
-
|
|
1432
|
-
```ts
|
|
1433
|
-
onPageEnter: async (ctx) => {
|
|
1434
|
-
const saved = await ctx.db.savedChoices("accounts");
|
|
1435
|
-
if (saved) return choices(saved);
|
|
1436
|
-
},
|
|
1437
|
-
onChoices: (ctx, chosen) => ctx.db.saveChoices("accounts", chosen),
|
|
1438
|
-
```
|
|
1439
|
-
|
|
1440
|
-
The page load then answers the `lb-choices` item first, then the queries.
|
|
1441
|
-
When `url()` moves the page, the choices arrive with the load at the new
|
|
1442
|
-
URL. A
|
|
1443
|
-
`choices()` naming no column, or holding a value that is not a string, is
|
|
1444
|
-
reported on the server console and ignored, as for `url()`.
|
|
1445
|
-
|
|
1446
|
-
### The markup checks
|
|
1447
|
-
|
|
1448
|
-
The builder checks the chrome and every page, after expansion, and refuses
|
|
1449
|
-
to build on any of these:
|
|
1450
|
-
|
|
1451
|
-
- An `lb-` attribute that is not a developer attribute: `lb-query`,
|
|
1452
|
-
`lb-column`, `lb-show`, `lb-group`, `lb-count`, `lb-sum`, `lb-avg`,
|
|
1453
|
-
`lb-min`, `lb-max`, `lb-order`, `lb-filter`, `lb-request`, `lb-select`,
|
|
1454
|
-
`lb-url-link`, `lb-url-push`, `lb-url-unknown`. Every other `lb-` attribute is a stamp.
|
|
1455
|
-
`lb-exp-slot` and `lb-exp-template` are consumed by expansion and never
|
|
1456
|
-
reach the check.
|
|
1457
|
-
- `lb-request` naming a value that begins with `lb-` and is not one of the
|
|
1458
|
-
three request names.
|
|
1459
|
-
- `lb-query` naming a query the page does not declare. `lb-url` and
|
|
1460
|
-
`lb-choices` are the hub's, and every page and the chrome may name them.
|
|
1461
|
-
The chrome belongs to no page, and names no other query.
|
|
1462
|
-
- `lb-request` naming a request the page does not declare under `handlers`.
|
|
1463
|
-
The chrome sends none of them.
|
|
1464
|
-
- One of the three request names for a query whose `crud` entry does not
|
|
1465
|
-
declare it, and any but `lb-row-update` for `lb-url` or `lb-choices`. The
|
|
1466
|
-
query is a form's own when the form carries `lb-query`, and otherwise the
|
|
1467
|
-
nearest `lb-query` around the element.
|
|
1468
|
-
- `lb-url-link` on anything but an `<a>`.
|
|
1469
|
-
- `lb-url-push` on an element with no `lb-request`.
|
|
1470
|
-
- `lb-url-unknown` on anything but a `<dialog>`, and in the chrome, outside
|
|
1471
|
-
`<lb-hub>`.
|
|
1472
|
-
- `lb-show` on a `<template>`, on a row template's root, or with no
|
|
1473
|
-
`lb-query` around it.
|
|
1474
|
-
- `lb-show="!"` or `lb-show="!!column"`, which reverse no column.
|
|
1475
|
-
- `lb-order` or `lb-filter` on an element not carrying `lb-query`, or naming
|
|
1476
|
-
no column.
|
|
1477
|
-
- `lb-select` on anything but an `<input type="checkbox">`, outside a row
|
|
1478
|
-
template, or beside `lb-column` or `lb-request`.
|
|
1479
|
-
- A `command` beginning `--lb-` that is not one of the three
|
|
1480
|
-
[commands](#a-form-pointed-at-a-row), and one of them with no
|
|
1481
|
-
`commandfor`.
|
|
1482
|
-
- `lb-group` on anything but a `<template>`, or on one holding no
|
|
1483
|
-
`<template>` for its contents.
|
|
1484
|
-
- An aggregate on a `<template>`, and any but `lb-count` naming no column.
|
|
1485
|
-
- A chrome without exactly one `<lb-hub>` and exactly one `<main>`, a
|
|
1486
|
-
`<main>` outside `<lb-hub>`, or a `<main>` with anything in it but
|
|
1487
|
-
whitespace.
|
|
1488
|
-
- A page carrying an `<lb-hub>` or a `<main>`.
|
|
1489
|
-
|
|
1490
|
-
The builder also reads the script of every custom element it ships, other
|
|
1491
|
-
than `<lb-hub>`, and refuses one that declares a method, accessor or field
|
|
1492
|
-
whose name begins with `lb` and a capital and is not in
|
|
1493
|
-
[Reserved methods](#reserved-methods).
|
|
1494
|
-
|
|
1495
|
-
`lb-column` with no ancestor row is allowed: the hub gathers from it.
|
|
1496
|
-
|
|
1497
|
-
To know what each page declares, the builder loads the page's
|
|
1498
|
-
`.queries.ts` and `.requests.ts` modules, so their import-time code runs
|
|
1499
|
-
during the build, as it does when the server starts.
|
|
1500
|
-
|
|
1501
|
-
Script never assigns an `lb-` attribute, so the markup states every one
|
|
1502
|
-
that exists, and checking the markup is sound.
|
|
1503
|
-
|
|
1504
|
-
### Uncommon layouts
|
|
1505
|
-
|
|
1506
|
-
The hub supports these layouts and adds no check to refuse them. They are
|
|
1507
|
-
uncommon, and Loadbare/app does not shape itself around them.
|
|
1508
|
-
|
|
1509
|
-
- **A radio button in a row template.** The browser groups radio buttons
|
|
1510
|
-
by `name`, and a row template repeats one `name` in every live row. The
|
|
1511
|
-
hub stamps each radio button's `name` with the `name` written, or its
|
|
1512
|
-
column when none is, followed by `-` and the live row's key, so each live
|
|
1513
|
-
row holds a group of its own
|
|
1514
|
-
([miswart](./miswarts.md#radio-button-names-in-a-row-template)).
|
|
1515
|
-
- **Several rows not yet entered, each holding radio buttons.** A
|
|
1516
|
-
[row not yet entered](#a-row-not-yet-entered) has an empty key, so the
|
|
1517
|
-
radio buttons of every such row share one `name` and form one group.
|
|
1518
|
-
- **Radio buttons populated from a query.** A radio button in a query's row
|
|
1519
|
-
template answers to that query's row, as any element there does, so which
|
|
1520
|
-
row and column it lands from and gathers to follows from where the layout
|
|
1521
|
-
puts it. The result is the same as in any framework that populates radio
|
|
1522
|
-
buttons from a query. A choice among a query's rows is better made with a
|
|
1523
|
-
form-associated control, such as `lb-options` in `@loadbare/widgets`.
|
|
1524
|
-
|
|
1525
|
-
## Application files
|
|
1526
|
-
|
|
1527
|
-
### Chrome
|
|
1528
|
-
|
|
1529
|
-
The Loadbare/app builder always requires exactly one file
|
|
1530
|
-
named `chrome.html`, located anywhere in builder `--src`.
|
|
1531
|
-
|
|
1532
|
-
The chrome is the application's single HTML document. This is
|
|
1533
|
-
where the standard landmarks go: header, nav, footer, main.
|
|
1534
|
-
|
|
1535
|
-
The file is an HTML document, which must contain:
|
|
1536
|
-
- `<lb-hub>` inside `<body>`. The hub can only act on its children,
|
|
1537
|
-
that is why it is usually nested right below `<body>`.
|
|
1538
|
-
- One empty `<main>` inside `<lb-hub>`. The builder refuses a chrome
|
|
1539
|
-
without exactly one of each, and a page carrying either.
|
|
1540
|
-
- The script tag for `/client.js`, the javascript bundle.
|
|
1541
|
-
|
|
1542
|
-
It may also contain:
|
|
1543
|
-
- `/app.css`, the stylesheet bundle
|
|
1544
|
-
- A `<dialog lb-url-unknown>`, which the hub opens when the path names no
|
|
1545
|
-
page. It goes inside `<lb-hub>`, like everything the hub acts on; the
|
|
1546
|
-
builder rejects one placed elsewhere.
|
|
1547
|
-
- The `hidden` attribute on `<body>`, which the hub removes once the first page has landed.
|
|
1548
|
-
|
|
1549
|
-
```html
|
|
1550
|
-
<!-- src/chrome.html -->
|
|
1551
|
-
<!doctype html>
|
|
1552
|
-
<html lang="en">
|
|
1553
|
-
<head>
|
|
1554
|
-
<meta charset="utf-8" />
|
|
1555
|
-
<title>Membership Roster</title>
|
|
1556
|
-
<script src="/client.js" defer></script>
|
|
1557
|
-
<link rel="stylesheet" href="/app.css" />
|
|
1558
|
-
</head>
|
|
1559
|
-
<body hidden>
|
|
1560
|
-
<lb-hub>
|
|
1561
|
-
<header lb-query="lb-url">
|
|
1562
|
-
<h1>Membership Roster</h1>
|
|
1563
|
-
<h2 lb-column="lb-page-label"></h2>
|
|
1564
|
-
</header>
|
|
1565
|
-
<nav>
|
|
1566
|
-
<a href="/" lb-url-link>Home</a>
|
|
1567
|
-
<a href="/members" lb-url-link>Members</a>
|
|
1568
|
-
<a href="https://example.org/">Our website</a>
|
|
1569
|
-
</nav>
|
|
1570
|
-
<main></main>
|
|
1571
|
-
<dialog lb-url-unknown lb-query="lb-url">
|
|
1572
|
-
The URL <span lb-column="lb-path"></span> is not in this app.
|
|
1573
|
-
</dialog>
|
|
1574
|
-
</lb-hub>
|
|
1575
|
-
</body>
|
|
1576
|
-
</html>
|
|
1577
|
-
```
|
|
1578
|
-
|
|
1579
|
-
The chrome's `<title>` shows until the first page lands, and each page's
|
|
1580
|
-
title replaces it — see [The URL](#the-url).
|
|
1581
|
-
|
|
1582
|
-
### Pages
|
|
1583
|
-
|
|
1584
|
-
The page namespace is flat. Loadbare/app does not care where in the `--src`
|
|
1585
|
-
the page files are located, but they must be unique across the application.
|
|
1586
|
-
A page `/deep/path/to/mypage.page.html` is routed to `/mypage`.
|
|
1587
|
-
|
|
1588
|
-
Pages are grouped as
|
|
1589
|
-
- `<stub>.page.html` is recognized as navigable and capable of
|
|
1590
|
-
having associated queries and requests
|
|
1591
|
-
- `<stub>.queries.ts` are the queries for a page
|
|
1592
|
-
- `<stub>.requests.ts` respond to the requests from the browser
|
|
1593
|
-
|
|
1594
|
-
The stub of a page must match the path used in links,
|
|
1595
|
-
so that `<a href="/members" lb-url-link>Members</a>` has matching
|
|
1596
|
-
files `members.page.html` et al.
|
|
1597
|
-
|
|
1598
|
-
#### Page HTML
|
|
1599
|
-
|
|
1600
|
-
The markup in `<stub>.page.html` is the same HTML as everywhere else
|
|
1601
|
-
in a Loadbare/app application, see [HTML](#html).
|
|
1602
|
-
|
|
1603
|
-
A page file may carry a `<title>`. The builder removes it and stamps its
|
|
1604
|
-
text content as `lb-page-title` on the page, and the hub shows it as
|
|
1605
|
-
`lb-page-label`.
|
|
1606
|
-
|
|
1607
|
-
```html
|
|
1608
|
-
<!-- src/pages/members.page.html -->
|
|
1609
|
-
<title>Members</title>
|
|
1610
|
-
<h1>Members</h1>
|
|
1611
|
-
```
|
|
1612
|
-
|
|
1613
|
-
The builder wraps each expanded page in
|
|
1614
|
-
`<template lb-page="<stub>" lb-page-title="...">` and puts it in the built
|
|
1615
|
-
document. The hub reads `lb-page` to find the page a path names.
|
|
1616
|
-
|
|
1617
|
-
| Stamp | The builder stamps it with |
|
|
1618
|
-
| --------------- | --------------------------------------------- |
|
|
1619
|
-
| `lb-page` | The page's stub, which the hub reads |
|
|
1620
|
-
| `lb-page-title` | The text content of the page file's `<title>` |
|
|
1621
|
-
|
|
1622
|
-
## The server
|
|
1623
|
-
|
|
1624
|
-
A page's server half is two files, `<stub>.queries.ts` and
|
|
1625
|
-
`<stub>.requests.ts`. Each pairs with `<stub>.page.html` by sharing its
|
|
1626
|
-
stub, as stated in [Pages](#pages). Either may be absent, and one with no
|
|
1627
|
-
matching page is a build error.
|
|
1628
|
-
|
|
1629
|
-
### The Express server
|
|
1630
|
-
|
|
1631
|
-
Loadbare/app ships no server. The application writes an ordinary Express
|
|
1632
|
-
app. Express is a peer dependency, installed by the application.
|
|
1633
|
-
|
|
1634
|
-
The builder's artifacts oblige that server to handle four things: the
|
|
1635
|
-
client script, the stylesheet, the hub's data channel, and the one HTML
|
|
1636
|
-
document.
|
|
1637
|
-
|
|
1638
|
-
| Handle | With |
|
|
1639
|
-
| ---------------------------- | ------------------- |
|
|
1640
|
-
| `/client.js` | `dist/client.js` |
|
|
1641
|
-
| `/app.css` | `dist/app.css` |
|
|
1642
|
-
| `hubRoutes(hub, contextFor)` | The hub's own route |
|
|
1643
|
-
| Every other GET | `dist/app.html` |
|
|
1644
|
-
|
|
1645
|
-
The builder writes `pages.ts` into `--out` whenever any page has a
|
|
1646
|
-
`.queries.ts` or a `.requests.ts` file. It imports each of them, calls
|
|
1647
|
-
`createHub()` once over the lot, and exports the result as `hub`. The
|
|
1648
|
-
server imports that rather than maintaining the registry by hand.
|
|
1649
|
-
`createHub` is the only implementation of the `Hub` interface, which may
|
|
1650
|
-
gain members in any release; an application never implements it.
|
|
1651
|
-
|
|
1652
|
-
```ts
|
|
1653
|
-
// server.ts
|
|
1654
|
-
import path from "node:path";
|
|
1655
|
-
import { readFileSync } from "node:fs";
|
|
1656
|
-
import express, { type Request } from "express";
|
|
1657
|
-
import { hubRoutes } from "@loadbare/app/express";
|
|
1658
|
-
import type { HubContext } from "@loadbare/app/server";
|
|
1659
|
-
import { hub } from "./dist/pages";
|
|
1660
|
-
import { openDb } from "./src/database";
|
|
1661
|
-
|
|
1662
|
-
const DIST = path.resolve("dist");
|
|
1663
|
-
const app = express();
|
|
1664
|
-
|
|
1665
|
-
// All data requests are handled by the hub
|
|
1666
|
-
// This example shows an application-specific openDB()
|
|
1667
|
-
function contextFor(_req: Request): HubContext {
|
|
1668
|
-
return { db: openDb() };
|
|
1669
|
-
}
|
|
1670
|
-
app.use(hubRoutes(hub, contextFor));
|
|
1671
|
-
|
|
1672
|
-
// Static assets, then the catch-all for app.html
|
|
1673
|
-
app.get("/client.js", (_req, res) => res.sendFile(path.join(DIST, "client.js")));
|
|
1674
|
-
app.get("/app.css", (_req, res) => res.sendFile(path.join(DIST, "app.css")));
|
|
1675
|
-
app.get(/.*/, (_req, res) =>
|
|
1676
|
-
res.type("html").send(readFileSync(path.join(DIST, "app.html"), "utf-8")),
|
|
1677
|
-
);
|
|
1678
|
-
|
|
1679
|
-
app.listen(8787);
|
|
1680
|
-
```
|
|
1681
|
-
|
|
1682
|
-
Explaining Express is beyond the scope of this technical reference. The
|
|
1683
|
-
only real requirement is that the catch-all for app.html is at the end,
|
|
1684
|
-
so it does not catch any other files.
|
|
1685
|
-
|
|
1686
|
-
`hubRoutes` mounts `express.json()` on its own routes, and takes that
|
|
1687
|
-
function's options under `json` in an optional third argument. The default
|
|
1688
|
-
limit of 100 KB refuses an oversized body with a 413 before any handler
|
|
1689
|
-
runs, and the server log names the option. A page that sends a file's text
|
|
1690
|
-
as a value needs more:
|
|
1691
|
-
|
|
1692
|
-
```ts
|
|
1693
|
-
app.use(hubRoutes(hub, contextFor, { json: { limit: "5mb" } }));
|
|
1694
|
-
```
|
|
1695
|
-
|
|
1696
|
-
`hubRoutes` answers `POST /lb/<stub>`, with the query string the browser is
|
|
1697
|
-
showing after it, verbatim. It hands `contextFor` that query string's parms
|
|
1698
|
-
as a second argument, and `contextFor` puts on the context whatever a query
|
|
1699
|
-
reads from them. They are user input:
|
|
1700
|
-
|
|
1701
|
-
```ts
|
|
1702
|
-
function contextFor(req: Request, parms: URLSearchParams): HubContext {
|
|
1703
|
-
return { db: openDb(), acct: parms.get("acct") ?? "" };
|
|
1704
|
-
}
|
|
1705
|
-
```
|
|
1706
|
-
|
|
1707
|
-
Read the parms from that argument, not from `req.query`. After a request
|
|
1708
|
-
whose handler returned `url()`, `contextFor` is called a second time for
|
|
1709
|
-
the same request, with the new parms, to load the page at them. So it must
|
|
1710
|
-
be safe to call twice, and a write must be visible to the second context by
|
|
1711
|
-
the time its `run` returns. A handle opened per request without a
|
|
1712
|
-
transaction around it is both.
|
|
1713
|
-
|
|
1714
|
-
Give the server the origin root. The hub reaches its own endpoints by
|
|
1715
|
-
absolute path, so an application cannot be hosted under a subpath such as
|
|
1716
|
-
`example.com/myapp/`, and anything proxying in front of the server passes
|
|
1717
|
-
the whole path space through unchanged.
|
|
1718
|
-
|
|
1719
|
-
### Page Queries
|
|
1720
|
-
|
|
1721
|
-
`<stub>.queries.ts` exports one object named `queries`, typed `Queries`.
|
|
1722
|
-
Each key is a query name, which the markup names with `lb-query`.
|
|
1723
|
-
|
|
1724
|
-
A query's kind and key belong to its name. Declare each query with
|
|
1725
|
-
`row(key, run)` or `rows(key, run, options)`, naming the key column first.
|
|
1726
|
-
A `rows` query's `options` declare its [order](#order). A query
|
|
1727
|
-
cannot be built without one of these functions. Every query has a key; an
|
|
1728
|
-
aggregate row answers with a constant one.
|
|
1729
|
-
|
|
1730
|
-
```ts
|
|
1731
|
-
// members.queries.ts
|
|
1732
|
-
import { row, rows, type Queries } from "@loadbare/app/server";
|
|
1733
|
-
|
|
1734
|
-
export const queries: Queries = {
|
|
1735
|
-
roster: rows("id", (ctx) => ctx.db.members()),
|
|
1736
|
-
summary: row("id", async (ctx) => ({
|
|
1737
|
-
id: "all",
|
|
1738
|
-
count: String(await ctx.db.memberCount()),
|
|
1739
|
-
})),
|
|
1740
|
-
};
|
|
1741
|
-
```
|
|
1742
|
-
|
|
1743
|
-
A `row` query returns one object, or `null` when it finds no row, and a
|
|
1744
|
-
`rows` query returns an array of objects, which the page shows in that order when the query declares none. An answer that disagrees with
|
|
1745
|
-
its declared kind is left out of the response, with a warning naming the
|
|
1746
|
-
query.
|
|
1747
|
-
|
|
1748
|
-
Query names cannot begin with `lb-`, that namespace is reserved for
|
|
1749
|
-
Loadbare/app queries the hub serves in the browser, such as
|
|
1750
|
-
[`lb-url`](#the-url). `createHub` refuses at startup a page that declares
|
|
1751
|
-
such a name as a query, a handler or a `crud` entry, and a query whose order
|
|
1752
|
-
has a term naming no column.
|
|
1753
|
-
|
|
1754
|
-
Every query and every handler receives `ctx`, the application's own request
|
|
1755
|
-
context. The application builds it once per request and hands it to the hub,
|
|
1756
|
-
see [The Express server](#the-express-server). Loadbare/app declares it empty
|
|
1757
|
-
and never reads it, so a query finds exactly what the application put there,
|
|
1758
|
-
which is usually a database handle opened for the authenticated caller.
|
|
1759
|
-
|
|
1760
|
-
Queries should not return deltas, deltas are handled in the `.requests.ts` file
|
|
1761
|
-
as explained in the next section.
|
|
1762
|
-
|
|
1763
|
-
### Page Requests
|
|
1764
|
-
|
|
1765
|
-
`<stub>.requests.ts` exports one object named `requests`, typed `Requests`.
|
|
1766
|
-
It has four optional keys.
|
|
1767
|
-
|
|
1768
|
-
| Key | Keyed by | Answers |
|
|
1769
|
-
| ------------- | --------------------- | -------------------------------------------------- |
|
|
1770
|
-
| `handlers` | The request name | The page's declared requests |
|
|
1771
|
-
| `crud` | A query name | The three requests Loadbare provides |
|
|
1772
|
-
| `onPageEnter` | Nothing | Runs once on entering the page, before its queries, and may move the URL or restore [choices](#choices) |
|
|
1773
|
-
| `onChoices` | Nothing | Runs with every choice made on the page, each time the user makes one |
|
|
1774
|
-
|
|
1775
|
-
The server resolves a declared request against `handlers`, and a request
|
|
1776
|
-
Loadbare provides against `crud` under the request's query. The builder
|
|
1777
|
-
refuses markup naming a request the page does not declare; see
|
|
1778
|
-
[The markup checks](#the-markup-checks). A name with no entry there can
|
|
1779
|
-
still arrive, from a widget's script or from outside the page, and it
|
|
1780
|
-
logs a server console warning naming the page and the missing entry, and
|
|
1781
|
-
answers 200 with no response items, so nothing lands and
|
|
1782
|
-
`lb-request-pending` clears from the element that issued the request. A
|
|
1783
|
-
request carrying no name answers 400, and a `run` that throws answers 500.
|
|
1784
|
-
|
|
1785
|
-
Every handler under `handlers` and `crud` has the same two members. `run`
|
|
1786
|
-
performs the work, and `refresh` names the queries to re-run once it has.
|
|
1787
|
-
A query belongs there when its answer changed, never to fill an element the
|
|
1788
|
-
request's answer creates: that element is filled from the last answer its
|
|
1789
|
-
query landed.
|
|
1790
|
-
|
|
1791
|
-
`run` receives the same `ctx` and the request less its name: `query`, `key`
|
|
1792
|
-
and `values`, as present.
|
|
1793
|
-
|
|
1794
|
-
`run` may also return answers of its own, by query name, which are laid over
|
|
1795
|
-
the refreshed ones. That is how a delta reaches the browser: wrap it in
|
|
1796
|
-
`patch()`, naming the rows that arrived or changed and the keys that went.
|
|
1797
|
-
|
|
1798
|
-
A refreshed `rows` query sends every row to say what changed. An update or
|
|
1799
|
-
a delete names its row by key, and an insert has the new row's key once it
|
|
1800
|
-
has written it, so each handler knows what changed and answers with a
|
|
1801
|
-
patch, which the hub places by the query's [order](#order): `createHub`
|
|
1802
|
-
refuses at startup a `crud` `rowInsert`, `rowUpdate` or `rowDelete` on a
|
|
1803
|
-
`rows` query whose `refresh` names that same query. A `row` query sends one
|
|
1804
|
-
row and may refresh itself.
|
|
1805
|
-
|
|
1806
|
-
`run` may instead return `url()`, a new row for `lb-url`, naming query parms
|
|
1807
|
-
only the write can know, such as the key of a row it inserted. The page
|
|
1808
|
-
then loads at them in the same round trip, in place of the refresh set — see
|
|
1809
|
-
[When a server response moves the URL](#when-a-server-response-moves-the-url).
|
|
1810
|
-
A `rowDelete` that removes the row on screen returns `url({ acct: "" })`
|
|
1811
|
-
and leaves any other delete to its refresh set.
|
|
1812
|
-
|
|
1813
|
-
```ts
|
|
1814
|
-
// members.requests.ts
|
|
1815
|
-
import { patch, type Requests } from "@loadbare/app/server";
|
|
1816
|
-
|
|
1817
|
-
export const requests: Requests = {
|
|
1818
|
-
handlers: {
|
|
1819
|
-
resetRoster: {
|
|
1820
|
-
run: (ctx) => ctx.db.resetMembers(),
|
|
1821
|
-
refresh: ["roster"],
|
|
1822
|
-
},
|
|
1823
|
-
},
|
|
1824
|
-
crud: {
|
|
1825
|
-
roster: {
|
|
1826
|
-
rowDelete: {
|
|
1827
|
-
run: async (ctx, { key }) => {
|
|
1828
|
-
await ctx.db.deleteMember(key);
|
|
1829
|
-
return { roster: patch({ drop: [key] }) };
|
|
1830
|
-
},
|
|
1831
|
-
refresh: [],
|
|
1832
|
-
},
|
|
1833
|
-
},
|
|
1834
|
-
},
|
|
1835
|
-
};
|
|
1836
|
-
```
|
|
1837
|
-
|
|
1838
|
-
Each key under a `crud` entry is a request name with its prefix stripped and
|
|
1839
|
-
the rest camel-cased, so the attribute, the wire and this key are one
|
|
1840
|
-
vocabulary. A query with no `crud` entry permits none of them.
|
|
1841
|
-
|
|
1842
|
-
| Request name | Key under `crud` | `run` receives |
|
|
1843
|
-
| --------------- | ---------------- | --------------- |
|
|
1844
|
-
| `lb-row-insert` | `rowInsert` | `values` |
|
|
1845
|
-
| `lb-row-update` | `rowUpdate` | `key`, `values` |
|
|
1846
|
-
| `lb-row-delete` | `rowDelete` | `key` |
|
|
1847
|
-
|
|
1848
|
-
A `rowUpdate` sets the columns `values` names and leaves the rest as they
|
|
1849
|
-
are, as an SQL `UPDATE` does. A form sends the controls it holds and a
|
|
1850
|
-
control carrying `lb-request` sends itself, so one handler answers both.
|
|
1851
|
-
|
|
1852
|
-
### The wire
|
|
1853
|
-
|
|
1854
|
-
---- UNEDITED ----
|
|
1855
|
-
|
|
1856
|
-
One path, one method. The hub sends every round trip as
|
|
1857
|
-
`POST /lb/<stub>?<query string>`, with the query string the browser is
|
|
1858
|
-
showing. An empty body asks for the page load. Any other body is a
|
|
1859
|
-
request:
|
|
1860
|
-
|
|
1861
|
-
```ts
|
|
1862
|
-
interface HubRequest {
|
|
1863
|
-
name: string;
|
|
1864
|
-
query?: string;
|
|
1865
|
-
key?: string;
|
|
1866
|
-
keys?: string[];
|
|
1867
|
-
values?: Record<string, string>;
|
|
1868
|
-
rows?: Record<string, { key: string; values: Record<string, string> }[]>;
|
|
1869
|
-
}
|
|
1870
|
-
```
|
|
1871
|
-
|
|
1872
|
-
A refused request is answered with status 422 and
|
|
1873
|
-
`{ refusal: { message?, columns? } }`, and any other failure with 500 and
|
|
1874
|
-
no body — see [Refusals](#refusals). Every other response is an array of
|
|
1875
|
-
response items:
|
|
1876
|
-
|
|
1877
|
-
```ts
|
|
1878
|
-
interface ResponseItem {
|
|
1879
|
-
query: string;
|
|
1880
|
-
kind: "row" | "rows";
|
|
1881
|
-
key: string;
|
|
1882
|
-
row?: Row;
|
|
1883
|
-
rows?: Row[];
|
|
1884
|
-
patch?: { rows?: Row[]; drop?: unknown[] };
|
|
1885
|
-
}
|
|
1886
|
-
```
|
|
1887
|
-
|
|
1888
|
-
A response item carries exactly one of `row`, `rows` or `patch`. A patch
|
|
1889
|
-
answers a `rows` query only: rows named in `rows` are added or updated, keys
|
|
1890
|
-
in `drop` are removed, and every other row is left alone.
|
|
1891
|
-
|
|
1892
|
-
A `rows` item also carries `order` when the query declares one.
|
|
1893
|
-
|
|
1894
|
-
A choice is sent as `lb-row-update` for `lb-choices`, keyed by the path,
|
|
1895
|
-
with every choice made on the page as `values`; see [Choices](#choices).
|
|
1896
|
-
|
|
1897
|
-
`createHub(pages)` builds the `Hub`, which has two calls, both answering
|
|
1898
|
-
with response items: `dataForPage(page, ctx, options)` for a page load, and
|
|
1899
|
-
`runRequest(page, request, ctx)` for a request. `options.parms` are the
|
|
1900
|
-
parms the page is loaded at, and `options.follow` false ignores a `url()`
|
|
1901
|
-
from `onPageEnter`; `hubRoutes` passes both.
|
|
1902
|
-
|
|
1903
|
-
## Custom elements
|
|
1904
|
-
|
|
1905
|
-
---- UNEDITED ----
|
|
1906
|
-
|
|
1907
|
-
A custom element is an element defined by `customElements.define`, and
|
|
1908
|
-
follows these fixed rules in a Loadbare/app application:
|
|
1909
|
-
- Its name contains a hyphen, as HTML requires, like `<my-custom-element>`.
|
|
1910
|
-
- Its element file, if present, is `my-custom-element.html`.
|
|
1911
|
-
- Its script, if present, is `my-custom-element.browser.ts`. The
|
|
1912
|
-
`browser` segment is a safety feature, requiring the file to be
|
|
1913
|
-
explicitly named as a browser file, to help prevent unfortunate
|
|
1914
|
-
naming collisions where a server file happens to have the name of
|
|
1915
|
-
a custom element and gets built into the browser bundle.
|
|
1916
|
-
|
|
1917
|
-
A custom element must have one or the other of an element file and a
|
|
1918
|
-
script, and it may have both. If it has neither, the builder reports an
|
|
1919
|
-
error.
|
|
1920
|
-
|
|
1921
|
-
To use custom elements from a widget library, add the library to
|
|
1922
|
-
`imports.ts` anywhere in the builder's `--src`:
|
|
1923
|
-
|
|
1924
|
-
```ts
|
|
1925
|
-
export default ["@scope/library-name"];
|
|
1926
|
-
```
|
|
1927
|
-
|
|
1928
|
-
Import every attribute name from `@loadbare/app/constants` — see
|
|
1929
|
-
[Constants](#constants). Never write one as a string literal.
|
|
1930
|
-
|
|
1931
|
-
### What the hub and an element say to each other
|
|
1932
|
-
|
|
1933
|
-
Each direction has one mechanism for each kind of message:
|
|
1934
|
-
|
|
1935
|
-
| Direction | What | How | Today |
|
|
1936
|
-
| -------------- | ------------------------------------------- | ---------------------------- | ------------------------------------------------ |
|
|
1937
|
-
| Hub to element | State that lasts | An attribute the hub stamps | `lb-column-value`, `lb-key-value`, `lb-query-row-count`, `lb-group-*`, `lb-row-*`, `lb-request-pending`, `lb-request-error` |
|
|
1938
|
-
| Hub to element | State the platform already names | A property | A control's `value` |
|
|
1939
|
-
| Hub to element | What the hub did | An optional `lb` method | `lbAttached`, `lbRowsLanded`, `lbRequestDone`, `lbDetached` |
|
|
1940
|
-
| Hub to element | A moment an element started | A bubbling event | `lb-request-done` |
|
|
1941
|
-
| Element to hub | A moment | A bubbling event | `lb-request`, and `change`, `click` and `submit` |
|
|
1942
|
-
|
|
1943
|
-
State is an attribute, because a stylesheet can select on it. A method is
|
|
1944
|
-
how the hub tells one element what it just did to that element. A moment is an event, because the element that cares is
|
|
1945
|
-
often an ancestor of the one it concerns, and an event reaches it with no
|
|
1946
|
-
knowledge of either.
|
|
1947
|
-
|
|
1948
|
-
### Controls
|
|
1949
|
-
|
|
1950
|
-
A form-associated custom element with a `value` property that fires
|
|
1951
|
-
`change` is a control. The hub sets its `value` when a column lands, and
|
|
1952
|
-
gathers its `value` when a request is issued, exactly as for a native
|
|
1953
|
-
`<input>`. It commits on `change`, and belongs to its form owner, which the
|
|
1954
|
-
browser tracks and the `form` attribute names.
|
|
1955
|
-
|
|
1956
|
-
```ts
|
|
1957
|
-
class NoteField extends HTMLElement {
|
|
1958
|
-
static formAssociated = true;
|
|
1959
|
-
// a `value` property, and a `change` event when the edit commits
|
|
1960
|
-
}
|
|
1961
|
-
```
|
|
1962
|
-
|
|
1963
|
-
A custom element that is not a control receives `lb-column-value` and
|
|
1964
|
-
renders it; its content is never replaced.
|
|
1965
|
-
|
|
1966
|
-
### Lifecycle
|
|
1967
|
-
|
|
1968
|
-
A custom element hears the hub's lifecycle, never the browser's. The hub
|
|
1969
|
-
calls four optional methods, which the `HubLifecycle` interface in
|
|
1970
|
-
`@loadbare/app/types` declares:
|
|
1971
|
-
|
|
1972
|
-
| The hub | A custom element hears |
|
|
1973
|
-
| ------------------------------------------------ | ------------------------------- |
|
|
1974
|
-
| Places it: chrome at startup, a page, a new live row, a new group | `lbAttached()`, once, after every column has landed on it |
|
|
1975
|
-
| Lands rows on it | `lbRowsLanded()` |
|
|
1976
|
-
| Finishes a request committed by it or inside it | `lbRequestDone(detail, origin)` |
|
|
1977
|
-
| Removes it: a row or group done leaving, the page replaced | `lbDetached()`, once |
|
|
1978
|
-
| Moves a live row the order put elsewhere | Nothing |
|
|
1979
|
-
| Turns `lb-show` off or on | Nothing |
|
|
1980
|
-
| Hides a row or a group for a filter | Nothing |
|
|
1981
|
-
|
|
1982
|
-
The chrome and a page are placed as the page's first answer lands, or once
|
|
1983
|
-
that round trip fails. An element under an `lb-show` that is off is placed
|
|
1984
|
-
inside its template, so `lbAttached` runs whether it shows or not. `lbAttached` reaches an outer
|
|
1985
|
-
custom element before the ones inside it, and `lbDetached` the inner ones
|
|
1986
|
-
first. `lbRequestDone` reaches the committing element and every custom
|
|
1987
|
-
element around it, innermost first, before the `lb-request-done` event.
|
|
1988
|
-
An element a script creates hears none of them.
|
|
1989
|
-
|
|
1990
|
-
A listener on the element itself goes in the constructor, which reads no
|
|
1991
|
-
attribute and no child. A child is looked up when it is needed. Work done
|
|
1992
|
-
once per instance goes in `lbAttached`, and its undoing in `lbDetached`.
|
|
1993
|
-
|
|
1994
|
-
The build refuses a script that declares `connectedCallback`,
|
|
1995
|
-
`disconnectedCallback`, `adoptedCallback` or `connectedMoveCallback`.
|
|
1996
|
-
|
|
1997
|
-
### Row hooks
|
|
1998
|
-
|
|
1999
|
-
The hub calls `lbRowsLanded()` on a custom element with `lb-query` and a row
|
|
2000
|
-
template, after the rows land, with every row placed and stamped. The hub
|
|
2001
|
-
places every row and builds every group itself, so a custom element reacts
|
|
2002
|
-
to rows and never places them. Scrolling to the row the page's request
|
|
2003
|
-
created is the case:
|
|
2004
|
-
|
|
2005
|
-
```ts
|
|
2006
|
-
lbRowsLanded() {
|
|
2007
|
-
this.querySelector(`[${ATTR_ROW_REQUESTED}="${REQUESTED_CREATED}"]`)
|
|
2008
|
-
?.scrollIntoView({ block: "nearest" });
|
|
2009
|
-
}
|
|
2010
|
-
```
|
|
2011
|
-
|
|
2012
|
-
A custom element under an `lb-show` that is off holds its rows all the
|
|
2013
|
-
same, and the hook runs as they land.
|
|
2014
|
-
|
|
2015
|
-
`applyRow(root, row)`, exported by `@loadbare/app`, fills `root` from one
|
|
2016
|
-
row, the same operation that fills a live row.
|
|
2017
|
-
|
|
2018
|
-
## Internal linkage
|
|
2019
|
-
|
|
2020
|
-
---- UNEDITED ----
|
|
2021
|
-
|
|
2022
|
-
The endpoint path, `index` for the bare path, the constant holding the
|
|
2023
|
-
request deadline, the generated client entry, and the `./build` export.
|
|
2024
|
-
Nobody outside this package reads any of it. The deadline itself is
|
|
2025
|
-
behavior an application sees — see [The round trip](#the-round-trip) — and
|
|
2026
|
-
giving it a knob later takes nothing away.
|
|
2027
|
-
|
|
2028
|
-
## Cross-reference
|
|
2029
|
-
|
|
2030
|
-
Every name Loadbare/app owns, in one place. Each row names the section
|
|
2031
|
-
that defines it. The definition lives there and only there.
|
|
2032
|
-
|
|
2033
|
-
### Builder flags
|
|
2034
|
-
|
|
2035
|
-
Loadbare/app owns every flag in this table.
|
|
2036
|
-
|
|
2037
|
-
| Flag | Default | Names | Defined in |
|
|
2038
|
-
| ---------- | ------- | ----------------------------------------- | ------------------------------------------------- |
|
|
2039
|
-
| `--src` | `src` | The application's own tree, scanned whole | [What the builder scans](#what-the-builder-scans) |
|
|
2040
|
-
| `--out` | `dist` | Where the builder writes | [Running the builder](#running-the-builder) |
|
|
2041
|
-
| `--watch` | off | Rebuild on change under `--src` | [Running the builder](#running-the-builder) |
|
|
2042
|
-
| `--minify` | off | Minify `client.js` and `app.css` | [Running the builder](#running-the-builder) |
|
|
2043
|
-
|
|
2044
|
-
### Reserved file names
|
|
2045
|
-
|
|
2046
|
-
Loadbare/app owns every file name and pattern in this table. A file so
|
|
2047
|
-
named carries its meaning wherever it sits in the `--src` tree, so an
|
|
2048
|
-
application must not use one of these names for anything else.
|
|
2049
|
-
|
|
2050
|
-
| File | How many | Holds | Defined in |
|
|
2051
|
-
| ----------------------- | ------------------------------ | -------------------------------------------------------- | ------------------------------------------------------- |
|
|
2052
|
-
| `chrome.html` | Exactly one | The application's one HTML document | [Chrome](#chrome) |
|
|
2053
|
-
| `imports.ts` | Zero or one | Default-exports an array of widget library package names | [Imported widget libraries](#imported-widget-libraries) |
|
|
2054
|
-
| `<stub>.page.html` | One per page | One page's markup, as a fragment | [Pages](#pages) |
|
|
2055
|
-
| `<stub>.queries.ts` | Zero or one per page | That page's queries | [Page Queries](#page-queries) |
|
|
2056
|
-
| `<stub>.requests.ts` | Zero or one per page | That page's requests | [Page Requests](#page-requests) |
|
|
2057
|
-
| `<tag-name>.html` | Zero or one per custom element | One element file, as a fragment | [Custom elements](#custom-elements) |
|
|
2058
|
-
| `<tag-name>.browser.ts` | Zero or one per custom element | One custom element's script, `.js` also accepted | [Custom elements](#custom-elements) |
|
|
2059
|
-
| `*.css` | Any number | A stylesheet, concatenated into `app.css` | [Running the builder](#running-the-builder) |
|
|
2060
|
-
|
|
2061
|
-
### Build outputs
|
|
2062
|
-
|
|
2063
|
-
Loadbare/app owns every file name in this table. The builder writes them
|
|
2064
|
-
into `--out`. Which of them exist depends on the application: `app.css`
|
|
2065
|
-
only when some origin has a stylesheet, `pages.ts` only when some page has a
|
|
2066
|
-
`.queries.ts` or a `.requests.ts`.
|
|
2067
|
-
|
|
2068
|
-
| Path | Written | Holds | Read by | Defined in |
|
|
2069
|
-
| ------------------ | ----------------- | ------------------------------------------------------------------ | ---------- | ------------------------------------------- |
|
|
2070
|
-
| `/app.html` | Always | The chrome, built | The server | [Chrome](#chrome) |
|
|
2071
|
-
| `/client.js` | Always | `<lb-hub>` and every custom element the application uses | The chrome | [Chrome](#chrome) |
|
|
2072
|
-
| `/client-entry.ts` | Always | The generated entry `client.js` is bundled from | Nothing | [Running the builder](#running-the-builder) |
|
|
2073
|
-
| `/app.css` | With a stylesheet | Every stylesheet in the cascade, concatenated | The chrome | [Chrome](#chrome) |
|
|
2074
|
-
| `/pages.ts` | With page data | The one `createHub()` call, over every page's queries and requests | The server | [The Express server](#the-express-server) |
|
|
2075
|
-
|
|
2076
|
-
### Reserved package.json keys
|
|
2077
|
-
|
|
2078
|
-
Loadbare/app owns every key in this table. A widget library declares them.
|
|
2079
|
-
An application never does.
|
|
2080
|
-
|
|
2081
|
-
| Key | Value | Means | Defined in |
|
|
2082
|
-
| ------------------ | ------------------------- | ------------------------------------------------------------------------------------ | ------------------------------------------------------- |
|
|
2083
|
-
| `loadbare.widgets` | A path inside the package | Where this package's custom elements are. Absent means the whole installed directory | [Imported widget libraries](#imported-widget-libraries) |
|
|
2084
|
-
|
|
2085
|
-
### Reserved namespaces
|
|
2086
|
-
|
|
2087
|
-
Loadbare/app owns every namespace in this table. Ownership of a name and
|
|
2088
|
-
ownership of its behavior are stated separately, because they differ.
|
|
2089
|
-
|
|
2090
|
-
| Namespace | Applies to | Loadbare owns | Defined in |
|
|
2091
|
-
| --------- | ----------------------------------------------------------- | --------------------------------------------------- | ----------------------------------------------- |
|
|
2092
|
-
| `lb-*` | Attributes, their values, queries, columns of `lb-` queries, requests, custom elements, events | The names and their behavior | [The lb-* namespace](#the-lb--namespace) |
|
|
2093
|
-
| `--lb-*` | Values of HTML's `command` attribute | The names and their behavior | [Reserved commands](#reserved-commands) |
|
|
2094
|
-
| `exp-*` | Attributes on a custom element | The behavior; element file authors pick the names | [Build time parameters](#build-time-parameters) |
|
|
2095
|
-
| `lb*` | Methods on custom elements | The names and their behavior | [Row hooks](#row-hooks) |
|
|
2096
|
-
|
|
2097
|
-
#### The lb-* namespace
|
|
2098
|
-
|
|
2099
|
-
Every name beginning with `lb-` belongs to Loadbare/app — see
|
|
2100
|
-
[Names](#names). An application writes the attributes this section names,
|
|
2101
|
-
and invents none of its own, because a name Loadbare has not defined today
|
|
2102
|
-
it may define tomorrow.
|
|
2103
|
-
|
|
2104
|
-
| Attribute | Written by | On | Defined in |
|
|
2105
|
-
| ----------------- | ------------- | ----------------------------------- | --------------------------------------------- |
|
|
2106
|
-
| `lb-query` | Developer | Any element | [Landing](#landing) |
|
|
2107
|
-
| `lb-column` | Developer | Any element | [Landing](#landing) |
|
|
2108
|
-
| `lb-show` | Developer | Any element but a `<template>` | [Displaying by condition](#displaying-by-condition) |
|
|
2109
|
-
| `lb-request` | Developer | Any element | [Requests](#requests) |
|
|
2110
|
-
| `lb-select` | Developer | A checkbox in a row template | [Selecting rows](#selecting-rows) |
|
|
2111
|
-
| `lb-url-link` | Developer | An `<a>` | [Links](#links) |
|
|
2112
|
-
| `lb-url-push` | Developer | An element with `lb-request` | [Query parms](#query-parms) |
|
|
2113
|
-
| `lb-url-unknown` | Developer | A `<dialog>` inside `<lb-hub>` | [An unknown page](#an-unknown-page) |
|
|
2114
|
-
| `lb-group` | Developer | A `<template>` holding a `<template>` | [Groups](#groups) |
|
|
2115
|
-
| `lb-count`, `lb-sum`, `lb-avg`, `lb-min`, `lb-max` | Developer | Any element but a `<template>` | [Aggregates](#aggregates) |
|
|
2116
|
-
| `lb-order` | Developer | An element with `lb-query` | [Order](#order) |
|
|
2117
|
-
| `lb-filter` | Developer | An element with `lb-query` | [Filters](#filters) |
|
|
2118
|
-
| `lb-exp-slot` | Developer | One element in an element file | [Slots and templates](#slots-and-templates) |
|
|
2119
|
-
| `lb-exp-template` | Developer | An element file, and a `<template>` | [Slots and templates](#slots-and-templates) |
|
|
2120
|
-
| `lb-column-value` | Hub | Every element set from a column | [How a column lands](#how-a-column-lands) |
|
|
2121
|
-
| `lb-key-value` | Hub | A live row, an element a `row` lands on, and a pointed form | [Rows](#rows), [A form pointed at a row](#a-form-pointed-at-a-row) |
|
|
2122
|
-
| `lb-row-editing` | Hub | The live row a form is pointed at | [A form pointed at a row](#a-form-pointed-at-a-row) |
|
|
2123
|
-
| `lb-row-selected` | Hub | A live row its `lb-select` checkbox ticks | [Selecting rows](#selecting-rows) |
|
|
2124
|
-
| `lb-row-live` | Hub | A live row | [Rows](#rows) |
|
|
2125
|
-
| `lb-query-row-count` | Hub | An element with a row template | [Rows](#rows) |
|
|
2126
|
-
| `lb-group-live`, `lb-group-column`, `lb-group-value` | Hub | Each top-level element of a group | [Groups](#groups) |
|
|
2127
|
-
| `lb-row-created`, `lb-row-changed`, `lb-row-moved`, `lb-row-requested` | Hub | A live row | [What happened to a row](#what-happened-to-a-row) |
|
|
2128
|
-
| `lb-row-leaving`, `lb-group-leaving` | Hub | A row or a group on its way out | [What happened to a row](#what-happened-to-a-row) |
|
|
2129
|
-
| `lb-request-pending` | Hub | The element that issued a request | [Request state](#request-state) |
|
|
2130
|
-
| `lb-request-error` | Hub | The element that issued a request | [Request state](#request-state) |
|
|
2131
|
-
| `lb-request-refused` | Hub | The element whose request was refused | [Refusals](#refusals) |
|
|
2132
|
-
| `lb-show` on a `<template>` | Hub and builder | Where an absent element stands | [Displaying by condition](#displaying-by-condition) |
|
|
2133
|
-
| `lb-page` | Builder | A page's `<template>` | [Page HTML](#page-html) |
|
|
2134
|
-
| `lb-page-title` | Builder | A page's `<template>` | [Page HTML](#page-html) |
|
|
2135
|
-
|
|
2136
|
-
The builder refuses any other `lb-` attribute in markup — see
|
|
2137
|
-
[The markup checks](#the-markup-checks).
|
|
2138
|
-
|
|
2139
|
-
### Reserved queries
|
|
2140
|
-
|
|
2141
|
-
| Query | Kind | Key | Columns | Defined in |
|
|
2142
|
-
| -------- | ----- | --------- | --------------------------------------------------------------- | ------------------- |
|
|
2143
|
-
| `lb-url` | `row` | `lb-path` | `lb-path`, `lb-page-label`, `lb-page-unknown`, every query parm | [The URL](#the-url) |
|
|
2144
|
-
| `lb-choices` | `row` | `lb-path` | `lb-path`, every choice | [Choices](#choices) |
|
|
2145
|
-
|
|
2146
|
-
### Reserved query parms
|
|
2147
|
-
|
|
2148
|
-
None. A column of `lb-url` whose name begins with `lb-` is never a query
|
|
2149
|
-
parm — see [Query parms](#query-parms).
|
|
2150
|
-
|
|
2151
|
-
### Reserved request names
|
|
2152
|
-
|
|
2153
|
-
| Request name | Runs under `crud` | Defined in |
|
|
2154
|
-
| --------------- | ----------------- | ----------------------------------- |
|
|
2155
|
-
| `lb-row-insert` | `rowInsert` | [The request names](#the-request-names) |
|
|
2156
|
-
| `lb-row-update` | `rowUpdate` | [The request names](#the-request-names) |
|
|
2157
|
-
| `lb-row-delete` | `rowDelete` | [The request names](#the-request-names) |
|
|
2158
|
-
|
|
2159
|
-
### Reserved commands
|
|
2160
|
-
|
|
2161
|
-
Loadbare/app owns every value of HTML's `command` attribute beginning with
|
|
2162
|
-
`--lb-`.
|
|
2163
|
-
|
|
2164
|
-
| Command | Defined in |
|
|
2165
|
-
| -------------- | --------------------------------------------------- |
|
|
2166
|
-
| `--lb-edit` | [A form pointed at a row](#a-form-pointed-at-a-row) |
|
|
2167
|
-
| `--lb-new` | [A form pointed at a row](#a-form-pointed-at-a-row) |
|
|
2168
|
-
| `--lb-release` | [A form pointed at a row](#a-form-pointed-at-a-row) |
|
|
2169
|
-
|
|
2170
|
-
### Reserved tags
|
|
2171
|
-
|
|
2172
|
-
Loadbare/app owns every tag in this table. An application writes them and
|
|
2173
|
-
never defines them.
|
|
2174
|
-
|
|
2175
|
-
| Tag | Written in | Means | Defined in |
|
|
2176
|
-
| ---------- | ---------- | ------------------------------ | ----------------- |
|
|
2177
|
-
| `<lb-hub>` | The chrome | The application's live element | [Chrome](#chrome) |
|
|
2178
|
-
|
|
2179
|
-
### Reserved events
|
|
2180
|
-
|
|
2181
|
-
Loadbare/app owns every DOM event in this table, both the name and what its
|
|
2182
|
-
`detail` carries.
|
|
2183
|
-
|
|
2184
|
-
| Event | Dispatched from | Bubbles | Cancelable | Defined in |
|
|
2185
|
-
| ------------ | -------------------------------- | ------- | ---------- | --------------------------------------- |
|
|
2186
|
-
| `lb-request` | The element that committed | Yes | No | [The request event](#the-request-event) |
|
|
2187
|
-
| `lb-request-done` | The element that committed | Yes | No | [The request event](#the-request-event) |
|
|
2188
|
-
|
|
2189
|
-
### Reserved methods
|
|
2190
|
-
|
|
2191
|
-
| Method | On | Defined in |
|
|
2192
|
-
| --------------- | --------------------------------------------------- | ----------------------- |
|
|
2193
|
-
| `lbAttached` | Any custom element the hub places | [Lifecycle](#lifecycle) |
|
|
2194
|
-
| `lbRowsLanded` | A custom element with `lb-query` and a row template | [Row hooks](#row-hooks) |
|
|
2195
|
-
| `lbRequestDone` | A custom element that committed, or holds one that did | [Lifecycle](#lifecycle) |
|
|
2196
|
-
| `lbDetached` | Any custom element the hub removes | [Lifecycle](#lifecycle) |
|
|
2197
|
-
|
|
2198
|
-
### Constants
|
|
2199
|
-
|
|
2200
|
-
`@loadbare/app/constants` exports every name above. Custom element code
|
|
2201
|
-
imports them rather than writing a string.
|
|
2202
|
-
|
|
2203
|
-
| Constant | Value |
|
|
2204
|
-
| ------------------------- | --------------------------------------- |
|
|
2205
|
-
| `ATTR_QUERY` | `lb-query` |
|
|
2206
|
-
| `ATTR_COLUMN` | `lb-column` |
|
|
2207
|
-
| `ATTR_SHOW` | `lb-show` |
|
|
2208
|
-
| `ATTR_COLUMN_VALUE` | `lb-column-value` |
|
|
2209
|
-
| `ATTR_KEY_VALUE` | `lb-key-value` |
|
|
2210
|
-
| `ATTR_ROW_LIVE` | `lb-row-live` |
|
|
2211
|
-
| `LIVE_ROW` | `[lb-row-live]`, the selector for a live row |
|
|
2212
|
-
| `ATTR_QUERY_ROW_COUNT` | `lb-query-row-count` |
|
|
2213
|
-
| `ATTR_GROUP` | `lb-group` |
|
|
2214
|
-
| `ATTR_GROUP_LIVE` | `lb-group-live` |
|
|
2215
|
-
| `ATTR_GROUP_COLUMN` | `lb-group-column` |
|
|
2216
|
-
| `ATTR_GROUP_VALUE` | `lb-group-value` |
|
|
2217
|
-
| `LIVE_GROUP` | `[lb-group-live]`, the selector for a group's element |
|
|
2218
|
-
| `ATTR_COUNT`, `ATTR_SUM`, `ATTR_AVG`, `ATTR_MIN`, `ATTR_MAX` | `lb-count`, `lb-sum`, `lb-avg`, `lb-min`, `lb-max` |
|
|
2219
|
-
| `AGGREGATES` | All five aggregate attributes |
|
|
2220
|
-
| `ORDER_DESCENDING` | `-`, written before a column in an order |
|
|
2221
|
-
| `CHOICES_QUERY` | `lb-choices` |
|
|
2222
|
-
| `ATTR_ORDER` | `lb-order` |
|
|
2223
|
-
| `ATTR_FILTER` | `lb-filter` |
|
|
2224
|
-
| `FILTER_OPERATORS` | Every filter operator, the longer first |
|
|
2225
|
-
| `ATTR_ROW_CREATED` | `lb-row-created` |
|
|
2226
|
-
| `ATTR_ROW_CHANGED` | `lb-row-changed` |
|
|
2227
|
-
| `ATTR_ROW_MOVED` | `lb-row-moved` |
|
|
2228
|
-
| `ATTR_ROW_REQUESTED` | `lb-row-requested` |
|
|
2229
|
-
| `REQUESTED_CREATED`, `REQUESTED_CHANGED`, `REQUESTED_MOVED` | `created`, `changed`, `moved` |
|
|
2230
|
-
| `ATTR_ROW_LEAVING` | `lb-row-leaving` |
|
|
2231
|
-
| `ATTR_GROUP_LEAVING` | `lb-group-leaving` |
|
|
2232
|
-
| `COMMAND_EDIT`, `COMMAND_NEW`, `COMMAND_RELEASE` | `--lb-edit`, `--lb-new`, `--lb-release` |
|
|
2233
|
-
| `LB_COMMANDS` | All three commands |
|
|
2234
|
-
| `LB_COMMAND_PREFIX` | `--lb-` |
|
|
2235
|
-
| `ATTR_ROW_EDITING` | `lb-row-editing` |
|
|
2236
|
-
| `ATTR_SELECT` | `lb-select` |
|
|
2237
|
-
| `ATTR_ROW_SELECTED` | `lb-row-selected` |
|
|
2238
|
-
| `ATTR_REQUEST` | `lb-request` |
|
|
2239
|
-
| `ATTR_REQUEST_PENDING` | `lb-request-pending` |
|
|
2240
|
-
| `ATTR_REQUEST_ERROR` | `lb-request-error` |
|
|
2241
|
-
| `ATTR_REQUEST_REFUSED` | `lb-request-refused` |
|
|
2242
|
-
| `LB_REFUSAL_STATUS` | `422` |
|
|
2243
|
-
| `ATTR_URL_LINK` | `lb-url-link` |
|
|
2244
|
-
| `ATTR_URL_PUSH` | `lb-url-push` |
|
|
2245
|
-
| `ATTR_URL_UNKNOWN` | `lb-url-unknown` |
|
|
2246
|
-
| `ATTR_EXP_SLOT` | `lb-exp-slot` |
|
|
2247
|
-
| `ATTR_EXP_TEMPLATE` | `lb-exp-template` |
|
|
2248
|
-
| `ATTR_PAGE` | `lb-page` |
|
|
2249
|
-
| `ATTR_PAGE_TITLE` | `lb-page-title` |
|
|
2250
|
-
| `DEVELOPER_ATTRIBUTES` | The sixteen attributes a developer writes |
|
|
2251
|
-
| `LB_EVENT_NAME` | `lb-request` |
|
|
2252
|
-
| `LB_DONE_EVENT_NAME` | `lb-request-done` |
|
|
2253
|
-
| `SHOW_NOT` | `!`, written before a column in `lb-show` |
|
|
2254
|
-
| `LB_RESERVED_PREFIX` | `lb-` |
|
|
2255
|
-
| `REQUEST_ROW_INSERT` | `lb-row-insert` |
|
|
2256
|
-
| `REQUEST_ROW_UPDATE` | `lb-row-update` |
|
|
2257
|
-
| `REQUEST_ROW_DELETE` | `lb-row-delete` |
|
|
2258
|
-
| `LB_ROW_REQUESTS` | All three request names |
|
|
2259
|
-
| `KIND_ROW` | `row` |
|
|
2260
|
-
| `KIND_ROWS` | `rows` |
|
|
2261
|
-
| `URL_QUERY` | `lb-url` |
|
|
2262
|
-
| `URL_COLUMN_PATH` | `lb-path` |
|
|
2263
|
-
| `URL_COLUMN_PAGE_LABEL` | `lb-page-label` |
|
|
2264
|
-
| `URL_COLUMN_PAGE_UNKNOWN` | `lb-page-unknown` |
|
|
2265
|
-
| `HUB_TAG_NAME` | `lb-hub` |
|
|
2266
|
-
| `LB_ENDPOINT` | `/lb` |
|
|
2267
|
-
| `LB_REQUEST_TIMEOUT_MS` | `10000` |
|