@loadbare/app 0.13.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 +31 -1
- package/dist/build/assemble.js.map +1 -1
- package/dist/core/lb-constants.d.ts +11 -1
- package/dist/core/lb-constants.d.ts.map +1 -1
- package/dist/core/lb-constants.js +34 -0
- package/dist/core/lb-constants.js.map +1 -1
- package/dist/core/lb-types.d.ts +27 -0
- package/dist/core/lb-types.d.ts.map +1 -1
- package/dist/core/lb-types.js.map +1 -1
- package/dist/hub/lb-apply.d.ts +20 -3
- package/dist/hub/lb-apply.d.ts.map +1 -1
- package/dist/hub/lb-apply.js +64 -8
- 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 +341 -21
- package/dist/hub/lb-hub.browser.js.map +1 -1
- package/dist/server/lb-express.d.ts +13 -3
- package/dist/server/lb-express.d.ts.map +1 -1
- package/dist/server/lb-express.js +30 -12
- package/dist/server/lb-express.js.map +1 -1
- package/dist/server/lb-server.d.ts +31 -1
- package/dist/server/lb-server.d.ts.map +1 -1
- package/dist/server/lb-server.js +112 -2
- package/dist/server/lb-server.js.map +1 -1
- package/docs/TECHREF-1.0.md +294 -21
- package/docs/comparison.md +6 -4
- package/docs/possible-ideas.md +146 -26
- package/docs/reference/custom-elements.md +97 -1
- package/docs/reference/data-binding.md +303 -4
- package/docs/reference/page-files.md +76 -3
- package/docs/reference/server.md +20 -0
- package/docs/reference/widgets.md +33 -10
- package/docs/roadmap.md +9 -2
- package/package.json +1 -1
- package/skills/loadbare-app/SKILL.md +50 -7
- package/skills/loadbare-app/references/TECHREF-1.0.md +294 -21
- package/skills/loadbare-app/references/custom-elements.md +97 -1
- package/skills/loadbare-app/references/data-binding.md +303 -4
- package/skills/loadbare-app/references/page-files.md +76 -3
- package/skills/loadbare-app/references/server.md +20 -0
- package/skills/loadbare-app/references/widgets.md +33 -10
|
@@ -31,10 +31,9 @@ handle.
|
|
|
31
31
|
|
|
32
32
|
### Form controls
|
|
33
33
|
|
|
34
|
-
- **
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
radio group is several elements answering to one column.
|
|
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.
|
|
38
37
|
|
|
39
38
|
### Custom element hooks
|
|
40
39
|
|
|
@@ -70,10 +69,6 @@ But the allowed values may depend on other values in the row.
|
|
|
70
69
|
|
|
71
70
|
### The server API
|
|
72
71
|
|
|
73
|
-
- **Decide the options-argument shape once.** Global hooks, transaction
|
|
74
|
-
wrapping, error handling and a CSRF token all want the same trailing
|
|
75
|
-
parameter on `createHub` and `hubRoutes`. Build none of them, but pick
|
|
76
|
-
where they go.
|
|
77
72
|
- **Land `staticRoutes`.** It closes the last place where an
|
|
78
73
|
application hand-writes facts the builder owns.
|
|
79
74
|
|
|
@@ -457,7 +452,8 @@ A query that arrives with nowhere to land is reported to the console.
|
|
|
457
452
|
| Any other element | Its text content |
|
|
458
453
|
|
|
459
454
|
A control is an `<input>`, `<select>` or `<textarea>`, or a form-associated
|
|
460
|
-
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
|
|
461
457
|
form-associated custom element declares `static formAssociated = true`.
|
|
462
458
|
|
|
463
459
|
The hub stamps `lb-column-value` with the value on every element it sets.
|
|
@@ -468,9 +464,21 @@ renders `lb-column-value` itself.
|
|
|
468
464
|
The hub gathers from the same controls it lands on, so a value read back is
|
|
469
465
|
the one that landed.
|
|
470
466
|
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
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.
|
|
474
482
|
|
|
475
483
|
A `<select>` with no option for the value shows no selection.
|
|
476
484
|
|
|
@@ -782,9 +790,13 @@ Every request has one shape, on the event and on the wire:
|
|
|
782
790
|
{ name: "lb-row-update", query: "roster", key: "42", values: { name: "Ann" } }
|
|
783
791
|
{ name: "lb-row-delete", query: "roster", key: "42" }
|
|
784
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" } }] } }
|
|
785
795
|
```
|
|
786
796
|
|
|
787
|
-
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).
|
|
788
800
|
|
|
789
801
|
#### Committing
|
|
790
802
|
|
|
@@ -852,7 +864,8 @@ controls belong to a form that cannot wrap a `<tr>`:
|
|
|
852
864
|
|
|
853
865
|
The hub gathers from controls only. It skips a control whose nearest
|
|
854
866
|
ancestor `lb-query` is a descendant of the live row or form, since that
|
|
855
|
-
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
|
|
856
869
|
`lb-column` both, such as a picker, belongs to the row around it and is
|
|
857
870
|
gathered.
|
|
858
871
|
|
|
@@ -866,6 +879,61 @@ ancestor `lb-query` of the element carrying `lb-request`, and takes `key`
|
|
|
866
879
|
from that element's nearest ancestor row when that row is of the same
|
|
867
880
|
query.
|
|
868
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
|
+
|
|
869
937
|
A field the request already carries is its issuer's and is kept. A custom
|
|
870
938
|
element that dispatches its own request with `values` is not gathered over.
|
|
871
939
|
|
|
@@ -877,7 +945,8 @@ element that dispatches its own request with `values` is not gathered over.
|
|
|
877
945
|
| `lb-row-update` | `query`, `key`, `values` | `rowUpdate` |
|
|
878
946
|
| `lb-row-delete` | `query`, `key` | `rowDelete` |
|
|
879
947
|
|
|
880
|
-
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
|
|
881
950
|
`lb-row-insert` or `lb-row-update` that gathers nothing is not issued. A
|
|
882
951
|
request that is not issued is reported to the console.
|
|
883
952
|
|
|
@@ -892,6 +961,125 @@ For a query the hub serves, the hub answers the request itself, with no
|
|
|
892
961
|
round trip. The hub serves `lb-url`, and answers `lb-row-update` for it —
|
|
893
962
|
see [Query parms](#query-parms).
|
|
894
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
|
+
|
|
895
1083
|
#### The request event
|
|
896
1084
|
|
|
897
1085
|
The hub dispatches every request as the bubbling `lb-request` event before
|
|
@@ -935,6 +1123,46 @@ failed one.
|
|
|
935
1123
|
The hub ignores a commit on an element carrying `lb-request-pending`, so a
|
|
936
1124
|
pending element is disabled in fact and a stylesheet only has to show it.
|
|
937
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
|
+
|
|
938
1166
|
#### The round trip
|
|
939
1167
|
|
|
940
1168
|
A request the hub sends has ten seconds to come back. Past that the hub
|
|
@@ -943,7 +1171,8 @@ its own. An application cannot change the deadline.
|
|
|
943
1171
|
|
|
944
1172
|
A round trip fails on a server error, on a network failure, or on that
|
|
945
1173
|
deadline, and all three stamp `lb-request-error` on the element that issued
|
|
946
|
-
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).
|
|
947
1176
|
|
|
948
1177
|
After a successful `lb-row-insert` gathered from a form, the hub resets the
|
|
949
1178
|
form, after it lands the response. A failed insert resets nothing, so the
|
|
@@ -1211,8 +1440,8 @@ to build on any of these:
|
|
|
1211
1440
|
|
|
1212
1441
|
- An `lb-` attribute that is not a developer attribute: `lb-query`,
|
|
1213
1442
|
`lb-column`, `lb-show`, `lb-group`, `lb-count`, `lb-sum`, `lb-avg`,
|
|
1214
|
-
`lb-min`, `lb-max`, `lb-request`, `lb-
|
|
1215
|
-
`lb-url-unknown`. Every other `lb-` attribute is a stamp.
|
|
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.
|
|
1216
1445
|
`lb-exp-slot` and `lb-exp-template` are consumed by expansion and never
|
|
1217
1446
|
reach the check.
|
|
1218
1447
|
- `lb-request` naming a value that begins with `lb-` and is not one of the
|
|
@@ -1224,6 +1453,11 @@ to build on any of these:
|
|
|
1224
1453
|
- `lb-show` on a `<template>`, on a row template's root, or with no
|
|
1225
1454
|
`lb-query` around it.
|
|
1226
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`.
|
|
1227
1461
|
- `lb-group` on anything but a `<template>`, or on one holding no
|
|
1228
1462
|
`<template>` for its contents.
|
|
1229
1463
|
- An aggregate on a `<template>`, and any but `lb-count` naming no column.
|
|
@@ -1403,6 +1637,16 @@ Explaining Express is beyond the scope of this technical reference. The
|
|
|
1403
1637
|
only real requirement is that the catch-all for app.html is at the end,
|
|
1404
1638
|
so it does not catch any other files.
|
|
1405
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
|
+
|
|
1406
1650
|
`hubRoutes` answers `POST /lb/<stub>`, with the query string the browser is
|
|
1407
1651
|
showing after it, verbatim. It hands `contextFor` that query string's parms
|
|
1408
1652
|
as a second argument, and `contextFor` puts on the context whatever a query
|
|
@@ -1569,11 +1813,16 @@ interface HubRequest {
|
|
|
1569
1813
|
name: string;
|
|
1570
1814
|
query?: string;
|
|
1571
1815
|
key?: string;
|
|
1816
|
+
keys?: string[];
|
|
1572
1817
|
values?: Record<string, string>;
|
|
1818
|
+
rows?: Record<string, { key: string; values: Record<string, string> }[]>;
|
|
1573
1819
|
}
|
|
1574
1820
|
```
|
|
1575
1821
|
|
|
1576
|
-
|
|
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:
|
|
1577
1826
|
|
|
1578
1827
|
```ts
|
|
1579
1828
|
interface ResponseItem {
|
|
@@ -1791,6 +2040,7 @@ ownership of its behavior are stated separately, because they differ.
|
|
|
1791
2040
|
| Namespace | Applies to | Loadbare owns | Defined in |
|
|
1792
2041
|
| --------- | ----------------------------------------------------------- | --------------------------------------------------- | ----------------------------------------------- |
|
|
1793
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) |
|
|
1794
2044
|
| `exp-*` | Attributes on a custom element | The behavior; element file authors pick the names | [Build time parameters](#build-time-parameters) |
|
|
1795
2045
|
| `lb*` | Methods on custom elements | The names and their behavior | [Row hooks](#row-hooks) |
|
|
1796
2046
|
|
|
@@ -1807,6 +2057,7 @@ it may define tomorrow.
|
|
|
1807
2057
|
| `lb-column` | Developer | Any element | [Landing](#landing) |
|
|
1808
2058
|
| `lb-show` | Developer | Any element but a `<template>` | [Displaying by condition](#displaying-by-condition) |
|
|
1809
2059
|
| `lb-request` | Developer | Any element | [Requests](#requests) |
|
|
2060
|
+
| `lb-select` | Developer | A checkbox in a row template | [Selecting rows](#selecting-rows) |
|
|
1810
2061
|
| `lb-url-link` | Developer | An `<a>` | [Links](#links) |
|
|
1811
2062
|
| `lb-url-push` | Developer | An element with `lb-request` | [Query parms](#query-parms) |
|
|
1812
2063
|
| `lb-url-unknown` | Developer | A `<dialog>` inside `<lb-hub>` | [An unknown page](#an-unknown-page) |
|
|
@@ -1815,7 +2066,9 @@ it may define tomorrow.
|
|
|
1815
2066
|
| `lb-exp-slot` | Developer | One element in an element file | [Slots and templates](#slots-and-templates) |
|
|
1816
2067
|
| `lb-exp-template` | Developer | An element file, and a `<template>` | [Slots and templates](#slots-and-templates) |
|
|
1817
2068
|
| `lb-column-value` | Hub | Every element set from a column | [How a column lands](#how-a-column-lands) |
|
|
1818
|
-
| `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) |
|
|
1819
2072
|
| `lb-row-live` | Hub | A live row | [Rows](#rows) |
|
|
1820
2073
|
| `lb-query-row-count` | Hub | An element with a row template | [Rows](#rows) |
|
|
1821
2074
|
| `lb-group-live`, `lb-group-column`, `lb-group-value` | Hub | Each top-level element of a group | [Groups](#groups) |
|
|
@@ -1823,6 +2076,7 @@ it may define tomorrow.
|
|
|
1823
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) |
|
|
1824
2077
|
| `lb-request-pending` | Hub | The element that issued a request | [Request state](#request-state) |
|
|
1825
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) |
|
|
1826
2080
|
| `lb-show` on a `<template>` | Hub and builder | Where an absent element stands | [Displaying by condition](#displaying-by-condition) |
|
|
1827
2081
|
| `lb-page` | Builder | A page's `<template>` | [Page HTML](#page-html) |
|
|
1828
2082
|
| `lb-page-title` | Builder | A page's `<template>` | [Page HTML](#page-html) |
|
|
@@ -1850,6 +2104,17 @@ The builder refuses any other `lb-` attribute in markup — see
|
|
|
1850
2104
|
| `lb-row-update` | `rowUpdate` | [The request names](#the-request-names) |
|
|
1851
2105
|
| `lb-row-delete` | `rowDelete` | [The request names](#the-request-names) |
|
|
1852
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
|
+
|
|
1853
2118
|
### Reserved tags
|
|
1854
2119
|
|
|
1855
2120
|
Loadbare/app owns every tag in this table. An application writes them and
|
|
@@ -1906,9 +2171,17 @@ imports them rather than writing a string.
|
|
|
1906
2171
|
| `REQUESTED_CREATED`, `REQUESTED_CHANGED`, `REQUESTED_MOVED` | `created`, `changed`, `moved` |
|
|
1907
2172
|
| `ATTR_ROW_LEAVING` | `lb-row-leaving` |
|
|
1908
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` |
|
|
1909
2180
|
| `ATTR_REQUEST` | `lb-request` |
|
|
1910
2181
|
| `ATTR_REQUEST_PENDING` | `lb-request-pending` |
|
|
1911
2182
|
| `ATTR_REQUEST_ERROR` | `lb-request-error` |
|
|
2183
|
+
| `ATTR_REQUEST_REFUSED` | `lb-request-refused` |
|
|
2184
|
+
| `LB_REFUSAL_STATUS` | `422` |
|
|
1912
2185
|
| `ATTR_URL_LINK` | `lb-url-link` |
|
|
1913
2186
|
| `ATTR_URL_PUSH` | `lb-url-push` |
|
|
1914
2187
|
| `ATTR_URL_UNKNOWN` | `lb-url-unknown` |
|
|
@@ -1916,7 +2189,7 @@ imports them rather than writing a string.
|
|
|
1916
2189
|
| `ATTR_EXP_TEMPLATE` | `lb-exp-template` |
|
|
1917
2190
|
| `ATTR_PAGE` | `lb-page` |
|
|
1918
2191
|
| `ATTR_PAGE_TITLE` | `lb-page-title` |
|
|
1919
|
-
| `DEVELOPER_ATTRIBUTES` | The
|
|
2192
|
+
| `DEVELOPER_ATTRIBUTES` | The fourteen attributes a developer writes |
|
|
1920
2193
|
| `LB_EVENT_NAME` | `lb-request` |
|
|
1921
2194
|
| `LB_DONE_EVENT_NAME` | `lb-request-done` |
|
|
1922
2195
|
| `SHOW_NOT` | `!`, written before a column in `lb-show` |
|
|
@@ -412,9 +412,104 @@ including through the HTML `form` attribute, so a form gathers it on submit.
|
|
|
412
412
|
Write `formResetCallback` to restore the default value: the hub resets a form
|
|
413
413
|
after a successful insert.
|
|
414
414
|
|
|
415
|
+
A control takes the message of a refusal naming its column when it has
|
|
416
|
+
`setCustomValidity` and `reportValidity`, as a native control does; see
|
|
417
|
+
[Refusals](./data-binding.md#refusals). A control wrapping a native one
|
|
418
|
+
passes both to it, so the browser shows the message there and focus lands
|
|
419
|
+
there.
|
|
420
|
+
|
|
415
421
|
The `<lb-input>`, `<lb-select>`, `<lb-options>` and `<lb-picker>` widgets
|
|
416
422
|
are controls; see [The Basic Widget Library](./widgets.md).
|
|
417
423
|
|
|
424
|
+
### Example: a file's text as a value
|
|
425
|
+
|
|
426
|
+
The hub takes no file input as a control. A custom element wraps one and is
|
|
427
|
+
the control in its place. This one reads the chosen file as text, which
|
|
428
|
+
suits a CSV or a bank statement, and sends the file's name under a second
|
|
429
|
+
column:
|
|
430
|
+
|
|
431
|
+
```html
|
|
432
|
+
<!-- src/file-text.html -->
|
|
433
|
+
<label>{{label}} <input type="file" accept="{{accept}}" /></label>
|
|
434
|
+
<input type="hidden" lb-column="{{name-column}}" />
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
```ts
|
|
438
|
+
// src/file-text.browser.ts
|
|
439
|
+
class FileText extends HTMLElement {
|
|
440
|
+
static formAssociated = true;
|
|
441
|
+
#internals = this.attachInternals();
|
|
442
|
+
#value = "";
|
|
443
|
+
|
|
444
|
+
constructor() {
|
|
445
|
+
super();
|
|
446
|
+
this.addEventListener("change", (e) => {
|
|
447
|
+
const input = e.target as HTMLInputElement;
|
|
448
|
+
if (e.target === this || input.type !== "file") return;
|
|
449
|
+
e.stopPropagation();
|
|
450
|
+
void this.read(input);
|
|
451
|
+
});
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
get value(): string {
|
|
455
|
+
return this.#value;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
set value(value: string) {
|
|
459
|
+
this.#value = value ?? "";
|
|
460
|
+
this.#internals.setFormValue(this.#value);
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
formResetCallback() {
|
|
464
|
+
this.value = "";
|
|
465
|
+
this.setName("");
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
async read(input: HTMLInputElement) {
|
|
469
|
+
const file = input.files?.[0] ?? null;
|
|
470
|
+
this.value = file ? await file.text() : "";
|
|
471
|
+
this.setName(file?.name ?? "");
|
|
472
|
+
this.dispatchEvent(new Event("change", { bubbles: true }));
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
setName(name: string) {
|
|
476
|
+
const hidden = this.querySelector<HTMLInputElement>('input[type="hidden"]');
|
|
477
|
+
if (hidden) hidden.value = name;
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
customElements.define("file-text", FileText);
|
|
482
|
+
```
|
|
483
|
+
|
|
484
|
+
```html
|
|
485
|
+
<!-- src/uploads.page.html -->
|
|
486
|
+
<form lb-query="uploads" lb-request="lb-row-insert">
|
|
487
|
+
<file-text
|
|
488
|
+
lb-column="filetext"
|
|
489
|
+
exp-name-column="filename"
|
|
490
|
+
exp-label="Statement file"
|
|
491
|
+
exp-accept=".csv,.ofx"
|
|
492
|
+
></file-text>
|
|
493
|
+
<button type="submit">Upload</button>
|
|
494
|
+
</form>
|
|
495
|
+
```
|
|
496
|
+
|
|
497
|
+
Reading a file is asynchronous, so the element stops the file input's own
|
|
498
|
+
`change` and fires its own once the text is in hand. A form submitted before
|
|
499
|
+
then gathers the empty value, and the handler refuses it as no file.
|
|
500
|
+
|
|
501
|
+
A control gathers one column, so the name rides in a hidden input that the
|
|
502
|
+
hub gathers as a control of its own. The form gathers both on submit. Its
|
|
503
|
+
reset after the insert clears the file input but not the hidden one, whose
|
|
504
|
+
`value` is its default, so `formResetCallback` clears the name itself.
|
|
505
|
+
|
|
506
|
+
The text travels in the request's JSON body, which `hubRoutes` limits to
|
|
507
|
+
100 KB unless the server raises it; see [Options](./server.md#options).
|
|
508
|
+
|
|
509
|
+
The choices here are this application's: text rather than binary, the whole
|
|
510
|
+
file in the request, the name in a second column. A different application
|
|
511
|
+
writes its own element, under its own name, to make different ones.
|
|
512
|
+
|
|
418
513
|
### Receiving a value
|
|
419
514
|
|
|
420
515
|
A custom element that is not a control keeps the content the builder placed
|
|
@@ -494,7 +589,8 @@ this.addEventListener(LB_DONE_EVENT_NAME, (e) => {
|
|
|
494
589
|
```
|
|
495
590
|
|
|
496
591
|
`items` is every response item that landed because of the request, in
|
|
497
|
-
order.
|
|
592
|
+
order. `refusal` is set beside `error` when the handler refused the
|
|
593
|
+
request, holding its `message` and `columns`. An answer that moved the URL contributes its `lb-url` item, which
|
|
498
594
|
carries a key only the server knew, and the page load. `error` is set when
|
|
499
595
|
the round trip failed, and then nothing landed. A new row's key is in the
|
|
500
596
|
answer only when the handler returned it in a patch: a refresh sends all
|