@loadbare/app 0.5.6 → 0.7.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/README.md +3 -4
- package/dist/build/assemble.d.ts +1 -1
- package/dist/build/assemble.d.ts.map +1 -1
- package/dist/build/assemble.js +81 -7
- package/dist/build/assemble.js.map +1 -0
- package/dist/build/cli.d.ts +2 -2
- package/dist/build/cli.js +3 -2
- package/dist/build/cli.js.map +1 -0
- package/dist/build/elements.js +1 -0
- package/dist/build/elements.js.map +1 -0
- package/dist/build/expand.d.ts.map +1 -1
- package/dist/build/expand.js +20 -32
- package/dist/build/expand.js.map +1 -0
- package/dist/build/format.js +1 -0
- package/dist/build/format.js.map +1 -0
- package/dist/build/locations.d.ts +4 -4
- package/dist/build/locations.d.ts.map +1 -1
- package/dist/build/locations.js +16 -5
- package/dist/build/locations.js.map +1 -0
- package/dist/build/origins.d.ts +0 -13
- package/dist/build/origins.d.ts.map +1 -1
- package/dist/build/origins.js +33 -8
- package/dist/build/origins.js.map +1 -0
- package/dist/build/package-root.js +1 -0
- package/dist/build/package-root.js.map +1 -0
- package/dist/build/pages.d.ts +7 -3
- package/dist/build/pages.d.ts.map +1 -1
- package/dist/build/pages.js +14 -7
- package/dist/build/pages.js.map +1 -0
- package/dist/build/styles.js +1 -0
- package/dist/build/styles.js.map +1 -0
- package/dist/core/lb-constants.d.ts +15 -13
- package/dist/core/lb-constants.d.ts.map +1 -1
- package/dist/core/lb-constants.js +104 -53
- package/dist/core/lb-constants.js.map +1 -0
- package/dist/core/lb-types.d.ts +103 -62
- package/dist/core/lb-types.d.ts.map +1 -1
- package/dist/core/lb-types.js +12 -3
- package/dist/core/lb-types.js.map +1 -0
- package/dist/hub/lb-apply.d.ts +28 -4
- package/dist/hub/lb-apply.d.ts.map +1 -1
- package/dist/hub/lb-apply.js +227 -40
- package/dist/hub/lb-apply.js.map +1 -0
- package/dist/hub/lb-hub.browser.d.ts +1 -1
- package/dist/hub/lb-hub.browser.d.ts.map +1 -1
- package/dist/hub/lb-hub.browser.js +165 -110
- package/dist/hub/lb-hub.browser.js.map +1 -0
- package/dist/server/lb-express.d.ts +8 -5
- package/dist/server/lb-express.d.ts.map +1 -1
- package/dist/server/lb-express.js +46 -33
- package/dist/server/lb-express.js.map +1 -0
- package/dist/server/lb-server.d.ts +67 -45
- package/dist/server/lb-server.d.ts.map +1 -1
- package/dist/server/lb-server.js +56 -18
- package/dist/server/lb-server.js.map +1 -0
- package/docs/TECHREF-1.0.md +1107 -0
- package/docs/analysis-accidental-complexity.md +149 -0
- package/docs/reference/builder.md +4 -4
- package/docs/reference/chrome.md +10 -9
- package/docs/reference/custom-elements.md +54 -46
- package/docs/reference/data-binding.md +179 -97
- package/docs/reference/overview.md +1 -1
- package/docs/reference/page-files.md +64 -49
- package/docs/reference/server.md +25 -6
- package/docs/reference/widgets.md +22 -30
- package/docs/roadmap.md +68 -22
- package/docs/testing.md +47 -17
- package/docs/theory.md +116 -3
- package/docs/tutorials/010-pages-and-navigation.md +8 -8
- package/docs/tutorials/040-displaying-data.md +9 -9
- package/docs/tutorials/050-actions.md +5 -5
- package/docs/tutorials/060-custom-element-code.md +1 -1
- package/docs/tutorials/065-conditional-rendering.md +4 -4
- package/docs/tutorials/070-displaying-a-list.md +24 -47
- package/docs/tutorials/072-inserting-into-a-list.md +18 -15
- package/docs/tutorials/074-deleting-from-a-list.md +15 -17
- package/docs/tutorials/076-updating-a-list-item.md +20 -22
- package/docs/tutorials/080-widget-requests.md +22 -35
- package/docs/tutorials/090-using-widget-libraries.md +1 -1
- package/package.json +2 -3
- package/dist/hub/lb-rows.d.ts +0 -18
- package/dist/hub/lb-rows.d.ts.map +0 -1
- package/dist/hub/lb-rows.js +0 -106
- package/dist/tests/assemble.test.d.ts +0 -8
- package/dist/tests/assemble.test.d.ts.map +0 -1
- package/dist/tests/assemble.test.js +0 -58
- package/dist/tests/elements.test.d.ts +0 -8
- package/dist/tests/elements.test.d.ts.map +0 -1
- package/dist/tests/elements.test.js +0 -118
- package/dist/tests/expand.test.d.ts +0 -10
- package/dist/tests/expand.test.d.ts.map +0 -1
- package/dist/tests/expand.test.js +0 -250
- package/dist/tests/fixtures/elements/collision/imports.d.ts +0 -3
- package/dist/tests/fixtures/elements/collision/imports.d.ts.map +0 -1
- package/dist/tests/fixtures/elements/collision/imports.js +0 -1
- package/dist/tests/fixtures/elements/collision/widgets/acme-widget.browser.d.ts +0 -2
- package/dist/tests/fixtures/elements/collision/widgets/acme-widget.browser.d.ts.map +0 -1
- package/dist/tests/fixtures/elements/collision/widgets/acme-widget.browser.js +0 -1
- package/dist/tests/fixtures/elements/local/widgets/app-box.browser.d.ts +0 -2
- package/dist/tests/fixtures/elements/local/widgets/app-box.browser.d.ts.map +0 -1
- package/dist/tests/fixtures/elements/local/widgets/app-box.browser.js +0 -1
- package/dist/tests/fixtures/elements/manifest/imports.d.ts +0 -3
- package/dist/tests/fixtures/elements/manifest/imports.d.ts.map +0 -1
- package/dist/tests/fixtures/elements/manifest/imports.js +0 -1
- package/dist/tests/fixtures/elements/manifest-bad-entry/imports.d.ts +0 -3
- package/dist/tests/fixtures/elements/manifest-bad-entry/imports.d.ts.map +0 -1
- package/dist/tests/fixtures/elements/manifest-bad-entry/imports.js +0 -1
- package/dist/tests/fixtures/elements/manifest-not-array/imports.d.ts +0 -5
- package/dist/tests/fixtures/elements/manifest-not-array/imports.d.ts.map +0 -1
- package/dist/tests/fixtures/elements/manifest-not-array/imports.js +0 -1
- package/dist/tests/fixtures/elements/pkg/acme-widget.browser.d.ts +0 -2
- package/dist/tests/fixtures/elements/pkg/acme-widget.browser.d.ts.map +0 -1
- package/dist/tests/fixtures/elements/pkg/acme-widget.browser.js +0 -1
- package/dist/tests/fixtures/elements/unmarked/widgets/app-box.d.ts +0 -6
- package/dist/tests/fixtures/elements/unmarked/widgets/app-box.d.ts.map +0 -1
- package/dist/tests/fixtures/elements/unmarked/widgets/app-box.js +0 -1
- package/dist/tests/helpers/console.d.ts +0 -20
- package/dist/tests/helpers/console.d.ts.map +0 -1
- package/dist/tests/helpers/console.js +0 -28
- package/dist/tests/helpers/dom.d.ts +0 -18
- package/dist/tests/helpers/dom.d.ts.map +0 -1
- package/dist/tests/helpers/dom.js +0 -22
- package/dist/tests/lb-apply.test.d.ts +0 -8
- package/dist/tests/lb-apply.test.d.ts.map +0 -1
- package/dist/tests/lb-apply.test.js +0 -153
- package/dist/tests/lb-express.test.d.ts +0 -14
- package/dist/tests/lb-express.test.d.ts.map +0 -1
- package/dist/tests/lb-express.test.js +0 -238
- package/dist/tests/lb-rows.test.d.ts +0 -12
- package/dist/tests/lb-rows.test.d.ts.map +0 -1
- package/dist/tests/lb-rows.test.js +0 -336
- package/dist/tests/lb-server.test.d.ts +0 -9
- package/dist/tests/lb-server.test.d.ts.map +0 -1
- package/dist/tests/lb-server.test.js +0 -495
- package/dist/tests/origins.test.d.ts +0 -10
- package/dist/tests/origins.test.d.ts.map +0 -1
- package/dist/tests/origins.test.js +0 -369
- package/dist/tests/pages.test.d.ts +0 -6
- package/dist/tests/pages.test.d.ts.map +0 -1
- package/dist/tests/pages.test.js +0 -98
- package/dist/tests/styles.test.d.ts +0 -7
- package/dist/tests/styles.test.d.ts.map +0 -1
- package/dist/tests/styles.test.js +0 -76
|
@@ -15,7 +15,7 @@ to reset the visit count.
|
|
|
15
15
|
<h1>About</h1>
|
|
16
16
|
<p class="about-note">This is the about page.</p>
|
|
17
17
|
|
|
18
|
-
<div lb-
|
|
18
|
+
<div lb-row="visits">
|
|
19
19
|
<p>This page has been visited <span lb-cell="count"></span> times.</p>
|
|
20
20
|
<button lb-action="resetVisits">Reset count</button>
|
|
21
21
|
</div>
|
|
@@ -23,12 +23,12 @@ to reset the visit count.
|
|
|
23
23
|
|
|
24
24
|
## Implement the action on the server
|
|
25
25
|
|
|
26
|
-
Actions go into the page's
|
|
26
|
+
Actions go into the page's requests file:
|
|
27
27
|
|
|
28
28
|
```ts
|
|
29
|
-
// src/pages/about.
|
|
30
|
-
export const
|
|
31
|
-
|
|
29
|
+
// src/pages/about.requests.ts
|
|
30
|
+
export const requests = {
|
|
31
|
+
onPageEnter: (ctx) => ctx.db.recordVisit(),
|
|
32
32
|
actions: {
|
|
33
33
|
resetVisits: {
|
|
34
34
|
run: (ctx) => ctx.db.resetVisits(),
|
|
@@ -19,7 +19,7 @@ two of them carry the standard `hidden` attribute.
|
|
|
19
19
|
```html
|
|
20
20
|
<!-- src/pages/about.page.html -->
|
|
21
21
|
<h2>Sign up</h2>
|
|
22
|
-
<lb-wizard lb-
|
|
22
|
+
<lb-wizard lb-row="signup" lb-cell="step">
|
|
23
23
|
<section data-step="name">
|
|
24
24
|
<h3>1. Name</h3>
|
|
25
25
|
<p>This step is not hidden, because it's the one the page ships showing.</p>
|
|
@@ -96,9 +96,9 @@ export const queries = {
|
|
|
96
96
|
```
|
|
97
97
|
|
|
98
98
|
```ts
|
|
99
|
-
// src/pages/about.
|
|
100
|
-
export const
|
|
101
|
-
// ...
|
|
99
|
+
// src/pages/about.requests.ts
|
|
100
|
+
export const requests = {
|
|
101
|
+
// ...onPageEnter, resetVisits, and crud unchanged...
|
|
102
102
|
actions: {
|
|
103
103
|
resetVisits: { run: (ctx) => ctx.db.resetVisits(), refresh: ["visits"] },
|
|
104
104
|
wizardBack: { run: (ctx) => ctx.db.moveSignup(-1), refresh: ["signup"] },
|
|
@@ -1,22 +1,24 @@
|
|
|
1
1
|
# Displaying a List
|
|
2
2
|
|
|
3
|
-
Every query so far has answered with one
|
|
4
|
-
answers with many rows, and
|
|
3
|
+
Every query so far has answered with one row. Now we add a query that
|
|
4
|
+
answers with many rows, and markup to show them. No widget is involved: the
|
|
5
|
+
hub reconciles a list itself.
|
|
5
6
|
|
|
6
7
|
## Writing the query
|
|
7
8
|
|
|
8
9
|
```ts
|
|
9
10
|
// src/pages/about.queries.ts
|
|
10
|
-
import {
|
|
11
|
+
import { list, row } from "@loadbare/app/server";
|
|
11
12
|
|
|
12
13
|
export const queries = {
|
|
13
|
-
visits: async (ctx) => ({ count: String(await ctx.db.visitCount()) }),
|
|
14
|
-
notes:
|
|
14
|
+
visits: row(async (ctx) => ({ count: String(await ctx.db.visitCount()) })),
|
|
15
|
+
notes: list((ctx) => ctx.db.notes()),
|
|
15
16
|
};
|
|
16
17
|
```
|
|
17
18
|
|
|
18
|
-
|
|
19
|
-
|
|
19
|
+
Every query says which shape it answers with. `row()` answers with one row
|
|
20
|
+
and `list()` answers with a set of them, and a name never answers with both.
|
|
21
|
+
A page that needs the same data both ways declares two queries.
|
|
20
22
|
|
|
21
23
|
## Extending the database
|
|
22
24
|
|
|
@@ -64,35 +66,11 @@ export function openDb() {
|
|
|
64
66
|
`write` is now a merge, not a whole-file replace — otherwise saving
|
|
65
67
|
`notes` would erase `visitCount`.
|
|
66
68
|
|
|
67
|
-
## Installing the widget library
|
|
68
|
-
|
|
69
|
-
`<lb-list>` is the first widget we take from a library rather than write
|
|
70
|
-
ourselves. It ships in `@loadbare/widgets`:
|
|
71
|
-
|
|
72
|
-
```
|
|
73
|
-
npm install @loadbare/widgets
|
|
74
|
-
```
|
|
75
|
-
|
|
76
|
-
The builder scans `src/` on its own, but it will not go looking through
|
|
77
|
-
`node_modules` uninvited. List the package in `src/imports.ts`:
|
|
78
|
-
|
|
79
|
-
```ts
|
|
80
|
-
// src/imports.ts
|
|
81
|
-
export default ["@loadbare/widgets"];
|
|
82
|
-
```
|
|
83
|
-
|
|
84
|
-
That is the whole of it. List the package, not its tags — the builder finds
|
|
85
|
-
`<lb-list>` by filename, and bundles only the tags a page actually uses.
|
|
86
|
-
[Using Widget Libraries](./090-using-widget-libraries.md) covers the same
|
|
87
|
-
step for anyone else's package.
|
|
88
|
-
|
|
89
69
|
## A list display with a template
|
|
90
70
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
to display one template per row, and invokes code in the hub `<lb-hub>` to
|
|
95
|
-
display bound data values.
|
|
71
|
+
A list scope needs to be told what one row looks like, and we say so with a
|
|
72
|
+
`<template>` inside it. The hub clones that template once per row and fills
|
|
73
|
+
each clone the same way it fills any other scope.
|
|
96
74
|
|
|
97
75
|
> Loadbare does not use Shadow DOM because Shadow DOM is generally obtuse,
|
|
98
76
|
> interferes with CSS scoping, and makes templates difficult to supply at point
|
|
@@ -103,24 +81,23 @@ display bound data values.
|
|
|
103
81
|
> Rather than mess with the Shadow DOM, we use Light DOM.
|
|
104
82
|
> We take advantage of the fact that `<template>`
|
|
105
83
|
> elements are not rendered, so we drop the `<template>` directly
|
|
106
|
-
> into the light DOM, where it's easy to see how
|
|
107
|
-
> going to
|
|
84
|
+
> into the light DOM, where it's easy to see how a row
|
|
85
|
+
> is going to be rendered.
|
|
108
86
|
|
|
109
|
-
We know already that a DOM subtree can be scoped to
|
|
110
|
-
`lb-
|
|
111
|
-
|
|
112
|
-
|
|
87
|
+
We know already that a DOM subtree can be scoped to one row with
|
|
88
|
+
`lb-row="X"`. A set of rows uses `lb-list="X"` instead, and adds
|
|
89
|
+
`lb-key="id"`, which names the column that uniquely identifies a row. Each
|
|
90
|
+
row the hub clones is stamped with `lb-key-value`, holding that row's key,
|
|
91
|
+
which is what scopes the row's subtree to one specific row.
|
|
113
92
|
|
|
114
93
|
```html
|
|
115
94
|
<!-- src/pages/about.page.html -->
|
|
116
95
|
<h2>Notes</h2>
|
|
117
|
-
<lb-list
|
|
118
|
-
<
|
|
119
|
-
<
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
</ul>
|
|
123
|
-
</lb-list>
|
|
96
|
+
<ul lb-list="notes">
|
|
97
|
+
<template lb-key="id">
|
|
98
|
+
<li lb-cell="text"></li>
|
|
99
|
+
</template>
|
|
100
|
+
</ul>
|
|
124
101
|
```
|
|
125
102
|
|
|
126
103
|
## Run it
|
|
@@ -8,37 +8,40 @@ that allows a user to add a note.
|
|
|
8
8
|
```html
|
|
9
9
|
<!-- src/pages/about.page.html -->
|
|
10
10
|
<h2>Notes</h2>
|
|
11
|
-
<
|
|
12
|
-
<
|
|
13
|
-
|
|
14
|
-
</
|
|
11
|
+
<section lb-list="notes">
|
|
12
|
+
<form lb-action="lb-row-insert">
|
|
13
|
+
<input lb-cell="text" placeholder="Write a note" />
|
|
14
|
+
<button type="submit">Add</button>
|
|
15
|
+
</form>
|
|
15
16
|
|
|
16
|
-
<lb-list lb-query="notes">
|
|
17
17
|
<ul>
|
|
18
18
|
<template lb-key="id">
|
|
19
19
|
<li lb-cell="text"></li>
|
|
20
20
|
</template>
|
|
21
21
|
</ul>
|
|
22
|
-
</
|
|
22
|
+
</section>
|
|
23
23
|
```
|
|
24
24
|
|
|
25
|
-
`lb-insert` on a `<form>` gathers its `lb-cell`s into a values
|
|
26
|
-
|
|
25
|
+
`lb-action="lb-row-insert"` on a `<form>` gathers its `lb-cell`s into a values
|
|
26
|
+
map on submit and sends them against the `lb-list` around it, the way an
|
|
27
|
+
`<input>` belongs to the `<form>` around it. The name is one of
|
|
28
|
+
Loadbare's reserved ones — every value beginning with `lb-` is — so it
|
|
29
|
+
needs no declaring, and nothing of yours may be called that. There's no `lb-key-value` — there's no row
|
|
27
30
|
yet.
|
|
28
31
|
|
|
29
32
|
## Implementing CRUD server-side
|
|
30
33
|
|
|
31
|
-
Our CRUD operations go into the page's
|
|
34
|
+
Our CRUD operations go into the page's requests file:
|
|
32
35
|
|
|
33
36
|
```ts
|
|
34
|
-
// src/pages/about.
|
|
37
|
+
// src/pages/about.requests.ts
|
|
35
38
|
import { patch } from "@loadbare/app/server";
|
|
36
39
|
|
|
37
|
-
export const
|
|
38
|
-
// ...
|
|
40
|
+
export const requests = {
|
|
41
|
+
// ...onPageEnter and actions unchanged...
|
|
39
42
|
crud: {
|
|
40
43
|
notes: {
|
|
41
|
-
|
|
44
|
+
rowInsert: {
|
|
42
45
|
run: async (ctx, { values }) => {
|
|
43
46
|
const note = await ctx.db.addNote(values.text);
|
|
44
47
|
return { notes: patch({ rows: [note] }) };
|
|
@@ -50,12 +53,12 @@ export const hooks = {
|
|
|
50
53
|
};
|
|
51
54
|
```
|
|
52
55
|
|
|
53
|
-
`
|
|
56
|
+
`rowInsert` is a CRUD operation, declared under `crud` and keyed by
|
|
54
57
|
query name.
|
|
55
58
|
|
|
56
59
|
Notice that we do not use the `refresh` mechanism, as that would return
|
|
57
60
|
the entire query which would be wasteful. We instead return a `patch`
|
|
58
|
-
with a single new row and the
|
|
61
|
+
with a single new row and the hub just adds the row.
|
|
59
62
|
|
|
60
63
|
## Extending the database
|
|
61
64
|
|
|
@@ -6,34 +6,32 @@ Now that we can add notes, we need to be able to delete a note.
|
|
|
6
6
|
|
|
7
7
|
```html
|
|
8
8
|
<!-- src/pages/about.page.html -->
|
|
9
|
-
<lb-list
|
|
10
|
-
<
|
|
11
|
-
<
|
|
12
|
-
<
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
</ul>
|
|
18
|
-
</lb-list>
|
|
9
|
+
<ul lb-list="notes">
|
|
10
|
+
<template lb-key="id">
|
|
11
|
+
<li>
|
|
12
|
+
<span lb-cell="text"></span>
|
|
13
|
+
<button lb-action="lb-row-delete">Delete</button>
|
|
14
|
+
</li>
|
|
15
|
+
</template>
|
|
16
|
+
</ul>
|
|
19
17
|
```
|
|
20
18
|
|
|
21
19
|
The `<li>` now has two children, so the text moves onto its own `<span>`.
|
|
22
|
-
`lb-delete`
|
|
23
|
-
scope from its ancestors.
|
|
20
|
+
`lb-row-delete` is a reserved name, so it needs no form and no declaring — just
|
|
21
|
+
the `lb-list` and `lb-key-value` already in scope from its ancestors.
|
|
24
22
|
|
|
25
23
|
## Implementing delete server-side
|
|
26
24
|
|
|
27
25
|
```ts
|
|
28
|
-
// src/pages/about.
|
|
26
|
+
// src/pages/about.requests.ts
|
|
29
27
|
import { patch } from "@loadbare/app/server";
|
|
30
28
|
|
|
31
|
-
export const
|
|
32
|
-
// ...
|
|
29
|
+
export const requests = {
|
|
30
|
+
// ...onPageEnter and actions unchanged...
|
|
33
31
|
crud: {
|
|
34
32
|
notes: {
|
|
35
|
-
// ...
|
|
36
|
-
|
|
33
|
+
// ...rowInsert unchanged...
|
|
34
|
+
rowDelete: {
|
|
37
35
|
run: async (ctx, { key }) => {
|
|
38
36
|
await ctx.db.deleteNote(key);
|
|
39
37
|
return { notes: patch({ drop: [key] }) };
|
|
@@ -6,37 +6,35 @@ Now we edit a row in place, instead of removing and re-adding it.
|
|
|
6
6
|
|
|
7
7
|
```html
|
|
8
8
|
<!-- src/pages/about.page.html -->
|
|
9
|
-
<lb-list
|
|
10
|
-
<
|
|
11
|
-
<
|
|
12
|
-
<
|
|
13
|
-
<
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
</ul>
|
|
21
|
-
</lb-list>
|
|
9
|
+
<ul lb-list="notes">
|
|
10
|
+
<template lb-key="id">
|
|
11
|
+
<li>
|
|
12
|
+
<form lb-action="lb-row-update">
|
|
13
|
+
<input lb-cell="text" />
|
|
14
|
+
<button type="submit">Save</button>
|
|
15
|
+
</form>
|
|
16
|
+
<button lb-action="lb-row-delete">Delete</button>
|
|
17
|
+
</li>
|
|
18
|
+
</template>
|
|
19
|
+
</ul>
|
|
22
20
|
```
|
|
23
21
|
|
|
24
|
-
`lb-update` gathers its `lb-cell`s the same way `lb-insert` does, but
|
|
25
|
-
also reads `lb-key` from the row it's inside — the same ancestor
|
|
26
|
-
`lb-delete` already reads.
|
|
22
|
+
`lb-row-update` gathers its `lb-cell`s the same way `lb-row-insert` does, but
|
|
23
|
+
also reads `lb-key-value` from the row it's inside — the same ancestor
|
|
24
|
+
`lb-row-delete` already reads.
|
|
27
25
|
|
|
28
26
|
## Implementing update server-side
|
|
29
27
|
|
|
30
28
|
```ts
|
|
31
|
-
// src/pages/about.
|
|
29
|
+
// src/pages/about.requests.ts
|
|
32
30
|
import { patch } from "@loadbare/app/server";
|
|
33
31
|
|
|
34
|
-
export const
|
|
35
|
-
// ...
|
|
32
|
+
export const requests = {
|
|
33
|
+
// ...onPageEnter and actions unchanged...
|
|
36
34
|
crud: {
|
|
37
35
|
notes: {
|
|
38
|
-
// ...
|
|
39
|
-
|
|
36
|
+
// ...rowInsert, rowDelete unchanged...
|
|
37
|
+
rowUpdate: {
|
|
40
38
|
run: async (ctx, { key, values }) => {
|
|
41
39
|
const note = await ctx.db.updateNote(key, values.text);
|
|
42
40
|
return { notes: patch({ rows: [note] }) };
|
|
@@ -50,7 +48,7 @@ export const hooks = {
|
|
|
50
48
|
|
|
51
49
|
With more than one note, `key` is what says which row this request
|
|
52
50
|
means. `patch({ rows: [note] })` looks like insert's response, but since
|
|
53
|
-
this id already exists,
|
|
51
|
+
this id already exists, the hub updates that row instead of adding
|
|
54
52
|
one.
|
|
55
53
|
|
|
56
54
|
## Extending the database
|
|
@@ -9,11 +9,9 @@ changes.
|
|
|
9
9
|
|
|
10
10
|
## Writing the widget
|
|
11
11
|
|
|
12
|
-
Our new custom input will no longer rely on Loadbare's form handling
|
|
13
|
-
and
|
|
14
|
-
|
|
15
|
-
discouraged. This tutorial shows only that it is possible, and exposes
|
|
16
|
-
how Loadbare internals work.
|
|
12
|
+
Our new custom input will no longer rely on Loadbare's form handling. It
|
|
13
|
+
says what happened and what its value is. Where it sits, the list, the row
|
|
14
|
+
and the cell, is added by the hub.
|
|
17
15
|
|
|
18
16
|
```html
|
|
19
17
|
<!-- src/note-input.html -->
|
|
@@ -22,13 +20,7 @@ how Loadbare internals work.
|
|
|
22
20
|
|
|
23
21
|
```ts
|
|
24
22
|
// src/note-input.browser.ts
|
|
25
|
-
import {
|
|
26
|
-
ATTR_CELL,
|
|
27
|
-
ATTR_KEY,
|
|
28
|
-
ATTR_QUERY,
|
|
29
|
-
ATTR_VALUE,
|
|
30
|
-
LB_EVENT_NAME,
|
|
31
|
-
} from "@loadbare/app/constants";
|
|
23
|
+
import { ATTR_VALUE, LB_EVENT_NAME } from "@loadbare/app/constants";
|
|
32
24
|
|
|
33
25
|
class NoteInput extends HTMLElement {
|
|
34
26
|
static observedAttributes = [ATTR_VALUE];
|
|
@@ -42,14 +34,10 @@ class NoteInput extends HTMLElement {
|
|
|
42
34
|
this.addEventListener("change", () => {
|
|
43
35
|
const input = this.querySelector("input");
|
|
44
36
|
if (!input) return;
|
|
45
|
-
const query = this.closest(`[${ATTR_QUERY}]`)?.getAttribute(ATTR_QUERY);
|
|
46
|
-
const key = this.closest(`[${ATTR_KEY}]`)?.getAttribute(ATTR_KEY);
|
|
47
|
-
const cell = this.getAttribute(ATTR_CELL);
|
|
48
|
-
if (!query || !key || !cell) return;
|
|
49
37
|
this.dispatchEvent(
|
|
50
38
|
new CustomEvent(LB_EVENT_NAME, {
|
|
51
39
|
bubbles: true,
|
|
52
|
-
detail: {
|
|
40
|
+
detail: { action: "lb-cell-change", value: input.value },
|
|
53
41
|
}),
|
|
54
42
|
);
|
|
55
43
|
});
|
|
@@ -60,40 +48,39 @@ customElements.define("note-input", NoteInput);
|
|
|
60
48
|
```
|
|
61
49
|
|
|
62
50
|
A widget with both a `.html` and a `.browser.ts` file shares one tag name; the
|
|
63
|
-
build finds each half independently. On change, it
|
|
64
|
-
`lb-
|
|
65
|
-
|
|
51
|
+
build finds each half independently. On change, it dispatches a
|
|
52
|
+
`lb-cell-change` carrying the input's value. The hub adds the list, the key
|
|
53
|
+
of the row it is in and its own `lb-cell`, the same way it does for a
|
|
54
|
+
`lb-row-update` form.
|
|
66
55
|
|
|
67
56
|
## Using it
|
|
68
57
|
|
|
69
58
|
```html
|
|
70
59
|
<!-- src/pages/about.page.html -->
|
|
71
|
-
<lb-list
|
|
72
|
-
<
|
|
73
|
-
<
|
|
74
|
-
<
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
</ul>
|
|
80
|
-
</lb-list>
|
|
60
|
+
<ul lb-list="notes">
|
|
61
|
+
<template lb-key="id">
|
|
62
|
+
<li>
|
|
63
|
+
<note-input lb-cell="text"></note-input>
|
|
64
|
+
<button lb-action="lb-row-delete">Delete</button>
|
|
65
|
+
</li>
|
|
66
|
+
</template>
|
|
67
|
+
</ul>
|
|
81
68
|
```
|
|
82
69
|
|
|
83
|
-
The
|
|
70
|
+
The `lb-row-update` form and Save button are gone — `note-input` commits on
|
|
84
71
|
every change, so there's nothing left to batch.
|
|
85
72
|
|
|
86
73
|
## Answering it server-side
|
|
87
74
|
|
|
88
75
|
```ts
|
|
89
|
-
// src/pages/about.
|
|
76
|
+
// src/pages/about.requests.ts
|
|
90
77
|
import { patch } from "@loadbare/app/server";
|
|
91
78
|
|
|
92
|
-
export const
|
|
93
|
-
// ...
|
|
79
|
+
export const requests = {
|
|
80
|
+
// ...onPageEnter and actions unchanged...
|
|
94
81
|
crud: {
|
|
95
82
|
notes: {
|
|
96
|
-
// ...
|
|
83
|
+
// ...rowInsert, rowDelete, rowUpdate unchanged...
|
|
97
84
|
cellChange: {
|
|
98
85
|
run: async (ctx, { key, value }) => {
|
|
99
86
|
const note = await ctx.db.updateNote(key, value);
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@loadbare/app",
|
|
3
3
|
"description": "High performance web app framework for server-bound applications",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.7.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|
|
7
7
|
"dist",
|
|
@@ -18,7 +18,6 @@
|
|
|
18
18
|
".": "./dist/hub/lb-hub.browser.js",
|
|
19
19
|
"./constants": "./dist/core/lb-constants.js",
|
|
20
20
|
"./types": "./dist/core/lb-types.js",
|
|
21
|
-
"./rows": "./dist/hub/lb-rows.js",
|
|
22
21
|
"./server": "./dist/server/lb-server.js",
|
|
23
22
|
"./express": "./dist/server/lb-express.js",
|
|
24
23
|
"./build": "./dist/build/elements.js"
|
|
@@ -33,7 +32,7 @@
|
|
|
33
32
|
"prebuild": "node --eval \"fs.rmSync('dist',{recursive:true,force:true})\" --input-type=module",
|
|
34
33
|
"build:server": "tsc --project tsconfig.build.json",
|
|
35
34
|
"build": "npm run build:server",
|
|
36
|
-
"postbuild": "node --eval \"fs.chmodSync('dist/build/cli.js', 0o755)\" --input-type=module",
|
|
35
|
+
"postbuild": "node --eval \"fs.chmodSync('dist/build/cli.js', 0o755)\" --input-type=module && node ../../scripts/check-dist-imports.mjs",
|
|
37
36
|
"test": "tsx --test \"tests/**/*.test.ts\"",
|
|
38
37
|
"typecheck": "tsc --noEmit",
|
|
39
38
|
"format": "prettier --write .",
|
package/dist/hub/lb-rows.d.ts
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import type { Projection, QueryResult } from "../core/lb-types";
|
|
2
|
-
/**
|
|
3
|
-
* Where a row belongs, called with the row detached on its first appearance.
|
|
4
|
-
*
|
|
5
|
-
* The default puts it immediately before the template, so rows accumulate in
|
|
6
|
-
* the order they arrive and the template stays put as the insertion marker.
|
|
7
|
-
*/
|
|
8
|
-
export type Place = (row: Element, tuple: QueryResult, template: HTMLTemplateElement) => void;
|
|
9
|
-
/**
|
|
10
|
-
* Land a projection in a widget's subtree.
|
|
11
|
-
*
|
|
12
|
-
* `rows` is the whole set, so it decides membership and order: every row is
|
|
13
|
-
* placed in the order given, and a row whose key did not arrive is gone.
|
|
14
|
-
* `patch` disturbs only what it names — a row it did not mention keeps its
|
|
15
|
-
* contents and its position.
|
|
16
|
-
*/
|
|
17
|
-
export declare function applyRows(scope: Element, result: Projection, place?: Place): void;
|
|
18
|
-
//# sourceMappingURL=lb-rows.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"lb-rows.d.ts","sourceRoot":"","sources":["../../hub/lb-rows.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAGhE;;;;;GAKG;AACH,MAAM,MAAM,KAAK,GAAG,CAClB,GAAG,EAAE,OAAO,EACZ,KAAK,EAAE,WAAW,EAClB,QAAQ,EAAE,mBAAmB,KAC1B,IAAI,CAAC;AAsCV;;;;;;;GAOG;AACH,wBAAgB,SAAS,CACvB,KAAK,EAAE,OAAO,EACd,MAAM,EAAE,UAAU,EAClB,KAAK,GAAE,KAAc,GACpB,IAAI,CAkDN"}
|
package/dist/hub/lb-rows.js
DELETED
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
/// <reference lib="dom" />
|
|
2
|
-
/**
|
|
3
|
-
* The row machinery a list widget shares — see docs/reference/custom-elements.md,
|
|
4
|
-
* "Accepting rows".
|
|
5
|
-
*
|
|
6
|
-
* A widget owns where a row goes and nothing else. Cloning the template,
|
|
7
|
-
* matching a tuple to the row already showing it, and filling that row are
|
|
8
|
-
* the same in every list, so they are here, and a widget that sorts or groups
|
|
9
|
-
* supplies one function rather than a second implementation of all of it.
|
|
10
|
-
*/
|
|
11
|
-
import { ATTR_KEY, ATTR_ROW_COUNT } from "../core/lb-constants";
|
|
12
|
-
import { applyTuple } from "./lb-apply";
|
|
13
|
-
const before = (row, _tuple, template) => {
|
|
14
|
-
template.parentElement.insertBefore(row, template);
|
|
15
|
-
};
|
|
16
|
-
/** The row template: the one the page author wrote inside this widget. */
|
|
17
|
-
function templateIn(scope) {
|
|
18
|
-
const template = scope.querySelector("template");
|
|
19
|
-
if (!template) {
|
|
20
|
-
console.error(`lb-rows: <${scope.localName}> has no <template> to clone`);
|
|
21
|
-
return null;
|
|
22
|
-
}
|
|
23
|
-
if (!template.getAttribute(ATTR_KEY)) {
|
|
24
|
-
console.error(`lb-rows: the <template> in <${scope.localName}> has no ${ATTR_KEY} ` +
|
|
25
|
-
`naming the column that identifies a row`);
|
|
26
|
-
return null;
|
|
27
|
-
}
|
|
28
|
-
return template;
|
|
29
|
-
}
|
|
30
|
-
/**
|
|
31
|
-
* The rows already showing, by the key value each carries.
|
|
32
|
-
*
|
|
33
|
-
* `lb-key` names the key column on the template and carries the key value on
|
|
34
|
-
* a live row. One name in two positions, and never ambiguous, because a
|
|
35
|
-
* template is not a row.
|
|
36
|
-
*/
|
|
37
|
-
function showing(scope, template) {
|
|
38
|
-
const rows = new Map();
|
|
39
|
-
for (const el of scope.querySelectorAll(`[${ATTR_KEY}]`)) {
|
|
40
|
-
if (el !== template)
|
|
41
|
-
rows.set(el.getAttribute(ATTR_KEY), el);
|
|
42
|
-
}
|
|
43
|
-
return rows;
|
|
44
|
-
}
|
|
45
|
-
/**
|
|
46
|
-
* Land a projection in a widget's subtree.
|
|
47
|
-
*
|
|
48
|
-
* `rows` is the whole set, so it decides membership and order: every row is
|
|
49
|
-
* placed in the order given, and a row whose key did not arrive is gone.
|
|
50
|
-
* `patch` disturbs only what it names — a row it did not mention keeps its
|
|
51
|
-
* contents and its position.
|
|
52
|
-
*/
|
|
53
|
-
export function applyRows(scope, result, place = before) {
|
|
54
|
-
const template = templateIn(scope);
|
|
55
|
-
if (!template)
|
|
56
|
-
return;
|
|
57
|
-
const keyCell = template.getAttribute(ATTR_KEY);
|
|
58
|
-
const rows = showing(scope, template);
|
|
59
|
-
const upsert = (tuple) => {
|
|
60
|
-
const key = tuple[keyCell];
|
|
61
|
-
if (key === undefined) {
|
|
62
|
-
console.error(`lb-rows: a tuple for <${scope.localName}> has no '${keyCell}' cell`);
|
|
63
|
-
return null;
|
|
64
|
-
}
|
|
65
|
-
let row = rows.get(key);
|
|
66
|
-
const fresh = row === undefined;
|
|
67
|
-
if (!row) {
|
|
68
|
-
row = template.content.firstElementChild.cloneNode(true);
|
|
69
|
-
row.setAttribute(ATTR_KEY, key);
|
|
70
|
-
rows.set(key, row);
|
|
71
|
-
}
|
|
72
|
-
// Fill before insertion. The attributes are already there when the row
|
|
73
|
-
// upgrades, which is the same thing that makes hydration and refresh one
|
|
74
|
-
// operation everywhere else.
|
|
75
|
-
applyTuple(row, tuple);
|
|
76
|
-
if (fresh || result.op === "rows")
|
|
77
|
-
place(row, tuple, template);
|
|
78
|
-
return key;
|
|
79
|
-
};
|
|
80
|
-
if (result.op === "rows") {
|
|
81
|
-
const arrived = new Set();
|
|
82
|
-
for (const tuple of result.rows) {
|
|
83
|
-
const key = upsert(tuple);
|
|
84
|
-
if (key !== null)
|
|
85
|
-
arrived.add(key);
|
|
86
|
-
}
|
|
87
|
-
for (const [key, row] of rows)
|
|
88
|
-
if (!arrived.has(key))
|
|
89
|
-
row.remove();
|
|
90
|
-
}
|
|
91
|
-
else {
|
|
92
|
-
for (const tuple of result.rows ?? [])
|
|
93
|
-
upsert(tuple);
|
|
94
|
-
for (const key of result.drop ?? [])
|
|
95
|
-
rows.get(key)?.remove();
|
|
96
|
-
}
|
|
97
|
-
// How many rows are showing, counted from the DOM rather than from either
|
|
98
|
-
// branch above, so a set and a patch report the same fact the same way.
|
|
99
|
-
//
|
|
100
|
-
// It is stamped here because only this function knows the count: it is the
|
|
101
|
-
// one conditional a page cannot be sent, since the server answers with rows
|
|
102
|
-
// and says nothing about how many survived reconciliation. A page says what
|
|
103
|
-
// an empty list looks like in a stylesheet, and no list widget carries code
|
|
104
|
-
// for it. See docs/reference/data-binding.md.
|
|
105
|
-
scope.setAttribute(ATTR_ROW_COUNT, String(showing(scope, template).size));
|
|
106
|
-
}
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Assembling a chrome file and its pages — see build/assemble.ts.
|
|
3
|
-
*
|
|
4
|
-
* The chrome and pages together are one write to a temp tree per test,
|
|
5
|
-
* since assemble() reads from disk rather than taking sources in memory.
|
|
6
|
-
*/
|
|
7
|
-
export {};
|
|
8
|
-
//# sourceMappingURL=assemble.test.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"assemble.test.d.ts","sourceRoot":"","sources":["../../tests/assemble.test.ts"],"names":[],"mappings":"AAAA;;;;;GAKG"}
|