@loadbare/app 0.7.3 → 0.8.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 +67 -6
- package/dist/build/assemble.js.map +1 -1
- package/dist/core/lb-constants.d.ts +2 -2
- package/dist/core/lb-constants.d.ts.map +1 -1
- package/dist/core/lb-constants.js +28 -14
- package/dist/core/lb-constants.js.map +1 -1
- package/dist/core/lb-types.d.ts +4 -10
- 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 +2 -2
- package/dist/hub/lb-apply.d.ts.map +1 -1
- package/dist/hub/lb-apply.js +98 -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 +61 -26
- package/dist/hub/lb-hub.browser.js.map +1 -1
- package/dist/server/lb-express.d.ts.map +1 -1
- package/dist/server/lb-express.js +2 -7
- package/dist/server/lb-express.js.map +1 -1
- package/dist/server/lb-server.d.ts +8 -15
- package/dist/server/lb-server.d.ts.map +1 -1
- package/dist/server/lb-server.js +0 -3
- package/dist/server/lb-server.js.map +1 -1
- package/docs/TECHREF-1.0.md +141 -100
- package/docs/analysis-closed-set.md +210 -0
- package/docs/comparison.md +1124 -0
- package/docs/prior-art.md +216 -0
- package/docs/reference/custom-elements.md +21 -6
- package/docs/reference/data-binding.md +120 -40
- package/docs/reference/page-files.md +13 -9
- package/docs/reference/widgets.md +2 -2
- package/docs/roadmap.md +1 -1
- package/docs/testing.md +7 -1
- package/docs/theory.md +737 -408
- package/docs/tutorials/080-widget-requests.md +9 -28
- package/package.json +1 -1
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
# Prior art
|
|
2
|
+
|
|
3
|
+
> **LLM-researched, not yet revised by a person.** Compiled by Claude on
|
|
4
|
+
> 2026-09-16 from web sources, against `@loadbare/app` 0.7.3, while designing
|
|
5
|
+
> conditional rendering. The sources are listed under each section.
|
|
6
|
+
|
|
7
|
+
Loadbare binds HTML to data by attributes and lets the platform do the DOM
|
|
8
|
+
work. That idea has been tried before. This document records the two
|
|
9
|
+
closest predecessors, where Loadbare agrees with them and where it parts
|
|
10
|
+
ways, and what other frameworks do about conditional rendering.
|
|
11
|
+
|
|
12
|
+
## The short version
|
|
13
|
+
|
|
14
|
+
Loadbare is not Model-Driven Views, although MDV is the better-known name.
|
|
15
|
+
MDV's model is a mutable object graph in the browser that the view observes.
|
|
16
|
+
Loadbare has no model in the browser: the view is driven by server responses
|
|
17
|
+
that say what changed.
|
|
18
|
+
|
|
19
|
+
The closer ancestor is Internet Explorer 4's data binding, from 1997, whose
|
|
20
|
+
vocabulary of record sets, single records and columns maps almost one to one
|
|
21
|
+
onto Loadbare's.
|
|
22
|
+
|
|
23
|
+
Loadbare can be described as IE4's relational binding vocabulary, on the
|
|
24
|
+
`<template>` element that MDV left behind, with the server as the model.
|
|
25
|
+
Andromeda (2003), where Loadbare began, sits between the two.
|
|
26
|
+
|
|
27
|
+
## Internet Explorer 4 data binding (1997)
|
|
28
|
+
|
|
29
|
+
Internet Explorer 4.0 bound HTML to data through attributes, built from four
|
|
30
|
+
parts: a data source object (DSO), data consumers, a binding agent and a
|
|
31
|
+
table repetition agent. A DSO might take "an Open Database Connectivity
|
|
32
|
+
(ODBC) connection string and an Structured Query Language (SQL) statement,"
|
|
33
|
+
and had to expose its data through OLE DB.
|
|
34
|
+
|
|
35
|
+
| IE4 | Loadbare |
|
|
36
|
+
|-----------------------------------------------------------------------------|-----------------------|
|
|
37
|
+
| `DATASRC` on a table repeats "an entire set of records" ("set binding") | `lb-list` |
|
|
38
|
+
| A single-valued consumer takes one value "from the current record" | `lb-row` |
|
|
39
|
+
| `DATAFLD` names "a column in the data set" | `lb-cell` |
|
|
40
|
+
| The repetition agent "uses the table row (tr) in the table body as a template" | `<template lb-key>` |
|
|
41
|
+
| The DSO's SQL statement | a page's queries |
|
|
42
|
+
| The binding agent "work[s] completely behind the scenes" | the hub |
|
|
43
|
+
|
|
44
|
+
It differed from Loadbare in three ways:
|
|
45
|
+
|
|
46
|
+
- **The data lived in the browser.** "Since the DSO maintains the data on the
|
|
47
|
+
client, it also manages how the data is sorted and filtered."
|
|
48
|
+
- **Binding was two-way.** "When a user updates a databound element on the
|
|
49
|
+
page, the binding agent notifies the DSO." Loadbare sends a request and
|
|
50
|
+
lands the answer.
|
|
51
|
+
- **It was proprietary.** A DSO was an ActiveX control or applet, and the
|
|
52
|
+
feature ended with Internet Explorer.
|
|
53
|
+
|
|
54
|
+
Sources:
|
|
55
|
+
[About Data Binding Architecture](https://learn.microsoft.com/en-us/previous-versions/windows/internet-explorer/ie-developer/platform-apis/ms531384(v=vs.85)),
|
|
56
|
+
[Using the Tabular Data Control](https://www.sitepoint.com/control-internet-explorer/).
|
|
57
|
+
|
|
58
|
+
## Model-Driven Views (2011–2015)
|
|
59
|
+
|
|
60
|
+
### What it was
|
|
61
|
+
|
|
62
|
+
Rafael Weinstein proposed MDV to the W3C WebApps group in April 2011:
|
|
63
|
+
"Myself and a few other chromium folks have been working on a design for a
|
|
64
|
+
formalized separation between View and Model in the browser." Its README
|
|
65
|
+
calls it "a way to write _dynamic_ HTML _using_ HTML," with the goal of native
|
|
66
|
+
implementation in browsers.
|
|
67
|
+
|
|
68
|
+
- `{{path.to.value}}` placeholders in text and attribute values, with a
|
|
69
|
+
custom syntax API for expressions.
|
|
70
|
+
- `<template bind>` for one instance, `<template repeat>` for one instance
|
|
71
|
+
per array item, `<template if>` to make either conditional, and
|
|
72
|
+
`<template ref>` to reuse another template's content.
|
|
73
|
+
- Two-way binding on inputs: "If DOM elements which collect user input are
|
|
74
|
+
bound, they _push_ the collected value into the model."
|
|
75
|
+
- A conditional attribute, `hidden?`: "the attribute will be set to the empty
|
|
76
|
+
string if the value is reachable and truthy, otherwise the attribute will be
|
|
77
|
+
removed."
|
|
78
|
+
- Models are plain JavaScript objects, observed through `Object.observe`.
|
|
79
|
+
|
|
80
|
+
In the TemplateBinding implementation, a template stays in the document and
|
|
81
|
+
its instances are inserted after it, tracked by a terminator node. When `if`
|
|
82
|
+
turns false, instances are removed and their bindings closed.
|
|
83
|
+
|
|
84
|
+
### What became of it
|
|
85
|
+
|
|
86
|
+
The W3C group asked for MDV to be broken into primitives. In Weinstein's
|
|
87
|
+
account those became DOM Mutation Observers, **the HTML `<template>`
|
|
88
|
+
element**, and `Object.observe`. `<template>` was specified by Weinstein and
|
|
89
|
+
reported stable in May 2013. No source found states outright that
|
|
90
|
+
`<template>` was created for MDV, but they share an author, a period and a
|
|
91
|
+
design.
|
|
92
|
+
|
|
93
|
+
The rest did not survive:
|
|
94
|
+
|
|
95
|
+
- `Object.observe` was withdrawn from TC39 in November 2015. It disabled
|
|
96
|
+
optimization paths in V8, and Polymer found that complex applications made
|
|
97
|
+
"tens of thousands of O.o calls."
|
|
98
|
+
- Without native observation, MDV had to keep a list of bindings, and a node
|
|
99
|
+
discarded without being unbound was never garbage collected.
|
|
100
|
+
- The Polymer community debated whether the model or the markup should be the
|
|
101
|
+
source of truth, and settled on the model.
|
|
102
|
+
- The binding half continues in Apple's Template Instantiation proposal (2017)
|
|
103
|
+
and Chrome's DOM Parts, which as of 2025 was pending, with no signals from
|
|
104
|
+
Gecko or WebKit.
|
|
105
|
+
|
|
106
|
+
Sources:
|
|
107
|
+
[Weinstein, "Model-driven Views"](https://lists.w3.org/Archives/Public/public-webapps/2011AprJun/0309.html),
|
|
108
|
+
[MDV README](https://github.com/toolkitchen/mdv),
|
|
109
|
+
[MDV template.md](https://raw.githubusercontent.com/toolkitchen/mdv/master/docs/template.md),
|
|
110
|
+
[MDV node_bind.md](https://raw.githubusercontent.com/toolkitchen/mdv/master/docs/node_bind.md),
|
|
111
|
+
[TemplateBinding source](https://raw.githubusercontent.com/googlearchive/TemplateBinding/master/src/TemplateBinding.js),
|
|
112
|
+
[Will MDV become a standard?](https://groups.google.com/g/polymer-dev/c/4RSYaKmbtEk/m/uYnY3900wpIJ),
|
|
113
|
+
[MDV or markup as data](https://groups.google.com/g/polymer-dev/c/FJQLrcSKGT0),
|
|
114
|
+
[Polymer issue #154](https://github.com/Polymer/polymer/issues/154),
|
|
115
|
+
[W3C HTML Templates, 2013](https://www.w3.org/TR/2013/WD-html-templates-20130214/),
|
|
116
|
+
[InfoQ: Object.observe withdrawn](https://www.infoq.com/news/2015/11/object-observe-withdrawn/),
|
|
117
|
+
[esdiscuss: An update on Object.observe](https://esdiscuss.org/topic/an-update-on-object-observe),
|
|
118
|
+
[Template Instantiation](https://github.com/WICG/webcomponents/blob/gh-pages/proposals/Template-Instantiation.md),
|
|
119
|
+
[DOM Parts](https://github.com/WICG/webcomponents/blob/gh-pages/proposals/DOM-Parts.md),
|
|
120
|
+
[Intent to Prototype: DOM Parts](https://groups.google.com/a/chromium.org/g/blink-dev/c/wIADRnljZDA).
|
|
121
|
+
|
|
122
|
+
## Where the three overlap
|
|
123
|
+
|
|
124
|
+
**All three:**
|
|
125
|
+
|
|
126
|
+
- Dynamic HTML declared in HTML.
|
|
127
|
+
- Attributes naming data on ordinary elements.
|
|
128
|
+
- The platform, not the application, performs the DOM work, with no virtual
|
|
129
|
+
DOM.
|
|
130
|
+
|
|
131
|
+
**Loadbare and MDV:**
|
|
132
|
+
|
|
133
|
+
- `<template>` as the unit of repetition and of conditions.
|
|
134
|
+
- Built toward web standards, not a proprietary runtime.
|
|
135
|
+
|
|
136
|
+
**Loadbare and IE4:**
|
|
137
|
+
|
|
138
|
+
- A relational vocabulary: a record set, a single record, a column per
|
|
139
|
+
element.
|
|
140
|
+
- The row as the repetition template.
|
|
141
|
+
- SQL behind the data source.
|
|
142
|
+
|
|
143
|
+
**MDV and IE4, and not Loadbare:**
|
|
144
|
+
|
|
145
|
+
- The data lives in the browser.
|
|
146
|
+
- Two-way binding.
|
|
147
|
+
- The view keeps itself synchronized with changes to that data.
|
|
148
|
+
|
|
149
|
+
**MDV only:**
|
|
150
|
+
|
|
151
|
+
- Runtime `{{}}` placeholders and expressions.
|
|
152
|
+
- Paths into nested object trees.
|
|
153
|
+
- Change observation, and bindings that have to be closed.
|
|
154
|
+
- A false `if` destroys its instances.
|
|
155
|
+
|
|
156
|
+
**Loadbare only:**
|
|
157
|
+
|
|
158
|
+
- The server is the source of truth, and the hub holds no data.
|
|
159
|
+
- A response says what changed, as a whole list, a patch or a row, so nothing
|
|
160
|
+
is observed.
|
|
161
|
+
- Writes are requests (`lb-action` and the CRUD operations) whose answers land
|
|
162
|
+
back.
|
|
163
|
+
- A request's position is read from the document.
|
|
164
|
+
- A build step: expansion, tree shaking, pages shipped as templates.
|
|
165
|
+
- Navigation and request state carried by the hub.
|
|
166
|
+
|
|
167
|
+
Two of Loadbare's central decisions reject what ended MDV. There is no
|
|
168
|
+
client-side model to observe, which is what made `Object.observe` too slow in
|
|
169
|
+
practice. And data comes only as rows and sets of rows, never as the nested
|
|
170
|
+
object paths MDV bound to.
|
|
171
|
+
|
|
172
|
+
## Conditional rendering elsewhere
|
|
173
|
+
|
|
174
|
+
Frameworks handle a false branch in one of two ways. Either it is hidden
|
|
175
|
+
with CSS and keeps its state, or it is removed and rebuilt from scratch when
|
|
176
|
+
it returns. A few keep a removed branch alive in framework memory.
|
|
177
|
+
|
|
178
|
+
| Framework | The false branch | State kept | Updated while hidden |
|
|
179
|
+
|----------------------------------|-----------------------------------------------------------------------------------|------------|----------------------|
|
|
180
|
+
| MDV `<template if>` | Instances removed after the template, bindings closed | no | no |
|
|
181
|
+
| Alpine `<template x-if>` | Cloned from the template and inserted after it on show, destroyed and removed on hide | no | no |
|
|
182
|
+
| Knockout `if` | Comment nodes mark the place, contents re-rendered completely | no | no |
|
|
183
|
+
| Angular `*ngIf` | `<ng-template>` rendered as a comment anchor; a view can be detached and reinserted | if detached | no |
|
|
184
|
+
| Aurelia `if.bind` | Removed from the DOM, views cached by default and reused | yes | not documented |
|
|
185
|
+
| Vue `<KeepAlive>` | Detached from the document but not unmounted | yes | no |
|
|
186
|
+
| Lit `cache()` | DOM kept in a `DocumentFragment`, swapped back in | yes | on swap-in |
|
|
187
|
+
| Polymer `dom-if` | `style.display = 'none'`; `restamp` destroys and re-creates instead | yes | yes |
|
|
188
|
+
| React `<Activity mode="hidden">` | `display: none`, Effects destroyed | yes | yes, at lower priority |
|
|
189
|
+
|
|
190
|
+
Loadbare's `lb-show` moves the live element into a `<template>` that takes
|
|
191
|
+
its place, and landing still reaches it there. Each
|
|
192
|
+
part has precedent: templates as conditions (MDV, Alpine, Angular), branches
|
|
193
|
+
kept rather than rebuilt (Aurelia, Angular, Vue), and hidden branches kept
|
|
194
|
+
current (Polymer, React). No source found keeps the branch in a template in
|
|
195
|
+
the document, standing where the element stood, with no framework cache. The
|
|
196
|
+
hub has nowhere else to keep one.
|
|
197
|
+
|
|
198
|
+
Loadbare's recommendation before `lb-show`, a stylesheet rule on `lb-value`,
|
|
199
|
+
is the same family as Polymer's `dom-if` and MDV's `hidden?`: the element stays and
|
|
200
|
+
CSS hides it.
|
|
201
|
+
|
|
202
|
+
Sources:
|
|
203
|
+
[Alpine x-if source](https://raw.githubusercontent.com/alpinejs/alpine/main/packages/alpinejs/src/directives/x-if.js),
|
|
204
|
+
[Alpine x-if](https://alpinejs.dev/directives/if),
|
|
205
|
+
[Knockout virtual elements](https://knockoutjs.com/documentation/custom-bindings-for-virtual-elements.html),
|
|
206
|
+
[Knockout if/with re-rendering](https://www.knockmeout.net/2012/03/knockoutjs-performance-gotcha-1ifwith.html),
|
|
207
|
+
[Angular NgIf](https://angular.dev/api/common/NgIf),
|
|
208
|
+
[Reusing views in Angular](https://angular.love/optimization-techniques-reusing-views/),
|
|
209
|
+
[Aurelia conditional rendering](https://docs.aurelia.io/templates/conditional-rendering),
|
|
210
|
+
[Vue KeepAlive](https://vuejs.org/guide/built-ins/keep-alive.html),
|
|
211
|
+
[Lit directives](https://lit.dev/docs/templates/directives/),
|
|
212
|
+
[Lit cache.ts](https://github.com/lit/lit/blob/lit-html-1.x/src/directives/cache.ts),
|
|
213
|
+
[Polymer dom-if](https://polymer-library.polymer-project.org/2.0/docs/devguide/templates),
|
|
214
|
+
[Polymer display caching](https://github.com/Polymer/polymer/commit/2611285),
|
|
215
|
+
[Polymer issue #2712](https://github.com/Polymer/polymer/issues/2712),
|
|
216
|
+
[React Activity](https://react.dev/reference/react/Activity).
|
|
@@ -223,13 +223,14 @@ as a string literal.
|
|
|
223
223
|
|------------------|------------|
|
|
224
224
|
| `ATTR_VALUE` | `lb-value` |
|
|
225
225
|
| `ATTR_CELL` | `lb-cell` |
|
|
226
|
+
| `ATTR_SHOW` | `lb-show` |
|
|
226
227
|
| `ATTR_LIST` | `lb-list` |
|
|
227
228
|
| `ATTR_ROW` | `lb-row` |
|
|
228
229
|
| `ATTR_KEY` | `lb-key` |
|
|
229
230
|
| `ATTR_KEY_VALUE` | `lb-key-value` |
|
|
230
231
|
| `ATTR_ACTION` | `lb-action`|
|
|
231
|
-
| `ACTION_ROW_INSERT`, `ACTION_ROW_DELETE`, `ACTION_ROW_UPDATE
|
|
232
|
-
| `LB_ACTIONS` | all
|
|
232
|
+
| `ACTION_ROW_INSERT`, `ACTION_ROW_DELETE`, `ACTION_ROW_UPDATE` | the reserved `lb-action` values |
|
|
233
|
+
| `LB_ACTIONS` | all three of them, in one array |
|
|
233
234
|
| `LB_RESERVED_PREFIX` | `lb-`, the prefix every reserved name begins with |
|
|
234
235
|
| `ATTR_ROW_COUNT` | `lb-row-count`|
|
|
235
236
|
| `LB_EVENT_NAME` | `lb-request` |
|
|
@@ -260,6 +261,15 @@ cells a successful insert resets — see
|
|
|
260
261
|
[TECHREF-1.0](../TECHREF-1.0.md#the-round-trip). Read it as nothing landed,
|
|
261
262
|
and show the widget's default. A blank string is a value that landed.
|
|
262
263
|
|
|
264
|
+
A widget carrying `lb-show`, or inside an element that does, is moved into a
|
|
265
|
+
template while its column is off and back out when it turns on — see
|
|
266
|
+
[Conditional rendering](./data-binding.md#conditional-rendering). Going in, it
|
|
267
|
+
sees `disconnectedCallback` and then `adoptedCallback`; coming out,
|
|
268
|
+
`adoptedCallback` and then `connectedCallback`. It is the same instance
|
|
269
|
+
throughout, and it still receives `lb-value` while it is away, so write
|
|
270
|
+
`connectedCallback` to run more than once, as a row a list reorders already
|
|
271
|
+
requires.
|
|
272
|
+
|
|
263
273
|
List `ATTR_VALUE` in `observedAttributes`, or `attributeChangedCallback`
|
|
264
274
|
never fires. The browser calls it for an attribute already present when an
|
|
265
275
|
element upgrades, not only for one that changes afterward, so a widget's
|
|
@@ -277,7 +287,7 @@ control's value as its `detail`:
|
|
|
277
287
|
import { LB_EVENT_NAME } from "@loadbare/app/constants";
|
|
278
288
|
import type { HubRequest } from "@loadbare/app/types";
|
|
279
289
|
|
|
280
|
-
const detail: HubRequest = { action: "lb-
|
|
290
|
+
const detail: HubRequest = { action: "lb-row-update", value: input.value };
|
|
281
291
|
this.dispatchEvent(new CustomEvent(LB_EVENT_NAME, { bubbles: true, detail }));
|
|
282
292
|
```
|
|
283
293
|
|
|
@@ -286,9 +296,14 @@ event, and does not send a request missing a field its operation requires.
|
|
|
286
296
|
See [TECHREF-1.0](../TECHREF-1.0.md#requests) for what each operation is
|
|
287
297
|
filled with.
|
|
288
298
|
|
|
289
|
-
A widget
|
|
290
|
-
|
|
291
|
-
|
|
299
|
+
A widget that carries `lb-cell` and sends `lb-row-insert` or `lb-row-update`
|
|
300
|
+
sends that one cell. The hub builds `values` from the cell's name and the
|
|
301
|
+
`value` the widget sent, or reads the control the widget wraps when it sent
|
|
302
|
+
none.
|
|
303
|
+
|
|
304
|
+
A widget that carries no `lb-cell` doesn't read its own controls. It
|
|
305
|
+
dispatches the bare action from the row, or from anything inside it, and the
|
|
306
|
+
hub gathers `values` from the row the event came from:
|
|
292
307
|
the nearest `<form>`, `<tr>` or live row around the dispatching element,
|
|
293
308
|
itself included, inside its scope. Every readable `lb-cell` in that row is
|
|
294
309
|
gathered, wherever in the row it sits, except the cells of a scope nested in
|
|
@@ -41,14 +41,15 @@ asks for are declared on the server; see [page files](./page-files.md).
|
|
|
41
41
|
|
|
42
42
|
## Binding
|
|
43
43
|
|
|
44
|
-
| Attribute | Written by | Names
|
|
45
|
-
|
|
46
|
-
| `lb-list` | a developer | The set of rows a subtree displays
|
|
47
|
-
| `lb-row` | a developer | The one row a subtree displays
|
|
48
|
-
| `lb-key` | a developer | The column that identifies a row
|
|
49
|
-
| `lb-cell` | a developer | The column an element displays
|
|
50
|
-
| `lb-
|
|
51
|
-
| `lb-value`
|
|
44
|
+
| Attribute | Written by | Names |
|
|
45
|
+
|----------------|-------------|-------------------------------------------------------|
|
|
46
|
+
| `lb-list` | a developer | The set of rows a subtree displays |
|
|
47
|
+
| `lb-row` | a developer | The one row a subtree displays |
|
|
48
|
+
| `lb-key` | a developer | The column that identifies a row |
|
|
49
|
+
| `lb-cell` | a developer | The column an element displays |
|
|
50
|
+
| `lb-show` | a developer | The column that decides whether an element is present |
|
|
51
|
+
| `lb-key-value` | the hub | A live row's own key |
|
|
52
|
+
| `lb-value` | the hub | The value that landed on a cell |
|
|
52
53
|
|
|
53
54
|
A binding is scoped by ancestry. Either scope attribute scopes its DOM
|
|
54
55
|
children, and a nested one of either kind begins a new scope, so an element
|
|
@@ -151,9 +152,9 @@ the prefix stripped and the rest camel-cased.
|
|
|
151
152
|
| `lb-action="lb-row-delete"` | `rowDelete` | `list`, `key` |
|
|
152
153
|
| `lb-action="lb-row-insert"` on a `<form>` | `rowInsert` | `list`, `values` |
|
|
153
154
|
| `lb-action="lb-row-update"` on a `<form>` | `rowUpdate` | `list`, `key`, `values` |
|
|
154
|
-
| `lb-action="lb-
|
|
155
|
+
| `lb-action="lb-row-update"` on a widget cell | `rowUpdate` | `list`, `key`, `values` of one cell |
|
|
155
156
|
|
|
156
|
-
All
|
|
157
|
+
All three operations are list operations. Each needs a key, and a key exists
|
|
157
158
|
only on a live row the hub stamped inside a list, so a single-row scope is
|
|
158
159
|
read-only and a declared action is the only thing it can send. An application
|
|
159
160
|
that wants a writable single row declares a list that answers with one row.
|
|
@@ -165,15 +166,11 @@ with one an application already uses. That reservation is also the whole of
|
|
|
165
166
|
the wire discriminant: a value beginning with `lb-` is an operation, and
|
|
166
167
|
anything else is a name the page declared.
|
|
167
168
|
|
|
168
|
-
The hub sends
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
cell
|
|
172
|
-
|
|
173
|
-
`<lb-input>` does when it carries `lb-action="lb-cell-change"`, and stays
|
|
174
|
-
quiet otherwise — an input inside an `lb-row-insert` or `lb-row-update` form is read
|
|
175
|
-
again by the form on submit, so a widget that sent on its own would write
|
|
176
|
-
the same edit twice.
|
|
169
|
+
The hub sends a request from a native element on the element's own event: a
|
|
170
|
+
form on submit, anything else on click. An insert or update clicked from a
|
|
171
|
+
button reads the form or row the button is in (see [Forms](#forms)). To
|
|
172
|
+
commit one cell as it changes, put `lb-row-update` on a widget cell instead
|
|
173
|
+
(see [Committing one cell](#committing-one-cell)).
|
|
177
174
|
|
|
178
175
|
Declare every action on the server, and permit every operation on its list. A
|
|
179
176
|
name the page has not declared, and an operation a list does not permit, are
|
|
@@ -183,7 +180,7 @@ refused; see
|
|
|
183
180
|
### Actions
|
|
184
181
|
|
|
185
182
|
Write `lb-action` on a button to ask the server to do something that is not
|
|
186
|
-
one of the
|
|
183
|
+
one of the three CRUD operations:
|
|
187
184
|
|
|
188
185
|
```html
|
|
189
186
|
<button lb-action="mailRoster">Mail the roster</button>
|
|
@@ -279,46 +276,123 @@ refuses it. A widget may dispatch either from any element, and the hub
|
|
|
279
276
|
gathers the same way; see
|
|
280
277
|
[Sending a request](./custom-elements.md#sending-a-request).
|
|
281
278
|
|
|
279
|
+
### Committing one cell
|
|
280
|
+
|
|
281
|
+
Put `lb-row-update` on a widget that carries `lb-cell` to save that one cell
|
|
282
|
+
whenever it changes. The shipped `<lb-input>` sends it on `change`:
|
|
283
|
+
|
|
284
|
+
```html
|
|
285
|
+
<template lb-key="id">
|
|
286
|
+
<tr>
|
|
287
|
+
<td><lb-input lb-cell="name" lb-action="lb-row-update"></lb-input></td>
|
|
288
|
+
<td><lb-input lb-cell="note" lb-action="lb-row-update"></lb-input></td>
|
|
289
|
+
</tr>
|
|
290
|
+
</template>
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
An element carrying `lb-cell` is a record of one cell, the way a control has
|
|
294
|
+
a value and a form has values. Its update carries `values` holding that cell
|
|
295
|
+
alone, so an edit in one input never sends the other. It reaches the same
|
|
296
|
+
`rowUpdate` a form does. SQL has one UPDATE whether it sets one column or
|
|
297
|
+
many, and the application writes one handler for both.
|
|
298
|
+
|
|
299
|
+
The widget decides when the cell has changed. A native `<input>` carrying
|
|
300
|
+
`lb-cell` and `lb-row-update` has no such moment, since a click into it
|
|
301
|
+
would send it, so the hub refuses it and says so.
|
|
302
|
+
|
|
303
|
+
Leave `lb-action` off a widget inside an `lb-row-insert` or `lb-row-update`
|
|
304
|
+
form. The form reads every `lb-cell` in it on submit, so a widget that also
|
|
305
|
+
sent its own would write the same edit twice.
|
|
306
|
+
|
|
282
307
|
## Conditional rendering
|
|
283
308
|
|
|
284
309
|
Loadbare ships static HTML and hydrates elements that are already in the
|
|
285
310
|
document. There is no `if`, and none is needed: write every possibility into
|
|
286
|
-
the page, and
|
|
311
|
+
the page, and let a column decide which of them is present.
|
|
287
312
|
|
|
288
|
-
|
|
289
|
-
stylesheet can show or hide part of a page from a value the server sent. Bind
|
|
290
|
-
a column the page does not display to a hidden element:
|
|
313
|
+
Write `lb-show` on an element, naming the column that decides it:
|
|
291
314
|
|
|
292
315
|
```html
|
|
293
316
|
<template lb-key="id">
|
|
294
317
|
<tr>
|
|
295
318
|
<td lb-cell="name"></td>
|
|
296
|
-
<td lb-
|
|
297
|
-
<td><button lb-action="lb-row-delete">Remove</button></td>
|
|
319
|
+
<td><button lb-action="lb-row-delete" lb-show="removable">Remove</button></td>
|
|
298
320
|
</tr>
|
|
299
321
|
</template>
|
|
300
322
|
```
|
|
301
323
|
|
|
302
|
-
```
|
|
303
|
-
|
|
304
|
-
display: none;
|
|
305
|
-
}
|
|
324
|
+
```sql
|
|
325
|
+
(ledger_count = 0 AND system_behavior IS NULL) AS removable
|
|
306
326
|
```
|
|
307
327
|
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
328
|
+
A value of `null` or `false` takes the element out of the page, and any other
|
|
329
|
+
value puts it back. The hub never reads a string, so `"false"` is a value like
|
|
330
|
+
any other: have the query answer with a boolean or a null. A row that does not
|
|
331
|
+
carry the column leaves the element as it is, so a query that answers with
|
|
332
|
+
whole rows returns the column in every row.
|
|
312
333
|
|
|
313
|
-
|
|
314
|
-
|
|
334
|
+
`lb-show` binds the way `lb-cell` does, to the row on its nearest scoped
|
|
335
|
+
ancestor. On an element that is itself a scope, the column belongs to the
|
|
336
|
+
row around it, so this picker takes its choices from `groups` and whether it
|
|
337
|
+
is present from the account row:
|
|
315
338
|
|
|
316
|
-
```
|
|
317
|
-
|
|
339
|
+
```html
|
|
340
|
+
<select lb-list="groups" lb-cell="group_id" lb-show="group_choice">
|
|
318
341
|
```
|
|
319
342
|
|
|
320
|
-
|
|
321
|
-
|
|
343
|
+
An element may show a column and be decided by it, which shows a note only
|
|
344
|
+
when there is one:
|
|
345
|
+
|
|
346
|
+
```html
|
|
347
|
+
<span lb-cell="note" lb-show="note"></span>
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
An element that is not present cannot be clicked, but that is presentation:
|
|
351
|
+
the server still refuses what a request may not do.
|
|
352
|
+
|
|
353
|
+
### Where an absent element is
|
|
354
|
+
|
|
355
|
+
An element whose column is off is moved into a `<template lb-show>` that
|
|
356
|
+
stands where it stood, and moved back out when the column turns on. The
|
|
357
|
+
developer never writes that template.
|
|
358
|
+
|
|
359
|
+
- Nothing renders it, whatever a stylesheet says, because a template's content
|
|
360
|
+
is not its children.
|
|
361
|
+
- It cannot be focused or clicked, assistive technology does not announce it,
|
|
362
|
+
and a form does not gather it.
|
|
363
|
+
- It is moved, never rebuilt, so a widget keeps its instance and a control
|
|
364
|
+
keeps what was typed into it.
|
|
365
|
+
- Values keep landing on it, and on every cell and scope inside it, while it
|
|
366
|
+
is away, so it returns current.
|
|
367
|
+
|
|
368
|
+
The builder ships every `lb-show` element already inside its template, so
|
|
369
|
+
nothing conditional shows until its row has landed.
|
|
370
|
+
|
|
371
|
+
A condition never changes the structure of a page. An absent element keeps
|
|
372
|
+
its place among its siblings, so a position selector (`:first-child`,
|
|
373
|
+
`:nth-child`, `:empty`, `+`, `~`) counts its template as a sibling. A selector
|
|
374
|
+
by tag, class or attribute is unaffected. Only a list changes a page's
|
|
375
|
+
structure.
|
|
376
|
+
|
|
377
|
+
A condition that is only a style is a class on an element that is present or
|
|
378
|
+
not:
|
|
379
|
+
|
|
380
|
+
```html
|
|
381
|
+
<span lb-show="out_of_balance" class="danger">Out of balance</span>
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
These are build errors:
|
|
385
|
+
|
|
386
|
+
- `lb-show` on a row template's root. A row that should not show is left out
|
|
387
|
+
by the query.
|
|
388
|
+
- `lb-show` with no row around it: outside every scope, on a scope with none
|
|
389
|
+
around it, or in a list scope outside its row template, where nothing lands.
|
|
390
|
+
- `lb-show` on a `<template>`.
|
|
391
|
+
|
|
392
|
+
A condition that is not data, such as a collapsed section or an open menu,
|
|
393
|
+
has no column. Use `<details>`, a stylesheet, or a widget. Every cell still
|
|
394
|
+
carries the value that landed on it as `lb-value`, for a widget to read or a
|
|
395
|
+
stylesheet to select on.
|
|
322
396
|
|
|
323
397
|
### An empty list
|
|
324
398
|
|
|
@@ -369,6 +443,12 @@ button in a stylesheet, or have a widget watch its own attributes and
|
|
|
369
443
|
disable itself. An application that styles neither behaves correctly and
|
|
370
444
|
shows nothing.
|
|
371
445
|
|
|
446
|
+
A native button or form pressed again while it carries `lb-pending` is
|
|
447
|
+
ignored, so a pending one is already disabled and the stylesheet only shows
|
|
448
|
+
it. A widget is not held back, since one that sends on change must send its
|
|
449
|
+
latest value. The hub also sets `aria-busy="true"` for as long as
|
|
450
|
+
`lb-pending` is present.
|
|
451
|
+
|
|
372
452
|
## Sending a request from a widget
|
|
373
453
|
|
|
374
454
|
A widget can build and dispatch a request itself instead of carrying one of
|
|
@@ -86,7 +86,7 @@ Export `requests` from `<name>.requests.ts`. It holds three keys, each optional:
|
|
|
86
86
|
|---------------|------------------------------------------------------|
|
|
87
87
|
| `onPageEnter` | Before the page's queries, on entering the page |
|
|
88
88
|
| `actions` | What the page may be asked to do, by name |
|
|
89
|
-
| `crud` | The
|
|
89
|
+
| `crud` | The three operations a list permits on its rows |
|
|
90
90
|
|
|
91
91
|
### onPageEnter
|
|
92
92
|
|
|
@@ -127,17 +127,21 @@ carries `list` or `row`, whichever attribute scoped the element, plus `key`,
|
|
|
127
127
|
### crud
|
|
128
128
|
|
|
129
129
|
Declare CRUD operations under `crud`, keyed by the list they operate on. All
|
|
130
|
-
|
|
130
|
+
three are list operations: each needs a key, and a key exists only on a live
|
|
131
131
|
row inside a list, so a single-row scope is read-only and a declared action is
|
|
132
132
|
the only thing it can send. Each operation takes the binding its trigger
|
|
133
133
|
supplies:
|
|
134
134
|
|
|
135
|
-
| Operation
|
|
136
|
-
|
|
137
|
-
| `
|
|
138
|
-
| `
|
|
139
|
-
| `
|
|
140
|
-
|
|
135
|
+
| Operation | The page writes | `run` receives |
|
|
136
|
+
|-------------|------------------------------------------------------------------------|-----------------|
|
|
137
|
+
| `rowDelete` | `lb-action="lb-row-delete"` | `key` |
|
|
138
|
+
| `rowInsert` | `<form lb-action="lb-row-insert">` | `values` |
|
|
139
|
+
| `rowUpdate` | `<form lb-action="lb-row-update">` or `<lb-input lb-action="lb-row-update">` | `key`, `values` |
|
|
140
|
+
|
|
141
|
+
Write `rowUpdate` to set the columns `values` names and leave every other
|
|
142
|
+
column as it is. A form sends the cells it holds, and a widget cell sends
|
|
143
|
+
itself alone. Check the names in `values` against the columns the list lets
|
|
144
|
+
the page edit.
|
|
141
145
|
|
|
142
146
|
The operation names are reserved: a name beginning with `lb-` cannot be
|
|
143
147
|
declared under `actions` or as a query, and `createHub` refuses a page that
|
|
@@ -179,7 +183,7 @@ would. What `run` returns is laid over the refreshed queries:
|
|
|
179
183
|
| `patch({ drop: [...] })` | These keys are gone; the rest stand |
|
|
180
184
|
|
|
181
185
|
Return a patch for a change the operation knows the extent of — one row added,
|
|
182
|
-
one row dropped, one
|
|
186
|
+
one row dropped, one row edited — and leave `refresh` empty. Re-run the query
|
|
183
187
|
instead when membership or order changed in a way the operation cannot name:
|
|
184
188
|
|
|
185
189
|
```ts
|
|
@@ -27,7 +27,7 @@ for where a listed package sits in the cascade.
|
|
|
27
27
|
|
|
28
28
|
Wraps an `<input>`. `lb-value` sets the input's `.value`. The widget sends
|
|
29
29
|
nothing on its own: `lb-action` names what the input's `change` sends. The
|
|
30
|
-
reserved `lb-
|
|
30
|
+
reserved `lb-row-update` saves the input's own cell; any other name sends that
|
|
31
31
|
action. Either carries the input's value, and the hub adds the scope the
|
|
32
32
|
input sits in.
|
|
33
33
|
|
|
@@ -42,7 +42,7 @@ of them, so an input that also sent its own would write the same edit twice.
|
|
|
42
42
|
|
|
43
43
|
| Attribute | Asks for |
|
|
44
44
|
| ---------- | ----- |
|
|
45
|
-
| `lb-action` | what to send on `change`; `lb-
|
|
45
|
+
| `lb-action` | what to send on `change`; `lb-row-update` to save the cell's own edit |
|
|
46
46
|
|
|
47
47
|
## `lb-select`
|
|
48
48
|
|
package/docs/roadmap.md
CHANGED
|
@@ -140,7 +140,7 @@ machinery would then also let `lb-list` and `lb-row` names be checked against
|
|
|
140
140
|
the declared queries.
|
|
141
141
|
|
|
142
142
|
One piece is separable and needs none of the above: an `lb-action` beginning
|
|
143
|
-
with `lb-` that names none of the
|
|
143
|
+
with `lb-` that names none of the three operations is a typo the builder can
|
|
144
144
|
refuse from markup alone. `LB_ACTIONS` in `core/lb-constants.ts` exists for
|
|
145
145
|
this, and the builder does not yet import it.
|
|
146
146
|
|
package/docs/testing.md
CHANGED
|
@@ -188,12 +188,18 @@ What the hub is tested for here:
|
|
|
188
188
|
its scope, whatever shares the button's cell: one ghost row among its
|
|
189
189
|
neighbours, a whole live row, a form nested in a row, a button's form
|
|
190
190
|
owner; and refuses a `<div>` holding cells, pointing to `<form>`
|
|
191
|
+
- an insert or update from an element carrying `lb-cell` sends that cell
|
|
192
|
+
alone, from the value a widget sent or else its control; refuses a value
|
|
193
|
+
with no `lb-cell` to name its column; and refuses a click on a native
|
|
194
|
+
element that is a cell
|
|
191
195
|
- gathering skips the cells of a nested scope, so a picker in a row sends
|
|
192
196
|
its own cell and nothing about its options
|
|
193
197
|
- a request arriving with no action, or with a reserved name that is not one
|
|
194
|
-
of the
|
|
198
|
+
of the three, is refused before it reaches the wire
|
|
195
199
|
- `lb-pending` lands on the element that dispatched, `lb-error` replaces it
|
|
196
200
|
on failure, and the next request clears it
|
|
201
|
+
- `aria-busy` comes and goes with `lb-pending`; a native button or form
|
|
202
|
+
performed again while pending sends nothing, and a widget still sends
|
|
197
203
|
- a path with no page host reports and opens the unknown-page dialog; two
|
|
198
204
|
hosts for one name report and take the first
|
|
199
205
|
- `lb-navigation` lands with the path and the label of the link that names it
|