@loadbare/app 0.8.2 → 0.10.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.
Files changed (80) hide show
  1. package/README.md +3 -3
  2. package/dist/build/assemble.d.ts.map +1 -1
  3. package/dist/build/assemble.js +74 -66
  4. package/dist/build/assemble.js.map +1 -1
  5. package/dist/build/expand.d.ts.map +1 -1
  6. package/dist/build/expand.js +20 -19
  7. package/dist/build/expand.js.map +1 -1
  8. package/dist/build/locations.d.ts +2 -3
  9. package/dist/build/locations.d.ts.map +1 -1
  10. package/dist/build/locations.js +2 -3
  11. package/dist/build/locations.js.map +1 -1
  12. package/dist/build/pages.d.ts +3 -4
  13. package/dist/build/pages.d.ts.map +1 -1
  14. package/dist/build/pages.js +3 -4
  15. package/dist/build/pages.js.map +1 -1
  16. package/dist/core/lb-constants.d.ts +25 -23
  17. package/dist/core/lb-constants.d.ts.map +1 -1
  18. package/dist/core/lb-constants.js +95 -158
  19. package/dist/core/lb-constants.js.map +1 -1
  20. package/dist/core/lb-types.d.ts +73 -75
  21. package/dist/core/lb-types.d.ts.map +1 -1
  22. package/dist/core/lb-types.js +58 -5
  23. package/dist/core/lb-types.js.map +1 -1
  24. package/dist/hub/lb-apply.d.ts +47 -37
  25. package/dist/hub/lb-apply.d.ts.map +1 -1
  26. package/dist/hub/lb-apply.js +174 -193
  27. package/dist/hub/lb-apply.js.map +1 -1
  28. package/dist/hub/lb-hub.browser.d.ts +1 -1
  29. package/dist/hub/lb-hub.browser.d.ts.map +1 -1
  30. package/dist/hub/lb-hub.browser.js +419 -415
  31. package/dist/hub/lb-hub.browser.js.map +1 -1
  32. package/dist/server/lb-express.d.ts +20 -13
  33. package/dist/server/lb-express.d.ts.map +1 -1
  34. package/dist/server/lb-express.js +50 -52
  35. package/dist/server/lb-express.js.map +1 -1
  36. package/dist/server/lb-server.d.ts +81 -116
  37. package/dist/server/lb-server.d.ts.map +1 -1
  38. package/dist/server/lb-server.js +151 -48
  39. package/dist/server/lb-server.js.map +1 -1
  40. package/docs/TECHREF-1.0.md +893 -558
  41. package/docs/comparison.md +222 -185
  42. package/docs/prior-art.md +15 -14
  43. package/docs/reference/builder.md +9 -3
  44. package/docs/reference/chrome.md +107 -56
  45. package/docs/reference/custom-elements.md +199 -173
  46. package/docs/reference/data-binding.md +375 -370
  47. package/docs/reference/overview.md +12 -10
  48. package/docs/reference/page-files.md +161 -86
  49. package/docs/reference/server.md +13 -7
  50. package/docs/reference/widgets.md +104 -110
  51. package/docs/roadmap.md +36 -31
  52. package/docs/terms-of-art.md +57 -0
  53. package/docs/testing.md +97 -68
  54. package/docs/theory.md +92 -58
  55. package/docs/tutorials/010-pages-and-navigation.md +20 -12
  56. package/docs/tutorials/020-css.md +6 -3
  57. package/docs/tutorials/030-html-decomposition.md +9 -7
  58. package/docs/tutorials/040-displaying-data.md +30 -13
  59. package/docs/tutorials/{050-actions.md → 050-requests.md} +25 -15
  60. package/docs/tutorials/060-custom-element-code.md +17 -16
  61. package/docs/tutorials/065-conditional-rendering.md +34 -23
  62. package/docs/tutorials/070-displaying-a-list.md +29 -21
  63. package/docs/tutorials/072-inserting-into-a-list.md +24 -16
  64. package/docs/tutorials/074-deleting-from-a-list.md +9 -7
  65. package/docs/tutorials/076-updating-a-list-item.md +11 -10
  66. package/docs/tutorials/080-widget-requests.md +71 -43
  67. package/docs/tutorials/090-using-widget-libraries.md +22 -22
  68. package/package.json +1 -1
  69. package/skills/loadbare-app/SKILL.md +178 -111
  70. package/skills/loadbare-app/references/TECHREF-1.0.md +893 -558
  71. package/skills/loadbare-app/references/builder.md +9 -3
  72. package/skills/loadbare-app/references/chrome.md +107 -56
  73. package/skills/loadbare-app/references/custom-elements.md +199 -173
  74. package/skills/loadbare-app/references/data-binding.md +375 -370
  75. package/skills/loadbare-app/references/overview.md +12 -10
  76. package/skills/loadbare-app/references/page-files.md +161 -86
  77. package/skills/loadbare-app/references/server.md +13 -7
  78. package/skills/loadbare-app/references/widgets.md +104 -110
  79. package/docs/analysis-accidental-complexity.md +0 -149
  80. 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 can understand and process scalar cells, entities, and
11
- collections. We begin with a simple scalar cell.
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 is scoped to a named query that
14
- is implemented on the server. In the code below we scope the div
15
- to query `visits`, then specify that the span will display
16
- the cell `count`.
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-row="visits">
24
- <p>This page has been visited <span lb-cell="count"></span> times.</p>
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) => ({ count: String(await ctx.db.visitCount()) }),
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-row` and `lb-cell` to display one value.
164
- The full attribute vocabulary — actions, CRUD operations, lists, and
165
- navigation — is in [Data Binding](../reference/data-binding.md).
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: [Actions](./050-actions.md)
186
+ Next: [Requests](./050-requests.md)
@@ -1,35 +1,41 @@
1
- # Actions
1
+ # Requests
2
2
 
3
- So far we have used data binding to display a single cell of a named
4
- query. Before we go on to CRUD operations, we will show an "action",
5
- which is a named routine on the server that cna be invoked from the
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
- ## An action in the client
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-row="visits">
19
- <p>This page has been visited <span lb-cell="count"></span> times.</p>
20
- <button lb-action="resetVisits">Reset count</button>
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
- ## Implement the action on the server
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
- Actions go into the page's requests file:
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
- actions: {
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
- An action is a `run` paired with a `refresh` list. After the `run` code
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>` refreshes
44
- the bound elements.
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 scalar data binding, and server-implemented
4
- actions that can be requested from the server.
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 CRUD operations, but those will require
7
- custom elements for list operations.
8
- To set the stage for custom elements and list operations, we will
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-row="visits">
20
- <p>This page has been <visit-count lb-cell="count"></visit-count>.</p>
21
- <button lb-action="resetVisits">Reset count</button>
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 cell value `count` the same as we did for the span,
27
- using `lb-cell="count"`.
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 { ATTR_VALUE } from "@loadbare/app/constants";
47
+ import { ATTR_COLUMN_VALUE } from "@loadbare/app/constants";
47
48
 
48
49
  class VisitCount extends HTMLElement {
49
- static observedAttributes = [ATTR_VALUE];
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 standard attribute
66
- used for a cell's value, defined in constant `ATTR_VALUE`. When the
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: [Actions](./050-actions.md)
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
- <lb-wizard lb-row="signup" lb-cell="step">
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-action="wizardBack" disabled>Back</button>
35
- <button data-nav="next" lb-action="wizardNext">Next</button>
36
- </lb-wizard>
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-cell="step"` on `<lb-wizard>` works exactly the way it did on
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 has a hyphen, the value lands on the `lb-value` attribute
42
- instead of `textContent`, and the class decides what to do with it. Back
43
- and Next are plain buttons carrying `lb-action`, the same mechanism as the
44
- Reset button in [Actions](./050-actions.md) — the wizard owns no
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/lb-wizard.browser.ts
51
- import { ATTR_VALUE } from "@loadbare/app/constants";
56
+ // src/signup-wizard.browser.ts
57
+ import { ATTR_COLUMN_VALUE } from "@loadbare/app/constants";
52
58
 
53
- class LbWizard extends HTMLElement {
54
- static observedAttributes = [ATTR_VALUE];
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("lb-wizard", LbWizard);
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 `LbWizard`
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 and notes unchanged...
94
- signup: async (ctx) => ({ step: await ctx.db.signupStep() }),
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, resetVisits, and crud unchanged...
102
- actions: {
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/cell path. The browser never shows a step the server
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(), notes, etc. unchanged...
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
- `LbWizard` with nothing to tell it otherwise, and it would have landed
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 widget is involved: the
5
- hub reconciles a list itself.
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 { list, row } from "@loadbare/app/server";
11
+ import { row, rows } from "@loadbare/app/server";
12
12
 
13
13
  export const queries = {
14
- visits: row(async (ctx) => ({ count: String(await ctx.db.visitCount()) })),
15
- notes: list((ctx) => ctx.db.notes()),
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 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.
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 list display with a template
73
+ ## A row template
70
74
 
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.
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 identifying the data-binding scope of elements.
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
- 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.
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-list="notes">
97
- <template lb-key="id">
98
- <li lb-cell="text"></li>
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-list="notes">
12
- <form lb-action="lb-row-insert">
13
- <input lb-cell="text" placeholder="Write a note" />
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 lb-key="id">
19
- <li lb-cell="text"></li>
18
+ <template>
19
+ <li lb-column="text"></li>
20
20
  </template>
21
21
  </ul>
22
22
  </section>
23
23
  ```
24
24
 
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
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
- ## Implementing CRUD server-side
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
- Our CRUD operations go into the page's requests file:
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 actions unchanged...
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
- `rowInsert` is a CRUD operation, declared under `crud` and keyed by
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-list="notes">
10
- <template lb-key="id">
9
+ <ul lb-query="notes">
10
+ <template>
11
11
  <li>
12
- <span lb-cell="text"></span>
13
- <button lb-action="lb-row-delete">Delete</button>
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
- `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.
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 actions unchanged...
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-list="notes">
10
- <template lb-key="id">
9
+ <ul lb-query="notes">
10
+ <template>
11
11
  <li>
12
- <form lb-action="lb-row-update">
13
- <input lb-cell="text" />
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-action="lb-row-delete">Delete</button>
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 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.
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 actions unchanged...
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: [Widget Requests](./080-widget-requests.md)
85
+ Next: [Committing a Control](./080-widget-requests.md)