@loadbare/app 0.12.0 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/build/assemble.d.ts.map +1 -1
- package/dist/build/assemble.js +87 -1
- package/dist/build/assemble.js.map +1 -1
- package/dist/build/cli.js +2 -1
- package/dist/build/cli.js.map +1 -1
- package/dist/build/elements.d.ts +9 -1
- package/dist/build/elements.d.ts.map +1 -1
- package/dist/build/elements.js +50 -0
- package/dist/build/elements.js.map +1 -1
- package/dist/build/origins.d.ts +2 -0
- package/dist/build/origins.d.ts.map +1 -1
- package/dist/build/origins.js +1 -1
- package/dist/build/origins.js.map +1 -1
- package/dist/core/lb-constants.d.ts +34 -1
- package/dist/core/lb-constants.d.ts.map +1 -1
- package/dist/core/lb-constants.js +105 -0
- package/dist/core/lb-constants.js.map +1 -1
- package/dist/core/lb-types.d.ts +74 -18
- package/dist/core/lb-types.d.ts.map +1 -1
- package/dist/core/lb-types.js +74 -11
- package/dist/core/lb-types.js.map +1 -1
- package/dist/hub/lb-apply.d.ts +57 -18
- package/dist/hub/lb-apply.d.ts.map +1 -1
- package/dist/hub/lb-apply.js +661 -63
- package/dist/hub/lb-apply.js.map +1 -1
- package/dist/hub/lb-hub.browser.d.ts.map +1 -1
- package/dist/hub/lb-hub.browser.js +400 -33
- package/dist/hub/lb-hub.browser.js.map +1 -1
- package/dist/server/lb-express.d.ts +19 -7
- package/dist/server/lb-express.d.ts.map +1 -1
- package/dist/server/lb-express.js +62 -26
- package/dist/server/lb-express.js.map +1 -1
- package/dist/server/lb-server.d.ts +80 -11
- package/dist/server/lb-server.d.ts.map +1 -1
- package/dist/server/lb-server.js +185 -33
- package/dist/server/lb-server.js.map +1 -1
- package/docs/TECHREF-1.0.md +584 -111
- package/docs/comparison.md +17 -13
- package/docs/possible-ideas.md +308 -0
- package/docs/reference/chrome.md +7 -1
- package/docs/reference/custom-elements.md +134 -39
- package/docs/reference/data-binding.md +405 -17
- package/docs/reference/page-files.md +101 -19
- package/docs/reference/server.md +24 -0
- package/docs/reference/widgets.md +118 -25
- package/docs/roadmap.md +17 -6
- package/docs/testing.md +21 -10
- package/package.json +2 -3
- package/skills/loadbare-app/SKILL.md +92 -30
- package/skills/loadbare-app/references/TECHREF-1.0.md +584 -111
- package/skills/loadbare-app/references/chrome.md +7 -1
- package/skills/loadbare-app/references/custom-elements.md +134 -39
- package/skills/loadbare-app/references/data-binding.md +405 -17
- package/skills/loadbare-app/references/page-files.md +101 -19
- package/skills/loadbare-app/references/server.md +24 -0
- package/skills/loadbare-app/references/widgets.md +118 -25
|
@@ -22,38 +22,28 @@ a value is rendered by browser coercion when it lands on an HTML text
|
|
|
22
22
|
element or a control, and by whatever the custom element does with
|
|
23
23
|
`lb-column-value` when it lands on one that is not a control.
|
|
24
24
|
|
|
25
|
+
Two places read a value's type today. The [order](#order) compares values
|
|
26
|
+
by their JSON type, and an [aggregate](#aggregates) reads a sum or an
|
|
27
|
+
average from a JSON number or a string holding a plain decimal.
|
|
28
|
+
|
|
25
29
|
We will then see if a useful solution emerges that Loadbare/app should
|
|
26
30
|
handle.
|
|
27
31
|
|
|
28
32
|
### Form controls
|
|
29
33
|
|
|
30
|
-
- **
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
radio group is several elements answering to one column.
|
|
34
|
-
|
|
35
|
-
### Run-time stamps
|
|
36
|
-
|
|
37
|
-
The hub stamps `lb-query-row-count`, `lb-request-pending` and
|
|
38
|
-
`lb-request-error` for a stylesheet to read. Their consumer is CSS rather
|
|
39
|
-
than code.
|
|
40
|
-
|
|
41
|
-
- **Decide whether an unarrived value needs a signal.** An element whose
|
|
42
|
-
column has not landed carries no `lb-column-value`. Recommend confirming
|
|
43
|
-
that as the signal and adding nothing.
|
|
34
|
+
- **Radio buttons are not implemented.** A value does not land on one and
|
|
35
|
+
the hub does not gather one. A radio group is several elements answering
|
|
36
|
+
to one column.
|
|
44
37
|
|
|
45
38
|
### Custom element hooks
|
|
46
39
|
|
|
47
40
|
Loadbare/app owns every method name beginning with `lb` on a custom element.
|
|
48
41
|
That decision is firm; what the set contains is not.
|
|
49
42
|
|
|
50
|
-
- **Decide what the build does with an unknown `lb*` method.** An element
|
|
51
|
-
carrying a method beginning with `lb` that Loadbare/app does not define may
|
|
52
|
-
be an error, a warning, or ignored.
|
|
53
43
|
- **Decide whether a custom element may receive a whole row.** A column
|
|
54
44
|
lands one element at a time and offers no escape hatch. An optional
|
|
55
|
-
`lbAcceptRow` would sit beside `
|
|
56
|
-
|
|
45
|
+
`lbAcceptRow` would sit beside `lbRowsLanded`, and this is the most likely
|
|
46
|
+
first request from a custom element author.
|
|
57
47
|
|
|
58
48
|
### Nested queries
|
|
59
49
|
|
|
@@ -77,38 +67,9 @@ But the allowed values may depend on other values in the row.
|
|
|
77
67
|
</tbody>
|
|
78
68
|
```
|
|
79
69
|
|
|
80
|
-
### Who creates and orders rows
|
|
81
|
-
|
|
82
|
-
The hub creates every live row from a row template and places it in the
|
|
83
|
-
query's order, unless the element holding the rows has `lbPlaceRow`, in
|
|
84
|
-
which case the custom element decides where each row goes. Custom elements
|
|
85
|
-
also create rows of their own: `lb-table` clones a ghost row per section and
|
|
86
|
-
builds each section's heading. Neither side owns creation or order.
|
|
87
|
-
|
|
88
|
-
- **Decide who creates rows and who orders them.** A patch cannot say where
|
|
89
|
-
a new row goes, so it lands where the host puts it: in order under
|
|
90
|
-
`lb-table` with `data-sort`, last in plain markup and in `lb-options`.
|
|
91
|
-
Whether an insert may answer with a patch therefore depends on markup the
|
|
92
|
-
server cannot see, and the server cannot refuse an insert that refreshes
|
|
93
|
-
its own query the way it refuses an update or a delete.
|
|
94
|
-
- **Decide whether landing all rows moves rows already in place.** It moves
|
|
95
|
-
every row today, which takes focus from the control the user is in, and
|
|
96
|
-
`lbPlaceRow` does the same.
|
|
97
|
-
|
|
98
70
|
### The server API
|
|
99
71
|
|
|
100
|
-
- **
|
|
101
|
-
the chrome naming a query forces every page to declare that query, since
|
|
102
|
-
the page load covers one page's set. `lb-url` shows the pattern from the
|
|
103
|
-
framework side, and an application has no equivalent. Recommend a
|
|
104
|
-
chrome-level query set on `createHub`.
|
|
105
|
-
- **State that only `createHub` implements `Hub`.** Otherwise a third call
|
|
106
|
-
breaks anyone who wrote the interface by hand.
|
|
107
|
-
- **Decide the options-argument shape once.** Global hooks, transaction
|
|
108
|
-
wrapping, error handling and a CSRF token all want the same trailing
|
|
109
|
-
parameter on `createHub` and `hubRoutes`. Build none of them, but pick
|
|
110
|
-
where they go.
|
|
111
|
-
- **Land `staticRoutes`.** See NEXT.md. It closes the last place where an
|
|
72
|
+
- **Land `staticRoutes`.** It closes the last place where an
|
|
112
73
|
application hand-writes facts the builder owns.
|
|
113
74
|
|
|
114
75
|
### The builder
|
|
@@ -123,9 +84,6 @@ builds each section's heading. Neither side owns creation or order.
|
|
|
123
84
|
definition would let the builder ship only what survives expansion.
|
|
124
85
|
Recommend deferring the mechanism and reserving the configuration key, so
|
|
125
86
|
that it lands as an opt-in rather than as a silent drop.
|
|
126
|
-
- **Land the remaining validations.** One `lb-hub` and one empty `<main>`.
|
|
127
|
-
Recommend landing these now, because refusing markup that used to build is
|
|
128
|
-
the kind of change 1.0 gives up.
|
|
129
87
|
- **Decide whether the build validates HTML.** A page can be well formed and
|
|
130
88
|
still break HTML's rules for what an element may hold, such as a `<div>`
|
|
131
89
|
inside a `<p>`. The parser rearranges it silently, the same way at build
|
|
@@ -141,9 +99,6 @@ builds each section's heading. Neither side owns creation or order.
|
|
|
141
99
|
None of this is surface. It is listed here because trimming it is free
|
|
142
100
|
today and breaking after 1.0.
|
|
143
101
|
|
|
144
|
-
- **Drop `./build` from the exports map.** Nothing in the repository imports
|
|
145
|
-
it, and nothing outside the CLI should call `resolveElements` or
|
|
146
|
-
`clientEntrySource`.
|
|
147
102
|
- **Audit what the hub does.** It appears to have routines that go beyond
|
|
148
103
|
what the `lb-*` attribute namespace specifies.
|
|
149
104
|
- **Keep the rest as constants.** The endpoint, `index` and the timeout are
|
|
@@ -455,9 +410,10 @@ The markup names a query and the columns it shows. The kind and the key are
|
|
|
455
410
|
the server's, and the markup states neither.
|
|
456
411
|
|
|
457
412
|
The row template is the first `<template>` among the descendants of an
|
|
458
|
-
element with `lb-query`, outside any nested `lb-query
|
|
459
|
-
|
|
460
|
-
|
|
413
|
+
element with `lb-query`, outside any nested `lb-query`, or, when that
|
|
414
|
+
template is a [group template](#groups), the innermost template inside it.
|
|
415
|
+
A live row is an element the hub cloned from a row template for one row,
|
|
416
|
+
and carries `lb-row-live`.
|
|
461
417
|
|
|
462
418
|
| Kind | Row template | The hub |
|
|
463
419
|
| ------ | ------------ | ----------------------------------- |
|
|
@@ -496,7 +452,8 @@ A query that arrives with nowhere to land is reported to the console.
|
|
|
496
452
|
| Any other element | Its text content |
|
|
497
453
|
|
|
498
454
|
A control is an `<input>`, `<select>` or `<textarea>`, or a form-associated
|
|
499
|
-
custom element with a `value` property that fires `change`. A
|
|
455
|
+
custom element with a `value` property that fires `change`. A checkbox is a
|
|
456
|
+
control too, by the rule below. A
|
|
500
457
|
form-associated custom element declares `static formAssociated = true`.
|
|
501
458
|
|
|
502
459
|
The hub stamps `lb-column-value` with the value on every element it sets.
|
|
@@ -507,9 +464,21 @@ renders `lb-column-value` itself.
|
|
|
507
464
|
The hub gathers from the same controls it lands on, so a value read back is
|
|
508
465
|
the one that landed.
|
|
509
466
|
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
467
|
+
A checkbox's state is `checked`, not `value`: HTML's quirk, and so the
|
|
468
|
+
hub's, so that every other part of the vocabulary treats it as any control.
|
|
469
|
+
The hub checks it by the rule `lb-show` is on by: `null` and `false` are
|
|
470
|
+
unchecked, and every other value is checked, with no string read. It
|
|
471
|
+
gathers `"true"` or `"false"`, so an unchecked box still says so, where a
|
|
472
|
+
native form submission leaves it out. The checkbox's own `value`
|
|
473
|
+
attribute means nothing to the hub.
|
|
474
|
+
|
|
475
|
+
```html
|
|
476
|
+
<input type="checkbox" lb-column="active" lb-request="lb-row-update" />
|
|
477
|
+
```
|
|
478
|
+
|
|
479
|
+
An `<input>` of type `radio` or `file` receives nothing, not even
|
|
480
|
+
`lb-column-value`, and the hub reports it to the console. The hub does not
|
|
481
|
+
gather one either.
|
|
513
482
|
|
|
514
483
|
A `<select>` with no option for the value shows no selection.
|
|
515
484
|
|
|
@@ -538,14 +507,15 @@ with `[lb-row-live]`, and never with `[lb-key-value]`.
|
|
|
538
507
|
A key is unique within a query. Two rows with one key in the same answer,
|
|
539
508
|
or two live rows showing one key, are reported on the console.
|
|
540
509
|
|
|
541
|
-
All rows decide membership
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
position alone.
|
|
510
|
+
All rows decide membership: a live row whose key did not arrive leaves —
|
|
511
|
+
see [What happened to a row](#what-happened-to-a-row). A patch touches only
|
|
512
|
+
the rows it names, and leaves every other live row's contents alone.
|
|
545
513
|
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
514
|
+
The hub places every live row by the query's [order](#order), immediately
|
|
515
|
+
before the row template, or before its clone in the row's
|
|
516
|
+
[group](#groups). A row moves only when the order puts it somewhere else,
|
|
517
|
+
so all rows landing again unchanged move nothing. A live row already in the
|
|
518
|
+
markup when the first answer lands is one of the query's rows.
|
|
549
519
|
|
|
550
520
|
After every landing the hub stamps the element with `lb-query-row-count`,
|
|
551
521
|
the number of live rows it holds. The server answers with rows and says
|
|
@@ -560,6 +530,162 @@ sent. It makes an empty query a stylesheet rule:
|
|
|
560
530
|
|
|
561
531
|
A row missing its key column is reported to the console and not landed.
|
|
562
532
|
|
|
533
|
+
#### Order
|
|
534
|
+
|
|
535
|
+
A `rows` query's order is a rule over its columns. The server declares it
|
|
536
|
+
with the query, and the URL may carry the user's own:
|
|
537
|
+
|
|
538
|
+
```ts
|
|
539
|
+
accounts: rows("id", (ctx) => ctx.db.accounts(), {
|
|
540
|
+
order: "category_order,account_name",
|
|
541
|
+
}),
|
|
542
|
+
```
|
|
543
|
+
|
|
544
|
+
An order is columns separated by commas. A column written after `-` sorts
|
|
545
|
+
descending. The query parm `lb-order-<query>` replaces the declared order
|
|
546
|
+
for that query: `/coa?lb-order-accounts=-account_name`. With neither, rows
|
|
547
|
+
show in the order the server sent them, and a patch's new rows go last.
|
|
548
|
+
|
|
549
|
+
| Values | Compare |
|
|
550
|
+
| -------------------- | ------------------------------------------ |
|
|
551
|
+
| Two numbers | Numerically |
|
|
552
|
+
| Two strings | As the user's language orders them |
|
|
553
|
+
| Two booleans | `false` before `true` |
|
|
554
|
+
| Two different types | Booleans, then numbers, then strings |
|
|
555
|
+
| `null` or missing | After everything ascending, first descending |
|
|
556
|
+
|
|
557
|
+
A number sent as a string sorts as a string, so a column the order reads
|
|
558
|
+
numerically arrives as a JSON number. Rows that compare equal keep the
|
|
559
|
+
order they arrived in, and a patch's new rows come after the rows already
|
|
560
|
+
showing.
|
|
561
|
+
|
|
562
|
+
The hub sorts the rows it has, so a control that writes the order parm
|
|
563
|
+
re-sorts the page with no round trip:
|
|
564
|
+
|
|
565
|
+
```html
|
|
566
|
+
<div lb-query="lb-url">
|
|
567
|
+
<select lb-column="lb-order-accounts" lb-request="lb-row-update">
|
|
568
|
+
<option value="">By category</option>
|
|
569
|
+
<option value="account_name">By name</option>
|
|
570
|
+
</select>
|
|
571
|
+
</div>
|
|
572
|
+
```
|
|
573
|
+
|
|
574
|
+
A query that pages or limits its rows decides membership by its order, so
|
|
575
|
+
it reads the parm itself, from the parms `contextFor` receives, and declares
|
|
576
|
+
`serverSortedByUrl: true` beside `order`. A change of the user's order then
|
|
577
|
+
loads the page again. The hub still places the rows it receives by the same
|
|
578
|
+
order, so a patch lands in its place.
|
|
579
|
+
|
|
580
|
+
#### Groups
|
|
581
|
+
|
|
582
|
+
A group is a run of rows sharing the order's leading term. A group
|
|
583
|
+
template is a `<template lb-group>` in the row template's place, holding the
|
|
584
|
+
group's heading and exactly one nested `<template>`, which is where the
|
|
585
|
+
group's contents go: the next group template, or the row template. The nth
|
|
586
|
+
group template breaks on the order's nth term, so groups nest to any depth.
|
|
587
|
+
|
|
588
|
+
```html
|
|
589
|
+
<table lb-query="accounts">
|
|
590
|
+
<thead><tr><th>Account</th></tr></thead>
|
|
591
|
+
<template lb-group>
|
|
592
|
+
<tbody>
|
|
593
|
+
<tr><th scope="rowgroup" lb-column="category"></th></tr>
|
|
594
|
+
<template><tr><td lb-column="account_name"></td></tr></template>
|
|
595
|
+
</tbody>
|
|
596
|
+
</template>
|
|
597
|
+
</table>
|
|
598
|
+
```
|
|
599
|
+
|
|
600
|
+
The hub clones the group template where the order breaks and puts the
|
|
601
|
+
group's contents immediately before the clone of its nested template. The
|
|
602
|
+
markup's nesting decides the shape:
|
|
603
|
+
|
|
604
|
+
| The nested template is | The group's contents land | Example |
|
|
605
|
+
| --------------------------------- | ----------------------------------- | -------------------------------- |
|
|
606
|
+
| Inside the heading's element | Inside it | `<tbody>`, `<li>`, `<details>` |
|
|
607
|
+
| Beside the heading | After the heading, as its siblings | A heading `<tr>` in one `<tbody>` |
|
|
608
|
+
|
|
609
|
+
`<tbody>` and `<optgroup>` do not nest, so a table's levels below the first
|
|
610
|
+
are heading rows beside their rows. Content after the nested template lands
|
|
611
|
+
after the group's rows, which is where a group's footer goes.
|
|
612
|
+
|
|
613
|
+
A group's heading is filled from its first row, the same way a live row is
|
|
614
|
+
filled, so it shows any column of the row. The example breaks on
|
|
615
|
+
`category_order` and shows `category`. The hub fills it from no other row.
|
|
616
|
+
|
|
617
|
+
The hub stamps each top-level element of a group's clone with
|
|
618
|
+
`lb-group-live`, `lb-group-column`, the column it breaks on, and
|
|
619
|
+
`lb-group-value`, its value there. A row creates its group, and a group
|
|
620
|
+
leaves with its last row. Groups never move: when a change of order puts
|
|
621
|
+
the groups at a level in another order, each of them is made again.
|
|
622
|
+
|
|
623
|
+
A group template with no nested template is reported to the console. More
|
|
624
|
+
group templates than the order has terms show each extra level as one
|
|
625
|
+
group, and are reported once.
|
|
626
|
+
|
|
627
|
+
#### Aggregates
|
|
628
|
+
|
|
629
|
+
An aggregate attribute sets an element from the rows of the nearest group
|
|
630
|
+
around it, or of the whole list outside any group.
|
|
631
|
+
|
|
632
|
+
| Attribute | Sets the element to |
|
|
633
|
+
| --------------- | --------------------------------------------- |
|
|
634
|
+
| `lb-count` | The number of rows |
|
|
635
|
+
| `lb-sum="col"` | The sum of `col` |
|
|
636
|
+
| `lb-avg="col"` | The average of `col` |
|
|
637
|
+
| `lb-min="col"` | The least value of `col`, by the [order](#order)'s comparison |
|
|
638
|
+
| `lb-max="col"` | The greatest, likewise |
|
|
639
|
+
|
|
640
|
+
```html
|
|
641
|
+
<tr><td><span lb-count></span> accounts</td><td lb-sum="balance"></td></tr>
|
|
642
|
+
```
|
|
643
|
+
|
|
644
|
+
It lands as a column does, with `lb-column-value`. A sum and an average
|
|
645
|
+
are exact decimals, read from JSON numbers and from strings holding a plain
|
|
646
|
+
decimal, as Postgres sends `numeric`. A sum has as many places as the most
|
|
647
|
+
precise value, and an average up to two more. `null` and empty values are
|
|
648
|
+
left out, as SQL leaves them out. Any other value is left out and reported
|
|
649
|
+
once. The hub does not format the result.
|
|
650
|
+
|
|
651
|
+
An aggregate covers the rows the browser holds. Where the true total
|
|
652
|
+
matters beyond what the page shows, it is a row the server answers.
|
|
653
|
+
|
|
654
|
+
#### What happened to a row
|
|
655
|
+
|
|
656
|
+
Every change the hub makes to a list is one a stylesheet can see, so an
|
|
657
|
+
application animates with CSS alone and the hub starts no animation.
|
|
658
|
+
|
|
659
|
+
| Attribute | On | Means |
|
|
660
|
+
| ------------------ | -------------------- | -------------------------------------------------- |
|
|
661
|
+
| `lb-row-created` | A live row | Created when the hub last touched it |
|
|
662
|
+
| `lb-row-changed` | A live row | Its values changed when the hub last touched it |
|
|
663
|
+
| `lb-row-moved` | A live row | The order put it somewhere else when last touched |
|
|
664
|
+
| `lb-row-requested` | A live row | `created`, `changed` or `moved` by this page's request |
|
|
665
|
+
| `lb-row-leaving` | A row on its way out | No longer live |
|
|
666
|
+
| `lb-group-leaving` | A group on its way out | No longer live |
|
|
667
|
+
|
|
668
|
+
The hub touches a row when an answer names it. `lb-row-created`,
|
|
669
|
+
`lb-row-changed` and `lb-row-moved` are cleared when it next does, and one
|
|
670
|
+
applied again is removed and added as two changes, so an animation keyed to
|
|
671
|
+
it plays again. `lb-row-requested` is cleared at every landing of the
|
|
672
|
+
row's query, and set only by a landing that answers a request from the page.
|
|
673
|
+
|
|
674
|
+
A row or a group that leaves loses `lb-row-live` or `lb-group-live`, takes
|
|
675
|
+
`lb-row-leaving` or `lb-group-leaving` and `inert`, and is removed once the
|
|
676
|
+
animations running on it have finished, at once when there are none. A
|
|
677
|
+
moved row stays live and moves. A copy of it stays behind where it was,
|
|
678
|
+
leaving.
|
|
679
|
+
|
|
680
|
+
```css
|
|
681
|
+
[lb-row-created] { animation: arrive 200ms; }
|
|
682
|
+
[lb-row-leaving] { animation: depart 200ms forwards; }
|
|
683
|
+
```
|
|
684
|
+
|
|
685
|
+
A row never travels between the two places, since that takes measuring, and
|
|
686
|
+
measuring takes script. Scrolling to a row is script too: a custom element
|
|
687
|
+
reads `lb-row-requested` in [`lbRowsLanded`](#row-hooks).
|
|
688
|
+
|
|
563
689
|
#### Displaying by condition
|
|
564
690
|
|
|
565
691
|
`lb-show` names the column that decides whether an element is present.
|
|
@@ -624,9 +750,9 @@ answered side by side. A column never holds rows.
|
|
|
624
750
|
```
|
|
625
751
|
|
|
626
752
|
Many masters, each with its own detail, is one `rows` query of joined rows:
|
|
627
|
-
each detail row carries its master's columns. A
|
|
628
|
-
|
|
629
|
-
|
|
753
|
+
each detail row carries its master's columns. A [group
|
|
754
|
+
template](#groups) shows each master as a group, ordered by the master's
|
|
755
|
+
key first.
|
|
630
756
|
|
|
631
757
|
A query nested in another query's row template receives the same rows in
|
|
632
758
|
every live row. That serves a picker offering the same choices on every
|
|
@@ -664,9 +790,13 @@ Every request has one shape, on the event and on the wire:
|
|
|
664
790
|
{ name: "lb-row-update", query: "roster", key: "42", values: { name: "Ann" } }
|
|
665
791
|
{ name: "lb-row-delete", query: "roster", key: "42" }
|
|
666
792
|
{ name: "mailRoster", query: "roster", key: "42" }
|
|
793
|
+
{ name: "lb-row-update", query: "roster", keys: ["42", "43"], values: { role: "lead" } }
|
|
794
|
+
{ name: "lb-row-insert", query: "invoice", values: { memo: "May" }, rows: { lines: [{ key: "1", values: { qty: "3" } }] } }
|
|
667
795
|
```
|
|
668
796
|
|
|
669
|
-
The hub sends `query`, `key` and `values` as present
|
|
797
|
+
The hub sends `query`, `key` and `values` as present, and `keys` in place of
|
|
798
|
+
`key` for a request acting on the rows selected — see
|
|
799
|
+
[Selecting rows](#selecting-rows).
|
|
670
800
|
|
|
671
801
|
#### Committing
|
|
672
802
|
|
|
@@ -734,7 +864,8 @@ controls belong to a form that cannot wrap a `<tr>`:
|
|
|
734
864
|
|
|
735
865
|
The hub gathers from controls only. It skips a control whose nearest
|
|
736
866
|
ancestor `lb-query` is a descendant of the live row or form, since that
|
|
737
|
-
control belongs to a nested query
|
|
867
|
+
control belongs to a nested query, except that a form gathers the rows of a
|
|
868
|
+
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
|
|
738
869
|
`lb-column` both, such as a picker, belongs to the row around it and is
|
|
739
870
|
gathered.
|
|
740
871
|
|
|
@@ -748,6 +879,61 @@ ancestor `lb-query` of the element carrying `lb-request`, and takes `key`
|
|
|
748
879
|
from that element's nearest ancestor row when that row is of the same
|
|
749
880
|
query.
|
|
750
881
|
|
|
882
|
+
#### A form over a set of rows
|
|
883
|
+
|
|
884
|
+
A form gathers the live rows of a query nested directly in its own query,
|
|
885
|
+
besides its own controls. Each goes as its key and the values of the
|
|
886
|
+
controls in it, under the nested query's name:
|
|
887
|
+
|
|
888
|
+
```
|
|
889
|
+
{ name: "lb-row-insert", query: "paychecks", values: { pay_date: "2026-10-02" },
|
|
890
|
+
rows: { deduction_types: [{ key: "401k", values: { amount: "50" } },
|
|
891
|
+
{ key: "dental", values: { amount: "" } }] } }
|
|
892
|
+
```
|
|
893
|
+
|
|
894
|
+
The form's own query is the one it carries, or else the nearest around it.
|
|
895
|
+
A nested query is gathered when its nearest ancestor `lb-query` is the
|
|
896
|
+
form's own, so one level only: a query nested deeper shows the same rows in
|
|
897
|
+
every row around it, and its keys would repeat. Every live row of it goes,
|
|
898
|
+
whatever its values, and only controls inside a live row of it.
|
|
899
|
+
|
|
900
|
+
An insert, an update and a declared request carry `rows`; a delete does
|
|
901
|
+
not. A form whose only controls are in nested rows sends for its own query,
|
|
902
|
+
keyed by the row landed on it or the row it is pointed at, with empty
|
|
903
|
+
`values`. A live row gathers nothing nested.
|
|
904
|
+
|
|
905
|
+
The handler for the form's query receives `rows` beside `values`, so the
|
|
906
|
+
master and its details are written together. The server checks that
|
|
907
|
+
`rows` holds string keys and string values by query, and ignores a request
|
|
908
|
+
whose `rows` does not.
|
|
909
|
+
|
|
910
|
+
A nested query answers once for the page, as anywhere — see
|
|
911
|
+
[Master-detail](#master-detail). The form enters a new master with its
|
|
912
|
+
details, or edits the master the page shows by a query parm, whose details
|
|
913
|
+
the nested query reads from the same parm. A pointed form fills its own
|
|
914
|
+
controls from the row it points at, and its nested rows stay the nested
|
|
915
|
+
query's answer.
|
|
916
|
+
|
|
917
|
+
#### A row not yet entered
|
|
918
|
+
|
|
919
|
+
A row whose key is the empty string is a new one, not yet entered. It
|
|
920
|
+
arises two ways: a `row` query answers with an empty key, as one reading
|
|
921
|
+
the master's key from an empty query parm does, or `--lb-new` points a form
|
|
922
|
+
(see [A form pointed at a row](#a-form-pointed-at-a-row)). Either way the
|
|
923
|
+
element carries `lb-key-value=""`.
|
|
924
|
+
|
|
925
|
+
The hub sends no `key` for it, whether the key came from where the element
|
|
926
|
+
sits or from a request a custom element dispatched. An `lb-row-insert` and
|
|
927
|
+
a declared request are sent without one. An `lb-row-update` and an
|
|
928
|
+
`lb-row-delete` are not sent, because nothing is on record to change, and
|
|
929
|
+
nothing is reported: it is the row's state, not a mistake. So a control
|
|
930
|
+
saving its own column on `change` saves on a row on record, and on a new
|
|
931
|
+
row only holds what a submit will gather. No ancestor sees a request not
|
|
932
|
+
sent, and it gets no `lb-request-done`.
|
|
933
|
+
|
|
934
|
+
An update or a delete with no row around it at all has no key either, and
|
|
935
|
+
is reported to the console, as any request missing what it needs is.
|
|
936
|
+
|
|
751
937
|
A field the request already carries is its issuer's and is kept. A custom
|
|
752
938
|
element that dispatches its own request with `values` is not gathered over.
|
|
753
939
|
|
|
@@ -759,7 +945,8 @@ element that dispatches its own request with `values` is not gathered over.
|
|
|
759
945
|
| `lb-row-update` | `query`, `key`, `values` | `rowUpdate` |
|
|
760
946
|
| `lb-row-delete` | `query`, `key` | `rowDelete` |
|
|
761
947
|
|
|
762
|
-
The hub issues one of these only when it has
|
|
948
|
+
`keys` stands in for `key`. The hub issues one of these only when it has
|
|
949
|
+
what the table lists. An
|
|
763
950
|
`lb-row-insert` or `lb-row-update` that gathers nothing is not issued. A
|
|
764
951
|
request that is not issued is reported to the console.
|
|
765
952
|
|
|
@@ -774,6 +961,125 @@ For a query the hub serves, the hub answers the request itself, with no
|
|
|
774
961
|
round trip. The hub serves `lb-url`, and answers `lb-row-update` for it —
|
|
775
962
|
see [Query parms](#query-parms).
|
|
776
963
|
|
|
964
|
+
#### A form pointed at a row
|
|
965
|
+
|
|
966
|
+
A set of rows the user edits or adds to has one form on the page, naming
|
|
967
|
+
the same query, outside every row. A button points it with HTML's
|
|
968
|
+
`commandfor`, naming the form's `id`, and `command`:
|
|
969
|
+
|
|
970
|
+
| Command | On a button | The hub |
|
|
971
|
+
| -------------- | --------------------------------- | ----------------------------------------- |
|
|
972
|
+
| `--lb-edit` | In a live row of the form's query | Fills the form from that row, keyed by it |
|
|
973
|
+
| `--lb-new` | Anywhere | Starts the form empty, for an insert |
|
|
974
|
+
| `--lb-release` | Anywhere | Lets the form go without saving |
|
|
975
|
+
|
|
976
|
+
HTML requires a custom command to begin with `--`, and fires it as a
|
|
977
|
+
`command` event on the form, which the hub hears.
|
|
978
|
+
|
|
979
|
+
```html
|
|
980
|
+
<tbody lb-query="paychecks">
|
|
981
|
+
<template><tr>
|
|
982
|
+
<td lb-column="pay_date"></td>
|
|
983
|
+
<td><button type="button" commandfor="paycheck" command="--lb-edit">Edit</button></td>
|
|
984
|
+
</tr></template>
|
|
985
|
+
</tbody>
|
|
986
|
+
<button type="button" commandfor="paycheck" command="--lb-new">New</button>
|
|
987
|
+
|
|
988
|
+
<dialog>
|
|
989
|
+
<form id="paycheck" lb-query="paychecks" lb-request="lb-row-update">
|
|
990
|
+
<input lb-column="pay_date" type="date" />
|
|
991
|
+
<button type="submit">Save</button>
|
|
992
|
+
<button type="submit" lb-request="lb-row-insert">Save new</button>
|
|
993
|
+
<button type="button" commandfor="paycheck" command="--lb-release">Cancel</button>
|
|
994
|
+
</form>
|
|
995
|
+
</dialog>
|
|
996
|
+
```
|
|
997
|
+
|
|
998
|
+
Which row a form edits is interaction state, not a view, so it is not a
|
|
999
|
+
query parm. The user edits only a row they can see, so Edit fills the form
|
|
1000
|
+
from the row as the hub holds it, every column the query answered, with no
|
|
1001
|
+
round trip. A row that must be fetched to be edited is another page, with
|
|
1002
|
+
that row as its master.
|
|
1003
|
+
|
|
1004
|
+
Pointing a form first empties it: every column it names lands as null, and
|
|
1005
|
+
the form resets, so each control returns to its default. Edit then lands
|
|
1006
|
+
the row on the form as on any element a `row` lands on, stamps the form
|
|
1007
|
+
`lb-key-value` with the row's key, and stamps the row `lb-row-editing`.
|
|
1008
|
+
Gathering then takes the key from the form. New stamps an empty key, making
|
|
1009
|
+
the form [a row not yet entered](#a-row-not-yet-entered). Both focus the
|
|
1010
|
+
form's first control.
|
|
1011
|
+
|
|
1012
|
+
| The form's `lb-key-value` | The form is |
|
|
1013
|
+
| ------------------------- | ------------------ |
|
|
1014
|
+
| A key | Editing that row |
|
|
1015
|
+
| Empty | Entering a new row |
|
|
1016
|
+
| Absent | Not in use |
|
|
1017
|
+
|
|
1018
|
+
A stylesheet shows the form's buttons by it, such as Save while it has a
|
|
1019
|
+
key and Save new while the key is empty.
|
|
1020
|
+
|
|
1021
|
+
A request from the form that succeeds lets it go, as `--lb-release` does:
|
|
1022
|
+
the form empties, and both stamps go. One that fails leaves the form
|
|
1023
|
+
pointed, as the user had it.
|
|
1024
|
+
|
|
1025
|
+
When the form is inside a `<dialog>`, Edit and New call `showModal()` on it,
|
|
1026
|
+
and letting go closes it. The dialog closing by any of HTML's means,
|
|
1027
|
+
Escape, `command="close"` or `method="dialog"`, lets the form go. A form in
|
|
1028
|
+
no dialog opens nothing; it is always showing, or a stylesheet shows it
|
|
1029
|
+
while it carries `lb-key-value`.
|
|
1030
|
+
|
|
1031
|
+
The form names a `rows` query and holds no row template, so that query's
|
|
1032
|
+
answers land nothing on it, and what the user typed survives a refresh.
|
|
1033
|
+
|
|
1034
|
+
A command the hub cannot carry out is reported to the console: Edit from
|
|
1035
|
+
outside a live row of the form's query, Edit for a key the hub holds no row
|
|
1036
|
+
for, and any command aimed at an element that is not a form carrying
|
|
1037
|
+
`lb-query`.
|
|
1038
|
+
|
|
1039
|
+
#### Selecting rows
|
|
1040
|
+
|
|
1041
|
+
A checkbox carrying `lb-select` in a row template selects the live row it
|
|
1042
|
+
sits in, and the hub stamps the row `lb-row-selected` while it is ticked.
|
|
1043
|
+
It is not a column: it carries no `lb-column` or `lb-request`, lands
|
|
1044
|
+
nothing, and is not gathered.
|
|
1045
|
+
|
|
1046
|
+
A request for the query that names no one row, neither from inside one of
|
|
1047
|
+
its rows nor from a form pointed at one, acts on the rows selected. When
|
|
1048
|
+
any `lb-select` checkbox of that query is showing, the hub adds the keys of
|
|
1049
|
+
those ticked as `keys`:
|
|
1050
|
+
|
|
1051
|
+
```
|
|
1052
|
+
{ name: "lb-row-update", query: "transactions", keys: ["4", "9"], values: { acct: "12" } }
|
|
1053
|
+
```
|
|
1054
|
+
|
|
1055
|
+
- `lb-row-update` and `lb-row-delete` are sent with `keys`, and with none
|
|
1056
|
+
ticked are not sent, silently, as for [a row not yet
|
|
1057
|
+
entered](#a-row-not-yet-entered).
|
|
1058
|
+
- A declared request is sent with `keys`, empty when none is ticked, and the
|
|
1059
|
+
handler decides.
|
|
1060
|
+
- `lb-row-insert` adds a row, and the selection is not its.
|
|
1061
|
+
- With no `lb-select` checkbox of the query showing, the request is an
|
|
1062
|
+
ordinary one, and carries no `keys`.
|
|
1063
|
+
|
|
1064
|
+
The server runs the page's `rowUpdate` or `rowDelete` once for each key,
|
|
1065
|
+
with that key, as for each row alone, so a handler written for one row
|
|
1066
|
+
serves several. It lays the patches the runs return together, and runs the
|
|
1067
|
+
refresh set once. A run returning `url()` is reported and ignored, since
|
|
1068
|
+
several rows cannot each choose one view. A declared request receives
|
|
1069
|
+
`keys` as sent.
|
|
1070
|
+
|
|
1071
|
+
After the request succeeds, the hub clears every `lb-select` checkbox of the
|
|
1072
|
+
query and the stamps with them. A failed request leaves the selection.
|
|
1073
|
+
|
|
1074
|
+
Only a checkbox that is showing counts: one parked by `lb-show` is in
|
|
1075
|
+
template content, out of reach. Parking one also clears its tick, so a
|
|
1076
|
+
selection never waits out of sight to return stale.
|
|
1077
|
+
|
|
1078
|
+
Which rows are selected is interaction state, as which row a form edits is.
|
|
1079
|
+
A selecting mode the page keeps in the URL reaches the rows as a column the
|
|
1080
|
+
query computes from the parm, which `lb-show` reads, since `lb-show` reads
|
|
1081
|
+
from the row it sits in.
|
|
1082
|
+
|
|
777
1083
|
#### The request event
|
|
778
1084
|
|
|
779
1085
|
The hub dispatches every request as the bubbling `lb-request` event before
|
|
@@ -817,6 +1123,46 @@ failed one.
|
|
|
817
1123
|
The hub ignores a commit on an element carrying `lb-request-pending`, so a
|
|
818
1124
|
pending element is disabled in fact and a stylesheet only has to show it.
|
|
819
1125
|
|
|
1126
|
+
#### Refusals
|
|
1127
|
+
|
|
1128
|
+
A handler refuses a request by throwing `refusal()`, exported by
|
|
1129
|
+
`@loadbare/app/server`, with words written for the user: a message for the
|
|
1130
|
+
request as a whole, a message per column, or both.
|
|
1131
|
+
|
|
1132
|
+
```ts
|
|
1133
|
+
throw refusal("That series already has paychecks entered.");
|
|
1134
|
+
throw refusal({ amount: "Must be more than zero" });
|
|
1135
|
+
throw refusal("Two fields need attention.", { amount: "…", pay_date: "…" });
|
|
1136
|
+
```
|
|
1137
|
+
|
|
1138
|
+
It is thrown rather than returned, so it may come from anything the handler
|
|
1139
|
+
calls, and rolls back whatever the application has open as any throw does.
|
|
1140
|
+
`hubRoutes` answers it with status 422 and `{ refusal: { message?, columns? } }`.
|
|
1141
|
+
Anything else thrown is a failure, answered 500 with no body, so no stack
|
|
1142
|
+
or database message reaches the browser.
|
|
1143
|
+
|
|
1144
|
+
The hub shows a refusal where it belongs:
|
|
1145
|
+
|
|
1146
|
+
- It stamps the element that committed `lb-request-refused`, whose value is
|
|
1147
|
+
the whole-request message, empty when there is none. A stylesheet shows
|
|
1148
|
+
it with `attr()`.
|
|
1149
|
+
- For each column, it calls `setCustomValidity(message)` on the control the
|
|
1150
|
+
request gathered that column from, and `reportValidity()` on the first,
|
|
1151
|
+
so the browser shows the message there and moves focus to it. A column
|
|
1152
|
+
the request gathered no such control for is reported to the console.
|
|
1153
|
+
- `lb-request-done` carries the refusal as `refusal`, beside `error`.
|
|
1154
|
+
|
|
1155
|
+
A refusal is not a failure: `lb-request-error` is not stamped and nothing is
|
|
1156
|
+
reported. It stands until the user edits any control the request gathered,
|
|
1157
|
+
a form holding them resets, or the element that committed sends again;
|
|
1158
|
+
then every message it put up is withdrawn at once. While a control carries
|
|
1159
|
+
one, the browser holds its form's submit, as for any invalid control.
|
|
1160
|
+
|
|
1161
|
+
A form-associated custom element takes a column's message when it defines
|
|
1162
|
+
`setCustomValidity` and `reportValidity`; one wrapping a native control
|
|
1163
|
+
passes both to it. A request for several rows takes the whole-request
|
|
1164
|
+
message only.
|
|
1165
|
+
|
|
820
1166
|
#### The round trip
|
|
821
1167
|
|
|
822
1168
|
A request the hub sends has ten seconds to come back. Past that the hub
|
|
@@ -825,7 +1171,8 @@ its own. An application cannot change the deadline.
|
|
|
825
1171
|
|
|
826
1172
|
A round trip fails on a server error, on a network failure, or on that
|
|
827
1173
|
deadline, and all three stamp `lb-request-error` on the element that issued
|
|
828
|
-
the request — see [Request state](#request-state).
|
|
1174
|
+
the request — see [Request state](#request-state). A refusal is not a
|
|
1175
|
+
failure — see [Refusals](#refusals).
|
|
829
1176
|
|
|
830
1177
|
After a successful `lb-row-insert` gathered from a form, the hub resets the
|
|
831
1178
|
form, after it lands the response. A failed insert resets nothing, so the
|
|
@@ -993,8 +1340,10 @@ names each one in the row it lands.
|
|
|
993
1340
|
|
|
994
1341
|
The hub sends the query parms with every round trip, a page load and a
|
|
995
1342
|
request alike. The server passes them to `contextFor` — see
|
|
996
|
-
[The Express server](#the-express-server).
|
|
997
|
-
|
|
1343
|
+
[The Express server](#the-express-server). Loadbare assigns one parm a
|
|
1344
|
+
meaning, `lb-order-<query>`, the user's [order](#order) for a query, and it
|
|
1345
|
+
reaches `contextFor` like any other. A request that changes only order
|
|
1346
|
+
parms of queries the hub sorts itself makes no round trip.
|
|
998
1347
|
|
|
999
1348
|
#### An unknown page
|
|
1000
1349
|
|
|
@@ -1058,6 +1407,29 @@ rowInsert: {
|
|
|
1058
1407
|
- A user who has moved by the time the response arrives, to another page or
|
|
1059
1408
|
to other query parms, keeps the URL they are on.
|
|
1060
1409
|
|
|
1410
|
+
`onPageEnter` may return `url()` the same way, to move the page before
|
|
1411
|
+
anything shows, as an application restoring the filters and order the user
|
|
1412
|
+
last had does. The page loads at the new URL in the same round trip, and
|
|
1413
|
+
that load does not follow `onPageEnter` again. A `url()` leading to the
|
|
1414
|
+
page and parms already in hand is ignored, so the page loads once. When to
|
|
1415
|
+
restore is the application's rule, such as only when the URL carries no
|
|
1416
|
+
parms of its own.
|
|
1417
|
+
|
|
1418
|
+
```ts
|
|
1419
|
+
// The application put the parms on ctx in contextFor.
|
|
1420
|
+
onPageEnter: async (ctx) => {
|
|
1421
|
+
if (ctx.parms.size > 0) return;
|
|
1422
|
+
const saved = await ctx.db.savedView("accounts");
|
|
1423
|
+
if (saved) return url(saved);
|
|
1424
|
+
},
|
|
1425
|
+
```
|
|
1426
|
+
|
|
1427
|
+
Saving what the user has on screen is a declared request with
|
|
1428
|
+
`refresh: []`, whose handler each page passes to a routine the
|
|
1429
|
+
application's pages share. A custom element naming `lb-url` sees every
|
|
1430
|
+
change of the URL and issues it, a change of order that made no round trip
|
|
1431
|
+
included.
|
|
1432
|
+
|
|
1061
1433
|
The page is loaded with a second context, built by `contextFor` from the new
|
|
1062
1434
|
parms — see [The Express server](#the-express-server).
|
|
1063
1435
|
|
|
@@ -1067,8 +1439,9 @@ The builder checks the chrome and every page, after expansion, and refuses
|
|
|
1067
1439
|
to build on any of these:
|
|
1068
1440
|
|
|
1069
1441
|
- An `lb-` attribute that is not a developer attribute: `lb-query`,
|
|
1070
|
-
`lb-column`, `lb-show`, `lb-
|
|
1071
|
-
`lb-
|
|
1442
|
+
`lb-column`, `lb-show`, `lb-group`, `lb-count`, `lb-sum`, `lb-avg`,
|
|
1443
|
+
`lb-min`, `lb-max`, `lb-request`, `lb-select`, `lb-url-link`,
|
|
1444
|
+
`lb-url-push`, `lb-url-unknown`. Every other `lb-` attribute is a stamp.
|
|
1072
1445
|
`lb-exp-slot` and `lb-exp-template` are consumed by expansion and never
|
|
1073
1446
|
reach the check.
|
|
1074
1447
|
- `lb-request` naming a value that begins with `lb-` and is not one of the
|
|
@@ -1080,6 +1453,23 @@ to build on any of these:
|
|
|
1080
1453
|
- `lb-show` on a `<template>`, on a row template's root, or with no
|
|
1081
1454
|
`lb-query` around it.
|
|
1082
1455
|
- `lb-show="!"` or `lb-show="!!column"`, which reverse no column.
|
|
1456
|
+
- `lb-select` on anything but an `<input type="checkbox">`, outside a row
|
|
1457
|
+
template, or beside `lb-column` or `lb-request`.
|
|
1458
|
+
- A `command` beginning `--lb-` that is not one of the three
|
|
1459
|
+
[commands](#a-form-pointed-at-a-row), and one of them with no
|
|
1460
|
+
`commandfor`.
|
|
1461
|
+
- `lb-group` on anything but a `<template>`, or on one holding no
|
|
1462
|
+
`<template>` for its contents.
|
|
1463
|
+
- An aggregate on a `<template>`, and any but `lb-count` naming no column.
|
|
1464
|
+
- A chrome without exactly one `<lb-hub>` and exactly one `<main>`, a
|
|
1465
|
+
`<main>` outside `<lb-hub>`, or a `<main>` with anything in it but
|
|
1466
|
+
whitespace.
|
|
1467
|
+
- A page carrying an `<lb-hub>` or a `<main>`.
|
|
1468
|
+
|
|
1469
|
+
The builder also reads the script of every custom element it ships, other
|
|
1470
|
+
than `<lb-hub>`, and refuses one that declares a method, accessor or field
|
|
1471
|
+
whose name begins with `lb` and a capital and is not in
|
|
1472
|
+
[Reserved methods](#reserved-methods).
|
|
1083
1473
|
|
|
1084
1474
|
`lb-column` with no ancestor row is allowed: the hub gathers from it.
|
|
1085
1475
|
|
|
@@ -1099,7 +1489,8 @@ where the standard landmarks go: header, nav, footer, main.
|
|
|
1099
1489
|
The file is an HTML document, which must contain:
|
|
1100
1490
|
- `<lb-hub>` inside `<body>`. The hub can only act on its children,
|
|
1101
1491
|
that is why it is usually nested right below `<body>`.
|
|
1102
|
-
- One empty `<main>` inside `<lb-hub>`.
|
|
1492
|
+
- One empty `<main>` inside `<lb-hub>`. The builder refuses a chrome
|
|
1493
|
+
without exactly one of each, and a page carrying either.
|
|
1103
1494
|
- The script tag for `/client.js`, the javascript bundle.
|
|
1104
1495
|
|
|
1105
1496
|
It may also contain:
|
|
@@ -1209,6 +1600,8 @@ The builder writes `pages.ts` into `--out` whenever any page has a
|
|
|
1209
1600
|
`.queries.ts` or a `.requests.ts` file. It imports each of them, calls
|
|
1210
1601
|
`createHub()` once over the lot, and exports the result as `hub`. The
|
|
1211
1602
|
server imports that rather than maintaining the registry by hand.
|
|
1603
|
+
`createHub` is the only implementation of the `Hub` interface, which may
|
|
1604
|
+
gain members in any release; an application never implements it.
|
|
1212
1605
|
|
|
1213
1606
|
```ts
|
|
1214
1607
|
// server.ts
|
|
@@ -1244,6 +1637,16 @@ Explaining Express is beyond the scope of this technical reference. The
|
|
|
1244
1637
|
only real requirement is that the catch-all for app.html is at the end,
|
|
1245
1638
|
so it does not catch any other files.
|
|
1246
1639
|
|
|
1640
|
+
`hubRoutes` mounts `express.json()` on its own routes, and takes that
|
|
1641
|
+
function's options under `json` in an optional third argument. The default
|
|
1642
|
+
limit of 100 KB refuses an oversized body with a 413 before any handler
|
|
1643
|
+
runs, and the server log names the option. A page that sends a file's text
|
|
1644
|
+
as a value needs more:
|
|
1645
|
+
|
|
1646
|
+
```ts
|
|
1647
|
+
app.use(hubRoutes(hub, contextFor, { json: { limit: "5mb" } }));
|
|
1648
|
+
```
|
|
1649
|
+
|
|
1247
1650
|
`hubRoutes` answers `POST /lb/<stub>`, with the query string the browser is
|
|
1248
1651
|
showing after it, verbatim. It hands `contextFor` that query string's parms
|
|
1249
1652
|
as a second argument, and `contextFor` puts on the context whatever a query
|
|
@@ -1273,7 +1676,8 @@ the whole path space through unchanged.
|
|
|
1273
1676
|
Each key is a query name, which the markup names with `lb-query`.
|
|
1274
1677
|
|
|
1275
1678
|
A query's kind and key belong to its name. Declare each query with
|
|
1276
|
-
`row(key, run)` or `rows(key, run)`, naming the key column first.
|
|
1679
|
+
`row(key, run)` or `rows(key, run, options)`, naming the key column first.
|
|
1680
|
+
A `rows` query's `options` declare its [order](#order). A query
|
|
1277
1681
|
cannot be built without one of these functions. Every query has a key; an
|
|
1278
1682
|
aggregate row answers with a constant one.
|
|
1279
1683
|
|
|
@@ -1291,14 +1695,15 @@ export const queries: Queries = {
|
|
|
1291
1695
|
```
|
|
1292
1696
|
|
|
1293
1697
|
A `row` query returns one object, and a `rows` query returns an array of
|
|
1294
|
-
objects
|
|
1698
|
+
objects, which the page shows in that order when the query declares none. An answer that disagrees with
|
|
1295
1699
|
its declared kind is left out of the response, with a warning naming the
|
|
1296
1700
|
query.
|
|
1297
1701
|
|
|
1298
1702
|
Query names cannot begin with `lb-`, that namespace is reserved for
|
|
1299
1703
|
Loadbare/app queries the hub serves in the browser, such as
|
|
1300
1704
|
[`lb-url`](#the-url). `createHub` refuses at startup a page that declares
|
|
1301
|
-
such a name as a query, a handler or a `crud` entry
|
|
1705
|
+
such a name as a query, a handler or a `crud` entry, and a query whose order
|
|
1706
|
+
has a term naming no column.
|
|
1302
1707
|
|
|
1303
1708
|
Every query and every handler receives `ctx`, the application's own request
|
|
1304
1709
|
context. The application builds it once per request and hands it to the hub,
|
|
@@ -1318,7 +1723,7 @@ It has three optional keys.
|
|
|
1318
1723
|
| ------------- | --------------------- | -------------------------------------------------- |
|
|
1319
1724
|
| `handlers` | The request name | The page's declared requests |
|
|
1320
1725
|
| `crud` | A query name | The three requests Loadbare provides |
|
|
1321
|
-
| `onPageEnter` | Nothing | Runs once on entering the page, before its queries |
|
|
1726
|
+
| `onPageEnter` | Nothing | Runs once on entering the page, before its queries, and may move the URL |
|
|
1322
1727
|
|
|
1323
1728
|
The server resolves a declared request against `handlers`, and a request
|
|
1324
1729
|
Loadbare provides against `crud` under the request's query. A name with no
|
|
@@ -1340,14 +1745,13 @@ and `values`, as present.
|
|
|
1340
1745
|
the refreshed ones. That is how a delta reaches the browser: wrap it in
|
|
1341
1746
|
`patch()`, naming the rows that arrived or changed and the keys that went.
|
|
1342
1747
|
|
|
1343
|
-
A refreshed `rows` query sends every row
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
never reorders the rest, so its handler always knows what changed:
|
|
1748
|
+
A refreshed `rows` query sends every row to say what changed. An update or
|
|
1749
|
+
a delete names its row by key, so its handler always knows what changed and
|
|
1750
|
+
answers with a patch, which the hub places by the query's [order](#order):
|
|
1347
1751
|
`createHub` refuses at startup a `crud` `rowUpdate` or `rowDelete` on a
|
|
1348
1752
|
`rows` query whose `refresh` names that same query. A `row` query sends one
|
|
1349
|
-
row and may refresh itself. An insert may refresh its own query
|
|
1350
|
-
|
|
1753
|
+
row and may refresh itself. An insert may refresh its own query, since its
|
|
1754
|
+
key exists only once the row does.
|
|
1351
1755
|
|
|
1352
1756
|
`run` may instead return `url()`, a new row for `lb-url`, naming query parms
|
|
1353
1757
|
only the write can know, such as the key of a row it inserted. The page
|
|
@@ -1409,11 +1813,16 @@ interface HubRequest {
|
|
|
1409
1813
|
name: string;
|
|
1410
1814
|
query?: string;
|
|
1411
1815
|
key?: string;
|
|
1816
|
+
keys?: string[];
|
|
1412
1817
|
values?: Record<string, string>;
|
|
1818
|
+
rows?: Record<string, { key: string; values: Record<string, string> }[]>;
|
|
1413
1819
|
}
|
|
1414
1820
|
```
|
|
1415
1821
|
|
|
1416
|
-
|
|
1822
|
+
A refused request is answered with status 422 and
|
|
1823
|
+
`{ refusal: { message?, columns? } }`, and any other failure with 500 and
|
|
1824
|
+
no body — see [Refusals](#refusals). Every other response is an array of
|
|
1825
|
+
response items:
|
|
1417
1826
|
|
|
1418
1827
|
```ts
|
|
1419
1828
|
interface ResponseItem {
|
|
@@ -1430,9 +1839,14 @@ A response item carries exactly one of `row`, `rows` or `patch`. A patch
|
|
|
1430
1839
|
answers a `rows` query only: rows named in `rows` are added or updated, keys
|
|
1431
1840
|
in `drop` are removed, and every other row is left alone.
|
|
1432
1841
|
|
|
1842
|
+
A `rows` item also carries `order` and `serverSortedByUrl` when the query
|
|
1843
|
+
declares them.
|
|
1844
|
+
|
|
1433
1845
|
`createHub(pages)` builds the `Hub`, which has two calls, both answering
|
|
1434
|
-
with response items: `dataForPage(page, ctx)` for a page load, and
|
|
1435
|
-
`runRequest(page, request, ctx)` for a request.
|
|
1846
|
+
with response items: `dataForPage(page, ctx, options)` for a page load, and
|
|
1847
|
+
`runRequest(page, request, ctx)` for a request. `options.parms` are the
|
|
1848
|
+
parms the page is loaded at, and `options.follow` false ignores a `url()`
|
|
1849
|
+
from `onPageEnter`; `hubRoutes` passes both.
|
|
1436
1850
|
|
|
1437
1851
|
## Custom elements
|
|
1438
1852
|
|
|
@@ -1468,9 +1882,9 @@ Each direction has one mechanism for each kind of message:
|
|
|
1468
1882
|
|
|
1469
1883
|
| Direction | What | How | Today |
|
|
1470
1884
|
| -------------- | ------------------------------------------- | ---------------------------- | ------------------------------------------------ |
|
|
1471
|
-
| Hub to element | State that lasts | An attribute the hub stamps | `lb-column-value`, `lb-key-value`, `lb-query-row-count`, `lb-request-pending`, `lb-request-error` |
|
|
1885
|
+
| 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` |
|
|
1472
1886
|
| Hub to element | State the platform already names | A property | A control's `value` |
|
|
1473
|
-
| Hub to element | Work the hub needs done now, while landing | An optional `lb` method | `
|
|
1887
|
+
| Hub to element | Work the hub needs done now, while landing | An optional `lb` method | `lbRowsLanded` |
|
|
1474
1888
|
| Hub to element | A moment an element started | A bubbling event | `lb-request-done` |
|
|
1475
1889
|
| Element to hub | A moment | A bubbling event | `lb-request`, and `change`, `click` and `submit` |
|
|
1476
1890
|
|
|
@@ -1512,8 +1926,9 @@ renders it; its content is never replaced.
|
|
|
1512
1926
|
| Shows a page | `constructor`, `connectedCallback` |
|
|
1513
1927
|
| Creates a live row, before placing it | `constructor` |
|
|
1514
1928
|
| Places a live row the first time | `connectedCallback` |
|
|
1515
|
-
|
|
|
1516
|
-
|
|
|
1929
|
+
| Moves a live row the order put elsewhere | `connectedMoveCallback` where the browser moves in place, otherwise `disconnectedCallback`, `connectedCallback` |
|
|
1930
|
+
| Leaves a copy of a moved row behind | The copy's `constructor`, `connectedCallback`, and later `disconnectedCallback` |
|
|
1931
|
+
| Removes a live row | `disconnectedCallback`, once its animations end |
|
|
1517
1932
|
| Turns `lb-show` off | `disconnectedCallback`, `adoptedCallback` |
|
|
1518
1933
|
| Turns `lb-show` on | `adoptedCallback`, `connectedCallback` |
|
|
1519
1934
|
| Turns on an element shipped absent | `constructor`, `connectedCallback` |
|
|
@@ -1525,19 +1940,27 @@ done once per instance goes behind a flag.
|
|
|
1525
1940
|
|
|
1526
1941
|
### Row hooks
|
|
1527
1942
|
|
|
1528
|
-
| Method
|
|
1529
|
-
|
|
|
1530
|
-
| `
|
|
1531
|
-
| `lbRowsLanded()` | Developer | After the rows land |
|
|
1943
|
+
| Method | Implemented By | The hub calls it |
|
|
1944
|
+
| ---------------- | -------------- | ------------------------------------------------------ |
|
|
1945
|
+
| `lbRowsLanded()` | Developer | After the rows land, with every row placed and stamped |
|
|
1532
1946
|
|
|
1533
|
-
The hub calls
|
|
1534
|
-
|
|
1535
|
-
|
|
1947
|
+
The hub calls it on a custom element with `lb-query` and a row template.
|
|
1948
|
+
It is optional. The `RowsHost` interface in `@loadbare/app/types` declares
|
|
1949
|
+
it. The hub places every row and builds every group itself, so a custom
|
|
1950
|
+
element reacts to rows and never places them. Scrolling to the row the
|
|
1951
|
+
page's request created is the case:
|
|
1952
|
+
|
|
1953
|
+
```ts
|
|
1954
|
+
lbRowsLanded() {
|
|
1955
|
+
this.querySelector(`[${ATTR_ROW_REQUESTED}="${REQUESTED_CREATED}"]`)
|
|
1956
|
+
?.scrollIntoView({ block: "nearest" });
|
|
1957
|
+
}
|
|
1958
|
+
```
|
|
1536
1959
|
|
|
1537
1960
|
A custom element the builder shipped absent under `lb-show` has not
|
|
1538
|
-
upgraded while its column is off
|
|
1539
|
-
|
|
1540
|
-
|
|
1961
|
+
upgraded while its column is off. When the column first turns on and it
|
|
1962
|
+
upgrades, the hub lands the query's last answer on it again, as all rows,
|
|
1963
|
+
so the hook runs.
|
|
1541
1964
|
|
|
1542
1965
|
`applyRow(root, row)`, exported by `@loadbare/app`, fills `root` from one
|
|
1543
1966
|
row, the same operation that fills a live row.
|
|
@@ -1617,6 +2040,7 @@ ownership of its behavior are stated separately, because they differ.
|
|
|
1617
2040
|
| Namespace | Applies to | Loadbare owns | Defined in |
|
|
1618
2041
|
| --------- | ----------------------------------------------------------- | --------------------------------------------------- | ----------------------------------------------- |
|
|
1619
2042
|
| `lb-*` | Attributes, their values, queries, columns of `lb-` queries, requests, custom elements, events | The names and their behavior | [The lb-* namespace](#the-lb--namespace) |
|
|
2043
|
+
| `--lb-*` | Values of HTML's `command` attribute | The names and their behavior | [Reserved commands](#reserved-commands) |
|
|
1620
2044
|
| `exp-*` | Attributes on a custom element | The behavior; element file authors pick the names | [Build time parameters](#build-time-parameters) |
|
|
1621
2045
|
| `lb*` | Methods on custom elements | The names and their behavior | [Row hooks](#row-hooks) |
|
|
1622
2046
|
|
|
@@ -1633,17 +2057,26 @@ it may define tomorrow.
|
|
|
1633
2057
|
| `lb-column` | Developer | Any element | [Landing](#landing) |
|
|
1634
2058
|
| `lb-show` | Developer | Any element but a `<template>` | [Displaying by condition](#displaying-by-condition) |
|
|
1635
2059
|
| `lb-request` | Developer | Any element | [Requests](#requests) |
|
|
2060
|
+
| `lb-select` | Developer | A checkbox in a row template | [Selecting rows](#selecting-rows) |
|
|
1636
2061
|
| `lb-url-link` | Developer | An `<a>` | [Links](#links) |
|
|
1637
2062
|
| `lb-url-push` | Developer | An element with `lb-request` | [Query parms](#query-parms) |
|
|
1638
2063
|
| `lb-url-unknown` | Developer | A `<dialog>` inside `<lb-hub>` | [An unknown page](#an-unknown-page) |
|
|
2064
|
+
| `lb-group` | Developer | A `<template>` holding a `<template>` | [Groups](#groups) |
|
|
2065
|
+
| `lb-count`, `lb-sum`, `lb-avg`, `lb-min`, `lb-max` | Developer | Any element but a `<template>` | [Aggregates](#aggregates) |
|
|
1639
2066
|
| `lb-exp-slot` | Developer | One element in an element file | [Slots and templates](#slots-and-templates) |
|
|
1640
2067
|
| `lb-exp-template` | Developer | An element file, and a `<template>` | [Slots and templates](#slots-and-templates) |
|
|
1641
2068
|
| `lb-column-value` | Hub | Every element set from a column | [How a column lands](#how-a-column-lands) |
|
|
1642
|
-
| `lb-key-value` | Hub | A live row,
|
|
2069
|
+
| `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) |
|
|
2070
|
+
| `lb-row-editing` | Hub | The live row a form is pointed at | [A form pointed at a row](#a-form-pointed-at-a-row) |
|
|
2071
|
+
| `lb-row-selected` | Hub | A live row its `lb-select` checkbox ticks | [Selecting rows](#selecting-rows) |
|
|
1643
2072
|
| `lb-row-live` | Hub | A live row | [Rows](#rows) |
|
|
1644
2073
|
| `lb-query-row-count` | Hub | An element with a row template | [Rows](#rows) |
|
|
2074
|
+
| `lb-group-live`, `lb-group-column`, `lb-group-value` | Hub | Each top-level element of a group | [Groups](#groups) |
|
|
2075
|
+
| `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) |
|
|
2076
|
+
| `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) |
|
|
1645
2077
|
| `lb-request-pending` | Hub | The element that issued a request | [Request state](#request-state) |
|
|
1646
2078
|
| `lb-request-error` | Hub | The element that issued a request | [Request state](#request-state) |
|
|
2079
|
+
| `lb-request-refused` | Hub | The element whose request was refused | [Refusals](#refusals) |
|
|
1647
2080
|
| `lb-show` on a `<template>` | Hub and builder | Where an absent element stands | [Displaying by condition](#displaying-by-condition) |
|
|
1648
2081
|
| `lb-page` | Builder | A page's `<template>` | [Page HTML](#page-html) |
|
|
1649
2082
|
| `lb-page-title` | Builder | A page's `<template>` | [Page HTML](#page-html) |
|
|
@@ -1657,6 +2090,12 @@ The builder refuses any other `lb-` attribute in markup — see
|
|
|
1657
2090
|
| -------- | ----- | --------- | --------------------------------------------------------------- | ------------------- |
|
|
1658
2091
|
| `lb-url` | `row` | `lb-path` | `lb-path`, `lb-page-label`, `lb-page-unknown`, every query parm | [The URL](#the-url) |
|
|
1659
2092
|
|
|
2093
|
+
### Reserved query parms
|
|
2094
|
+
|
|
2095
|
+
| Parm | Means | Defined in |
|
|
2096
|
+
| ------------------ | --------------------------- | --------------- |
|
|
2097
|
+
| `lb-order-<query>` | The user's order for a query | [Order](#order) |
|
|
2098
|
+
|
|
1660
2099
|
### Reserved request names
|
|
1661
2100
|
|
|
1662
2101
|
| Request name | Runs under `crud` | Defined in |
|
|
@@ -1665,6 +2104,17 @@ The builder refuses any other `lb-` attribute in markup — see
|
|
|
1665
2104
|
| `lb-row-update` | `rowUpdate` | [The request names](#the-request-names) |
|
|
1666
2105
|
| `lb-row-delete` | `rowDelete` | [The request names](#the-request-names) |
|
|
1667
2106
|
|
|
2107
|
+
### Reserved commands
|
|
2108
|
+
|
|
2109
|
+
Loadbare/app owns every value of HTML's `command` attribute beginning with
|
|
2110
|
+
`--lb-`.
|
|
2111
|
+
|
|
2112
|
+
| Command | Defined in |
|
|
2113
|
+
| -------------- | --------------------------------------------------- |
|
|
2114
|
+
| `--lb-edit` | [A form pointed at a row](#a-form-pointed-at-a-row) |
|
|
2115
|
+
| `--lb-new` | [A form pointed at a row](#a-form-pointed-at-a-row) |
|
|
2116
|
+
| `--lb-release` | [A form pointed at a row](#a-form-pointed-at-a-row) |
|
|
2117
|
+
|
|
1668
2118
|
### Reserved tags
|
|
1669
2119
|
|
|
1670
2120
|
Loadbare/app owns every tag in this table. An application writes them and
|
|
@@ -1688,7 +2138,6 @@ Loadbare/app owns every DOM event in this table, both the name and what its
|
|
|
1688
2138
|
|
|
1689
2139
|
| Method | On | Defined in |
|
|
1690
2140
|
| -------------- | -------------------------------------------------- | ----------------------- |
|
|
1691
|
-
| `lbPlaceRow` | A custom element with `lb-query` and a row template | [Row hooks](#row-hooks) |
|
|
1692
2141
|
| `lbRowsLanded` | A custom element with `lb-query` and a row template | [Row hooks](#row-hooks) |
|
|
1693
2142
|
|
|
1694
2143
|
### Constants
|
|
@@ -1706,9 +2155,33 @@ imports them rather than writing a string.
|
|
|
1706
2155
|
| `ATTR_ROW_LIVE` | `lb-row-live` |
|
|
1707
2156
|
| `LIVE_ROW` | `[lb-row-live]`, the selector for a live row |
|
|
1708
2157
|
| `ATTR_QUERY_ROW_COUNT` | `lb-query-row-count` |
|
|
2158
|
+
| `ATTR_GROUP` | `lb-group` |
|
|
2159
|
+
| `ATTR_GROUP_LIVE` | `lb-group-live` |
|
|
2160
|
+
| `ATTR_GROUP_COLUMN` | `lb-group-column` |
|
|
2161
|
+
| `ATTR_GROUP_VALUE` | `lb-group-value` |
|
|
2162
|
+
| `LIVE_GROUP` | `[lb-group-live]`, the selector for a group's element |
|
|
2163
|
+
| `ATTR_COUNT`, `ATTR_SUM`, `ATTR_AVG`, `ATTR_MIN`, `ATTR_MAX` | `lb-count`, `lb-sum`, `lb-avg`, `lb-min`, `lb-max` |
|
|
2164
|
+
| `AGGREGATES` | All five aggregate attributes |
|
|
2165
|
+
| `ORDER_PARM_PREFIX` | `lb-order-` |
|
|
2166
|
+
| `ORDER_DESCENDING` | `-`, written before a column in an order |
|
|
2167
|
+
| `ATTR_ROW_CREATED` | `lb-row-created` |
|
|
2168
|
+
| `ATTR_ROW_CHANGED` | `lb-row-changed` |
|
|
2169
|
+
| `ATTR_ROW_MOVED` | `lb-row-moved` |
|
|
2170
|
+
| `ATTR_ROW_REQUESTED` | `lb-row-requested` |
|
|
2171
|
+
| `REQUESTED_CREATED`, `REQUESTED_CHANGED`, `REQUESTED_MOVED` | `created`, `changed`, `moved` |
|
|
2172
|
+
| `ATTR_ROW_LEAVING` | `lb-row-leaving` |
|
|
2173
|
+
| `ATTR_GROUP_LEAVING` | `lb-group-leaving` |
|
|
2174
|
+
| `COMMAND_EDIT`, `COMMAND_NEW`, `COMMAND_RELEASE` | `--lb-edit`, `--lb-new`, `--lb-release` |
|
|
2175
|
+
| `LB_COMMANDS` | All three commands |
|
|
2176
|
+
| `LB_COMMAND_PREFIX` | `--lb-` |
|
|
2177
|
+
| `ATTR_ROW_EDITING` | `lb-row-editing` |
|
|
2178
|
+
| `ATTR_SELECT` | `lb-select` |
|
|
2179
|
+
| `ATTR_ROW_SELECTED` | `lb-row-selected` |
|
|
1709
2180
|
| `ATTR_REQUEST` | `lb-request` |
|
|
1710
2181
|
| `ATTR_REQUEST_PENDING` | `lb-request-pending` |
|
|
1711
2182
|
| `ATTR_REQUEST_ERROR` | `lb-request-error` |
|
|
2183
|
+
| `ATTR_REQUEST_REFUSED` | `lb-request-refused` |
|
|
2184
|
+
| `LB_REFUSAL_STATUS` | `422` |
|
|
1712
2185
|
| `ATTR_URL_LINK` | `lb-url-link` |
|
|
1713
2186
|
| `ATTR_URL_PUSH` | `lb-url-push` |
|
|
1714
2187
|
| `ATTR_URL_UNKNOWN` | `lb-url-unknown` |
|
|
@@ -1716,7 +2189,7 @@ imports them rather than writing a string.
|
|
|
1716
2189
|
| `ATTR_EXP_TEMPLATE` | `lb-exp-template` |
|
|
1717
2190
|
| `ATTR_PAGE` | `lb-page` |
|
|
1718
2191
|
| `ATTR_PAGE_TITLE` | `lb-page-title` |
|
|
1719
|
-
| `DEVELOPER_ATTRIBUTES` | The
|
|
2192
|
+
| `DEVELOPER_ATTRIBUTES` | The fourteen attributes a developer writes |
|
|
1720
2193
|
| `LB_EVENT_NAME` | `lb-request` |
|
|
1721
2194
|
| `LB_DONE_EVENT_NAME` | `lb-request-done` |
|
|
1722
2195
|
| `SHOW_NOT` | `!`, written before a column in `lb-show` |
|