@loadbare/app 0.9.0 → 0.11.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 -3
- package/dist/build/assemble.d.ts.map +1 -1
- package/dist/build/assemble.js +107 -90
- package/dist/build/assemble.js.map +1 -1
- package/dist/build/expand.d.ts +6 -1
- package/dist/build/expand.d.ts.map +1 -1
- package/dist/build/expand.js +112 -26
- package/dist/build/expand.js.map +1 -1
- package/dist/build/locations.d.ts +2 -3
- package/dist/build/locations.d.ts.map +1 -1
- package/dist/build/locations.js +2 -3
- package/dist/build/locations.js.map +1 -1
- package/dist/build/pages.d.ts +3 -4
- package/dist/build/pages.d.ts.map +1 -1
- package/dist/build/pages.js +3 -4
- package/dist/build/pages.js.map +1 -1
- package/dist/core/lb-constants.d.ts +27 -24
- package/dist/core/lb-constants.d.ts.map +1 -1
- package/dist/core/lb-constants.js +103 -168
- package/dist/core/lb-constants.js.map +1 -1
- package/dist/core/lb-types.d.ts +64 -77
- package/dist/core/lb-types.d.ts.map +1 -1
- package/dist/core/lb-types.js +40 -7
- package/dist/core/lb-types.js.map +1 -1
- package/dist/hub/lb-apply.d.ts +47 -37
- package/dist/hub/lb-apply.d.ts.map +1 -1
- package/dist/hub/lb-apply.js +195 -199
- package/dist/hub/lb-apply.js.map +1 -1
- 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 +410 -449
- package/dist/hub/lb-hub.browser.js.map +1 -1
- package/dist/server/lb-express.d.ts +5 -5
- package/dist/server/lb-express.d.ts.map +1 -1
- package/dist/server/lb-express.js +35 -66
- package/dist/server/lb-express.js.map +1 -1
- package/dist/server/lb-server.d.ts +77 -135
- package/dist/server/lb-server.d.ts.map +1 -1
- package/dist/server/lb-server.js +132 -79
- package/dist/server/lb-server.js.map +1 -1
- package/docs/TECHREF-1.0.md +908 -585
- package/docs/comparison.md +243 -185
- package/docs/prior-art.md +15 -14
- package/docs/reference/builder.md +9 -3
- package/docs/reference/chrome.md +107 -56
- package/docs/reference/custom-elements.md +291 -173
- package/docs/reference/data-binding.md +381 -374
- package/docs/reference/overview.md +12 -10
- package/docs/reference/page-files.md +164 -99
- package/docs/reference/server.md +2 -2
- package/docs/reference/widgets.md +104 -110
- package/docs/roadmap.md +32 -39
- package/docs/terms-of-art.md +57 -0
- package/docs/testing.md +97 -68
- package/docs/theory.md +92 -58
- package/docs/tutorials/010-pages-and-navigation.md +20 -12
- package/docs/tutorials/020-css.md +6 -3
- package/docs/tutorials/030-html-decomposition.md +9 -7
- package/docs/tutorials/040-displaying-data.md +30 -13
- package/docs/tutorials/{050-actions.md → 050-requests.md} +25 -15
- package/docs/tutorials/060-custom-element-code.md +17 -16
- package/docs/tutorials/065-conditional-rendering.md +34 -23
- package/docs/tutorials/070-displaying-a-list.md +29 -21
- package/docs/tutorials/072-inserting-into-a-list.md +24 -16
- package/docs/tutorials/074-deleting-from-a-list.md +9 -7
- package/docs/tutorials/076-updating-a-list-item.md +11 -10
- package/docs/tutorials/080-widget-requests.md +71 -43
- package/docs/tutorials/090-using-widget-libraries.md +22 -22
- package/docs/what-does-loadbare-extend.md +124 -0
- package/package.json +1 -1
- package/skills/loadbare-app/SKILL.md +201 -123
- package/skills/loadbare-app/references/TECHREF-1.0.md +908 -585
- package/skills/loadbare-app/references/builder.md +9 -3
- package/skills/loadbare-app/references/chrome.md +107 -56
- package/skills/loadbare-app/references/custom-elements.md +291 -173
- package/skills/loadbare-app/references/data-binding.md +381 -374
- package/skills/loadbare-app/references/overview.md +12 -10
- package/skills/loadbare-app/references/page-files.md +164 -99
- package/skills/loadbare-app/references/server.md +2 -2
- package/skills/loadbare-app/references/widgets.md +104 -110
- package/docs/analysis-accidental-complexity.md +0 -149
- package/docs/analysis-closed-set.md +0 -210
|
@@ -7,21 +7,23 @@ data-centric apps, displaying data.
|
|
|
7
7
|
|
|
8
8
|
## Data Binding
|
|
9
9
|
|
|
10
|
-
Loadbare
|
|
11
|
-
|
|
10
|
+
Loadbare displays rows. A query is a name for rows, of kind `row` or
|
|
11
|
+
`rows`, and the server declares each query a page shows. We begin with a
|
|
12
|
+
query of kind `row` holding one value.
|
|
12
13
|
|
|
13
|
-
All data that is displayed in the app
|
|
14
|
-
is implemented on the server. In the code below we
|
|
15
|
-
|
|
16
|
-
the
|
|
14
|
+
All data that is displayed in the app belongs to a named query that
|
|
15
|
+
is implemented on the server. In the code below we give the div
|
|
16
|
+
the query `visits` with `lb-query`, then specify that the span will display
|
|
17
|
+
the column `count` with `lb-column`.
|
|
17
18
|
|
|
18
19
|
```html
|
|
19
20
|
<!-- src/pages/about.page.html -->
|
|
21
|
+
<title>About</title>
|
|
20
22
|
<h1>About</h1>
|
|
21
23
|
<p class="about-note">This is the about page.</p>
|
|
22
24
|
|
|
23
|
-
<div lb-
|
|
24
|
-
<p>This page has been visited <span lb-
|
|
25
|
+
<div lb-query="visits">
|
|
26
|
+
<p>This page has been visited <span lb-column="count"></span> times.</p>
|
|
25
27
|
</div>
|
|
26
28
|
```
|
|
27
29
|
|
|
@@ -32,11 +34,26 @@ Put the query into `about.queries.ts`, which uses the same page stem,
|
|
|
32
34
|
|
|
33
35
|
```ts
|
|
34
36
|
// src/pages/about.queries.ts
|
|
37
|
+
import { row } from "@loadbare/app/server";
|
|
38
|
+
|
|
35
39
|
export const queries = {
|
|
36
|
-
visits: async (ctx) => ({
|
|
40
|
+
visits: row("page", async (ctx) => ({
|
|
41
|
+
page: "about",
|
|
42
|
+
count: String(await ctx.db.visitCount()),
|
|
43
|
+
})),
|
|
37
44
|
};
|
|
38
45
|
```
|
|
39
46
|
|
|
47
|
+
`row()` declares a query of kind `row`, and `rows()` declares one of kind
|
|
48
|
+
`rows`. The first argument names the key, the column whose value
|
|
49
|
+
identifies a row. Every query has a key, so a row holding a single count
|
|
50
|
+
carries a constant one, here `page`. The markup names neither the kind nor
|
|
51
|
+
the key: the server sends both with every answer.
|
|
52
|
+
|
|
53
|
+
The hub lands the row on the `<div>` itself, since it holds no
|
|
54
|
+
`<template>`, and sets the text of each element whose `lb-column` names one
|
|
55
|
+
of the row's columns.
|
|
56
|
+
|
|
40
57
|
We have not yet implemented `ctx.db`, we will do that shortly.
|
|
41
58
|
|
|
42
59
|
## Write the Before Get Hook
|
|
@@ -160,10 +177,10 @@ to About.
|
|
|
160
177
|
View source on either page: the host HTML is exactly what's on disk. Only
|
|
161
178
|
the `<span>`'s text changes, never the markup around it.
|
|
162
179
|
|
|
163
|
-
This tutorial only used `lb-
|
|
164
|
-
The full attribute vocabulary —
|
|
165
|
-
|
|
180
|
+
This tutorial only used `lb-query` and `lb-column` to display one value.
|
|
181
|
+
The full attribute vocabulary — requests, inserting, updating and deleting
|
|
182
|
+
rows, and many rows at once — is in [Data Binding](../reference/data-binding.md).
|
|
166
183
|
|
|
167
184
|
---
|
|
168
185
|
Prev: [HTML Decomposition](./030-html-decomposition.md)
|
|
169
|
-
Next: [
|
|
186
|
+
Next: [Requests](./050-requests.md)
|
|
@@ -1,35 +1,41 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Requests
|
|
2
2
|
|
|
3
|
-
So far we have used data binding to display a single
|
|
4
|
-
query. Before we go on to
|
|
5
|
-
which is a named routine on the server that
|
|
6
|
-
client.
|
|
3
|
+
So far we have used data binding to display a single column of a named
|
|
4
|
+
query. Before we go on to inserting, updating and deleting rows, we will
|
|
5
|
+
show a declared request, which is a named routine on the server that can be
|
|
6
|
+
invoked from the client.
|
|
7
7
|
|
|
8
|
-
##
|
|
8
|
+
## A request in the client
|
|
9
9
|
|
|
10
10
|
Modify the about page to include a button that requests the server
|
|
11
11
|
to reset the visit count.
|
|
12
12
|
|
|
13
13
|
```html
|
|
14
14
|
<!-- src/pages/about.page.html -->
|
|
15
|
+
<title>About</title>
|
|
15
16
|
<h1>About</h1>
|
|
16
17
|
<p class="about-note">This is the about page.</p>
|
|
17
18
|
|
|
18
|
-
<div lb-
|
|
19
|
-
<p>This page has been visited <span lb-
|
|
20
|
-
<button lb-
|
|
19
|
+
<div lb-query="visits">
|
|
20
|
+
<p>This page has been visited <span lb-column="count"></span> times.</p>
|
|
21
|
+
<button lb-request="resetVisits">Reset count</button>
|
|
21
22
|
</div>
|
|
22
23
|
```
|
|
23
24
|
|
|
24
|
-
|
|
25
|
+
`lb-request` names the request an element issues when it commits. A button
|
|
26
|
+
commits on click, a form on submit, and a control such as an `<input>` on
|
|
27
|
+
change. The hub sends the request name to the server, together with the
|
|
28
|
+
query the button sits in.
|
|
25
29
|
|
|
26
|
-
|
|
30
|
+
## Implement the request on the server
|
|
31
|
+
|
|
32
|
+
Declared requests go under `handlers` in the page's requests file:
|
|
27
33
|
|
|
28
34
|
```ts
|
|
29
35
|
// src/pages/about.requests.ts
|
|
30
36
|
export const requests = {
|
|
31
37
|
onPageEnter: (ctx) => ctx.db.recordVisit(),
|
|
32
|
-
|
|
38
|
+
handlers: {
|
|
33
39
|
resetVisits: {
|
|
34
40
|
run: (ctx) => ctx.db.resetVisits(),
|
|
35
41
|
refresh: ["visits"],
|
|
@@ -38,10 +44,14 @@ export const requests = {
|
|
|
38
44
|
};
|
|
39
45
|
```
|
|
40
46
|
|
|
41
|
-
|
|
47
|
+
A handler is a `run` paired with a `refresh` list. After the `run` code
|
|
42
48
|
is executed, all of the named queries in `refresh` are rerun and their
|
|
43
|
-
results go to the browser, where the Loadbare hub `<lb-hub>`
|
|
44
|
-
the
|
|
49
|
+
results go to the browser, where the Loadbare hub `<lb-hub>` lands them on
|
|
50
|
+
the elements that name them.
|
|
51
|
+
|
|
52
|
+
The server runs only the handlers a page declares, and refuses any other
|
|
53
|
+
request name. Names beginning with `lb-` are Loadbare's; the application
|
|
54
|
+
should never name anything with the `lb-` prefix anywhere.
|
|
45
55
|
|
|
46
56
|
## Extending the database
|
|
47
57
|
|
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
# Custom Element Code
|
|
2
2
|
|
|
3
|
-
So far we have simple
|
|
4
|
-
|
|
3
|
+
So far we have simple data binding of one row, and declared requests
|
|
4
|
+
that the server implements.
|
|
5
5
|
|
|
6
|
-
The natural next step is
|
|
7
|
-
custom
|
|
8
|
-
|
|
9
|
-
first implement a much simpler custom element to see how they work.
|
|
6
|
+
The natural next step is inserting, updating and deleting rows. Before
|
|
7
|
+
that, we will implement a simple custom element to see how custom element
|
|
8
|
+
code works.
|
|
10
9
|
|
|
11
10
|
## Using the element
|
|
12
11
|
|
|
@@ -16,15 +15,17 @@ then implement.
|
|
|
16
15
|
|
|
17
16
|
```html
|
|
18
17
|
<!-- src/pages/about.page.html -->
|
|
19
|
-
<div lb-
|
|
20
|
-
<p>This page has been <visit-count lb-
|
|
21
|
-
<button lb-
|
|
18
|
+
<div lb-query="visits">
|
|
19
|
+
<p>This page has been <visit-count lb-column="count"></visit-count>.</p>
|
|
20
|
+
<button lb-request="resetVisits">Reset count</button>
|
|
22
21
|
</div>
|
|
23
22
|
```
|
|
24
23
|
|
|
25
24
|
When we drop in a custom element, we bind the custom element
|
|
26
|
-
to the
|
|
27
|
-
using `lb-
|
|
25
|
+
to the column `count` the same as we did for the span,
|
|
26
|
+
using `lb-column="count"`. The hub never replaces a custom element's
|
|
27
|
+
content. It stamps the value on the element as the attribute
|
|
28
|
+
`lb-column-value`, and the element renders it.
|
|
28
29
|
|
|
29
30
|
## Writing the class
|
|
30
31
|
|
|
@@ -43,10 +44,10 @@ be able to pull it into the browser.
|
|
|
43
44
|
|
|
44
45
|
```ts
|
|
45
46
|
// src/visit-count.browser.ts
|
|
46
|
-
import {
|
|
47
|
+
import { ATTR_COLUMN_VALUE } from "@loadbare/app/constants";
|
|
47
48
|
|
|
48
49
|
class VisitCount extends HTMLElement {
|
|
49
|
-
static observedAttributes = [
|
|
50
|
+
static observedAttributes = [ATTR_COLUMN_VALUE];
|
|
50
51
|
|
|
51
52
|
attributeChangedCallback(_name: string, _old: string, value: string) {
|
|
52
53
|
const count = Number(value);
|
|
@@ -62,8 +63,8 @@ exactly, and we don't use Shadow DOM. For more specifics, see
|
|
|
62
63
|
[Custom Elements](../reference/custom-elements.md#code).
|
|
63
64
|
|
|
64
65
|
In brief, custom elements can name which attributes should trigger a callback
|
|
65
|
-
when their values change. Here we specify only one, the
|
|
66
|
-
|
|
66
|
+
when their values change. Here we specify only one, the stamp the hub
|
|
67
|
+
writes with a column's value, defined in constant `ATTR_COLUMN_VALUE`. When the
|
|
67
68
|
attribute changes, `attributeChangedCallback` fires and rewrites the text.
|
|
68
69
|
|
|
69
70
|
## Run it
|
|
@@ -76,5 +77,5 @@ Open the About page. Where the plain number was, it now reads "visited 7
|
|
|
76
77
|
times" (or "visited 1 time" the first time).
|
|
77
78
|
|
|
78
79
|
---
|
|
79
|
-
Prev: [
|
|
80
|
+
Prev: [Requests](./050-requests.md)
|
|
80
81
|
Next: [Conditional Rendering](./065-conditional-rendering.md)
|
|
@@ -19,7 +19,8 @@ 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
|
-
<
|
|
22
|
+
<div lb-query="signup">
|
|
23
|
+
<signup-wizard lb-column="step">
|
|
23
24
|
<section data-step="name">
|
|
24
25
|
<h3>1. Name</h3>
|
|
25
26
|
<p>This step is not hidden, because it's the one the page ships showing.</p>
|
|
@@ -31,27 +32,32 @@ two of them carry the standard `hidden` attribute.
|
|
|
31
32
|
<h3>3. Confirm</h3>
|
|
32
33
|
</section>
|
|
33
34
|
|
|
34
|
-
<button data-nav="back" lb-
|
|
35
|
-
<button data-nav="next" lb-
|
|
36
|
-
</
|
|
35
|
+
<button data-nav="back" lb-request="wizardBack" disabled>Back</button>
|
|
36
|
+
<button data-nav="next" lb-request="wizardNext">Next</button>
|
|
37
|
+
</signup-wizard>
|
|
38
|
+
</div>
|
|
37
39
|
```
|
|
38
40
|
|
|
39
|
-
`lb-
|
|
41
|
+
`lb-column="step"` on `<signup-wizard>` works exactly the way it did on
|
|
40
42
|
`<visit-count>` in [Custom Element Code](./060-custom-element-code.md):
|
|
41
|
-
because the tag
|
|
42
|
-
instead of `textContent`, and the class decides
|
|
43
|
-
and Next are plain buttons carrying `lb-
|
|
44
|
-
Reset button in [
|
|
45
|
-
arithmetic of its own.
|
|
43
|
+
because the tag is a custom element, the value lands on the
|
|
44
|
+
`lb-column-value` attribute instead of `textContent`, and the class decides
|
|
45
|
+
what to do with it. Back and Next are plain buttons carrying `lb-request`,
|
|
46
|
+
the same mechanism as the Reset button in [Requests](./050-requests.md) —
|
|
47
|
+
the wizard owns no arithmetic of its own.
|
|
48
|
+
|
|
49
|
+
The element is named `signup-wizard` rather than anything beginning with
|
|
50
|
+
`lb-`. The `lb-` prefix is Loadbare's, and the application should never
|
|
51
|
+
name anything with the `lb-` prefix anywhere.
|
|
46
52
|
|
|
47
53
|
## Writing the class
|
|
48
54
|
|
|
49
55
|
```ts
|
|
50
|
-
// src/
|
|
51
|
-
import {
|
|
56
|
+
// src/signup-wizard.browser.ts
|
|
57
|
+
import { ATTR_COLUMN_VALUE } from "@loadbare/app/constants";
|
|
52
58
|
|
|
53
|
-
class
|
|
54
|
-
static observedAttributes = [
|
|
59
|
+
class SignupWizard extends HTMLElement {
|
|
60
|
+
static observedAttributes = [ATTR_COLUMN_VALUE];
|
|
55
61
|
|
|
56
62
|
attributeChangedCallback(_name: string, _old: string, value: string) {
|
|
57
63
|
const steps = [...this.querySelectorAll<HTMLElement>("[data-step]")];
|
|
@@ -69,7 +75,7 @@ class LbWizard extends HTMLElement {
|
|
|
69
75
|
}
|
|
70
76
|
}
|
|
71
77
|
|
|
72
|
-
customElements.define("
|
|
78
|
+
customElements.define("signup-wizard", SignupWizard);
|
|
73
79
|
```
|
|
74
80
|
|
|
75
81
|
The whole conditional is one line: `step.hidden = step.dataset.step !==
|
|
@@ -81,7 +87,7 @@ all of them from the current value.
|
|
|
81
87
|
|
|
82
88
|
A multi-step signup form is exactly the kind of thing a user gets
|
|
83
89
|
interrupted out of when they close the tab, the browser crashes, they come
|
|
84
|
-
back an hour later. If `step` were just a private field on the `
|
|
90
|
+
back an hour later. If `step` were just a private field on the `SignupWizard`
|
|
85
91
|
instance, none of that would survive: a reload constructs a fresh element
|
|
86
92
|
with no memory of where the user was, and they'd have to start over from
|
|
87
93
|
Step 1. So the step lives on the server, the same way the visit count
|
|
@@ -89,17 +95,22 @@ does, and every reload asks for it again instead of assuming it.
|
|
|
89
95
|
|
|
90
96
|
```ts
|
|
91
97
|
// src/pages/about.queries.ts
|
|
98
|
+
import { row } from "@loadbare/app/server";
|
|
99
|
+
|
|
92
100
|
export const queries = {
|
|
93
|
-
// ...visits
|
|
94
|
-
signup: async (ctx) => ({
|
|
101
|
+
// ...visits unchanged...
|
|
102
|
+
signup: row("id", async (ctx) => ({
|
|
103
|
+
id: "signup",
|
|
104
|
+
step: await ctx.db.signupStep(),
|
|
105
|
+
})),
|
|
95
106
|
};
|
|
96
107
|
```
|
|
97
108
|
|
|
98
109
|
```ts
|
|
99
110
|
// src/pages/about.requests.ts
|
|
100
111
|
export const requests = {
|
|
101
|
-
// ...onPageEnter
|
|
102
|
-
|
|
112
|
+
// ...onPageEnter unchanged...
|
|
113
|
+
handlers: {
|
|
103
114
|
resetVisits: { run: (ctx) => ctx.db.resetVisits(), refresh: ["visits"] },
|
|
104
115
|
wizardBack: { run: (ctx) => ctx.db.moveSignup(-1), refresh: ["signup"] },
|
|
105
116
|
wizardNext: { run: (ctx) => ctx.db.moveSignup(1), refresh: ["signup"] },
|
|
@@ -109,7 +120,7 @@ export const requests = {
|
|
|
109
120
|
|
|
110
121
|
Back and Next both `refresh: ["signup"]` — a click sends the request, the
|
|
111
122
|
server computes and clamps the new step, and the answer comes back through
|
|
112
|
-
the normal query
|
|
123
|
+
the normal query and column path. The browser never shows a step the server
|
|
113
124
|
hasn't confirmed, the same rule the visit count follows for its number.
|
|
114
125
|
|
|
115
126
|
## Extending the database
|
|
@@ -120,7 +131,7 @@ const STEPS = ["name", "address", "confirm"];
|
|
|
120
131
|
|
|
121
132
|
export function openDb() {
|
|
122
133
|
return {
|
|
123
|
-
// ...visitCount(), recordVisit(), resetVisits()
|
|
134
|
+
// ...visitCount(), recordVisit(), resetVisits() unchanged...
|
|
124
135
|
async signupStep() {
|
|
125
136
|
return (await read()).step ?? STEPS[0];
|
|
126
137
|
},
|
|
@@ -150,7 +161,7 @@ both buttons are enabled.
|
|
|
150
161
|
Now click Next once more so Step 3 is showing, and reload the page. Step
|
|
151
162
|
3 is still what's showing — not Step 1. If `step` had been client-only
|
|
152
163
|
state instead of a query result, the reload would have built a brand new
|
|
153
|
-
`
|
|
164
|
+
`SignupWizard` with nothing to tell it otherwise, and it would have landed
|
|
154
165
|
back on Step 1 like the very first visit.
|
|
155
166
|
|
|
156
167
|
View source: all three `<section>`s are on the page the whole time. Only
|
|
@@ -1,24 +1,28 @@
|
|
|
1
1
|
# Displaying a List
|
|
2
2
|
|
|
3
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
|
|
5
|
-
hub
|
|
4
|
+
answers with many rows, and markup to show them. No custom element is
|
|
5
|
+
involved: the hub lands the rows itself.
|
|
6
6
|
|
|
7
7
|
## Writing the query
|
|
8
8
|
|
|
9
9
|
```ts
|
|
10
10
|
// src/pages/about.queries.ts
|
|
11
|
-
import {
|
|
11
|
+
import { row, rows } from "@loadbare/app/server";
|
|
12
12
|
|
|
13
13
|
export const queries = {
|
|
14
|
-
visits: row(async (ctx) => ({
|
|
15
|
-
|
|
14
|
+
visits: row("page", async (ctx) => ({
|
|
15
|
+
page: "about",
|
|
16
|
+
count: String(await ctx.db.visitCount()),
|
|
17
|
+
})),
|
|
18
|
+
notes: rows("id", (ctx) => ctx.db.notes()),
|
|
16
19
|
};
|
|
17
20
|
```
|
|
18
21
|
|
|
19
|
-
Every query
|
|
20
|
-
and `
|
|
21
|
-
A page that needs the same data both ways declares two queries.
|
|
22
|
+
Every query declares its kind and its key. `row()` answers with one row
|
|
23
|
+
and `rows()` answers with any number of them, and a name never answers with
|
|
24
|
+
both. A page that needs the same data both ways declares two queries. The
|
|
25
|
+
key of `notes` is `id`, the column whose value identifies each note.
|
|
22
26
|
|
|
23
27
|
## Extending the database
|
|
24
28
|
|
|
@@ -66,17 +70,18 @@ export function openDb() {
|
|
|
66
70
|
`write` is now a merge, not a whole-file replace — otherwise saving
|
|
67
71
|
`notes` would erase `visitCount`.
|
|
68
72
|
|
|
69
|
-
## A
|
|
73
|
+
## A row template
|
|
70
74
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
75
|
+
An element whose query answers with many rows needs to be told what one
|
|
76
|
+
row looks like, and we say so with a `<template>` inside it, the row
|
|
77
|
+
template. The hub clones that template once per row and fills each clone,
|
|
78
|
+
a live row, the same way it filled the `visits` element.
|
|
74
79
|
|
|
75
80
|
> Loadbare does not use Shadow DOM because Shadow DOM is generally obtuse,
|
|
76
81
|
> interferes with CSS scoping, and makes templates difficult to supply at point
|
|
77
82
|
> of use. Shadow DOM also prevents `closest()` from reaching outside an
|
|
78
83
|
> element's shadow root, and `closest()` is fundamental
|
|
79
|
-
> to
|
|
84
|
+
> to finding the row and the query an element belongs to.
|
|
80
85
|
>
|
|
81
86
|
> Rather than mess with the Shadow DOM, we use Light DOM.
|
|
82
87
|
> We take advantage of the fact that `<template>`
|
|
@@ -84,22 +89,25 @@ each clone the same way it fills any other scope.
|
|
|
84
89
|
> into the light DOM, where it's easy to see how a row
|
|
85
90
|
> is going to be rendered.
|
|
86
91
|
|
|
87
|
-
|
|
88
|
-
`
|
|
89
|
-
|
|
90
|
-
row
|
|
91
|
-
|
|
92
|
+
The markup is the same `lb-query` we used for one row. The server
|
|
93
|
+
declared `notes` of kind `rows` with the key `id`, so the markup says
|
|
94
|
+
neither. Each live row the hub clones is stamped with `lb-key-value`,
|
|
95
|
+
holding that row's key, which is how the hub matches a row that arrives
|
|
96
|
+
again to the live row already showing it.
|
|
92
97
|
|
|
93
98
|
```html
|
|
94
99
|
<!-- src/pages/about.page.html -->
|
|
95
100
|
<h2>Notes</h2>
|
|
96
|
-
<ul lb-
|
|
97
|
-
<template
|
|
98
|
-
<li lb-
|
|
101
|
+
<ul lb-query="notes">
|
|
102
|
+
<template>
|
|
103
|
+
<li lb-column="text"></li>
|
|
99
104
|
</template>
|
|
100
105
|
</ul>
|
|
101
106
|
```
|
|
102
107
|
|
|
108
|
+
The live row's own `lb-column` counts, which is how an `<li>` or an
|
|
109
|
+
`<option>` shows a column as its text.
|
|
110
|
+
|
|
103
111
|
## Run it
|
|
104
112
|
|
|
105
113
|
```
|
|
@@ -8,37 +8,44 @@ that allows a user to add a note.
|
|
|
8
8
|
```html
|
|
9
9
|
<!-- src/pages/about.page.html -->
|
|
10
10
|
<h2>Notes</h2>
|
|
11
|
-
<section lb-
|
|
12
|
-
<form lb-
|
|
13
|
-
<input lb-
|
|
11
|
+
<section lb-query="notes">
|
|
12
|
+
<form lb-request="lb-row-insert">
|
|
13
|
+
<input lb-column="text" placeholder="Write a note" />
|
|
14
14
|
<button type="submit">Add</button>
|
|
15
15
|
</form>
|
|
16
16
|
|
|
17
17
|
<ul>
|
|
18
|
-
<template
|
|
19
|
-
<li lb-
|
|
18
|
+
<template>
|
|
19
|
+
<li lb-column="text"></li>
|
|
20
20
|
</template>
|
|
21
21
|
</ul>
|
|
22
22
|
</section>
|
|
23
23
|
```
|
|
24
24
|
|
|
25
|
-
`lb-
|
|
26
|
-
|
|
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
|
|
30
|
-
yet.
|
|
25
|
+
`lb-query="notes"` moves from the `<ul>` to a `<section>` that holds both
|
|
26
|
+
the form and the list, so the form is inside the query it adds to.
|
|
31
27
|
|
|
32
|
-
|
|
28
|
+
A `<form>` commits on submit. `lb-request="lb-row-insert"` on it gathers
|
|
29
|
+
the value of every control carrying `lb-column` whose form owner is the
|
|
30
|
+
form, under the column each names, and sends them as `values` for the
|
|
31
|
+
nearest `lb-query` around those controls, `notes`. `lb-row-insert` is one
|
|
32
|
+
of the requests Loadbare provides, so the page declares no handler for it.
|
|
33
|
+
The request carries no key, since there is no row yet.
|
|
33
34
|
|
|
34
|
-
|
|
35
|
+
After a successful insert from a form, the hub resets the form, so the
|
|
36
|
+
input is empty for the next note.
|
|
37
|
+
|
|
38
|
+
## Implementing the insert server-side
|
|
39
|
+
|
|
40
|
+
The requests Loadbare provides run what the page declares under `crud`, in
|
|
41
|
+
the page's requests file:
|
|
35
42
|
|
|
36
43
|
```ts
|
|
37
44
|
// src/pages/about.requests.ts
|
|
38
45
|
import { patch } from "@loadbare/app/server";
|
|
39
46
|
|
|
40
47
|
export const requests = {
|
|
41
|
-
// ...onPageEnter and
|
|
48
|
+
// ...onPageEnter and handlers unchanged...
|
|
42
49
|
crud: {
|
|
43
50
|
notes: {
|
|
44
51
|
rowInsert: {
|
|
@@ -53,8 +60,9 @@ export const requests = {
|
|
|
53
60
|
};
|
|
54
61
|
```
|
|
55
62
|
|
|
56
|
-
`
|
|
57
|
-
query name.
|
|
63
|
+
`lb-row-insert` runs `rowInsert`, declared under `crud` and keyed by
|
|
64
|
+
query name. A query with no `crud` entry permits none of the three
|
|
65
|
+
requests.
|
|
58
66
|
|
|
59
67
|
Notice that we do not use the `refresh` mechanism, as that would return
|
|
60
68
|
the entire query which would be wasteful. We instead return a `patch`
|
|
@@ -6,19 +6,21 @@ 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
|
-
<ul lb-
|
|
10
|
-
<template
|
|
9
|
+
<ul lb-query="notes">
|
|
10
|
+
<template>
|
|
11
11
|
<li>
|
|
12
|
-
<span lb-
|
|
13
|
-
<button lb-
|
|
12
|
+
<span lb-column="text"></span>
|
|
13
|
+
<button lb-request="lb-row-delete">Delete</button>
|
|
14
14
|
</li>
|
|
15
15
|
</template>
|
|
16
16
|
</ul>
|
|
17
17
|
```
|
|
18
18
|
|
|
19
19
|
The `<li>` now has two children, so the text moves onto its own `<span>`.
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
A button commits on click. The button sits in a live row, so the hub
|
|
21
|
+
sends `lb-row-delete` for the live row's query, `notes`, with the live
|
|
22
|
+
row's `lb-key-value` as `key`. The request needs no form, and the page
|
|
23
|
+
declares no handler for it beyond `crud`.
|
|
22
24
|
|
|
23
25
|
## Implementing delete server-side
|
|
24
26
|
|
|
@@ -27,7 +29,7 @@ the `lb-list` and `lb-key-value` already in scope from its ancestors.
|
|
|
27
29
|
import { patch } from "@loadbare/app/server";
|
|
28
30
|
|
|
29
31
|
export const requests = {
|
|
30
|
-
// ...onPageEnter and
|
|
32
|
+
// ...onPageEnter and handlers unchanged...
|
|
31
33
|
crud: {
|
|
32
34
|
notes: {
|
|
33
35
|
// ...rowInsert unchanged...
|
|
@@ -6,22 +6,23 @@ 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
|
-
<ul lb-
|
|
10
|
-
<template
|
|
9
|
+
<ul lb-query="notes">
|
|
10
|
+
<template>
|
|
11
11
|
<li>
|
|
12
|
-
<form lb-
|
|
13
|
-
<input lb-
|
|
12
|
+
<form lb-request="lb-row-update">
|
|
13
|
+
<input lb-column="text" />
|
|
14
14
|
<button type="submit">Save</button>
|
|
15
15
|
</form>
|
|
16
|
-
<button lb-
|
|
16
|
+
<button lb-request="lb-row-delete">Delete</button>
|
|
17
17
|
</li>
|
|
18
18
|
</template>
|
|
19
19
|
</ul>
|
|
20
20
|
```
|
|
21
21
|
|
|
22
|
-
`lb-row-update` gathers
|
|
23
|
-
also
|
|
24
|
-
`lb-row-delete` already reads.
|
|
22
|
+
`lb-row-update` gathers the form's controls the same way `lb-row-insert`
|
|
23
|
+
does, and also takes `key` from the live row it's inside — the same
|
|
24
|
+
`lb-key-value` that `lb-row-delete` already reads. An update resets
|
|
25
|
+
nothing: the updated row lands back on the input.
|
|
25
26
|
|
|
26
27
|
## Implementing update server-side
|
|
27
28
|
|
|
@@ -30,7 +31,7 @@ also reads `lb-key-value` from the row it's inside — the same ancestor
|
|
|
30
31
|
import { patch } from "@loadbare/app/server";
|
|
31
32
|
|
|
32
33
|
export const requests = {
|
|
33
|
-
// ...onPageEnter and
|
|
34
|
+
// ...onPageEnter and handlers unchanged...
|
|
34
35
|
crud: {
|
|
35
36
|
notes: {
|
|
36
37
|
// ...rowInsert, rowDelete unchanged...
|
|
@@ -81,4 +82,4 @@ the other doesn't move. Reload — the edit is still there.
|
|
|
81
82
|
|
|
82
83
|
---
|
|
83
84
|
Prev: [Deleting From a List](./074-deleting-from-a-list.md)
|
|
84
|
-
Next: [
|
|
85
|
+
Next: [Committing a Control](./080-widget-requests.md)
|