@loadbare/app 0.4.0 → 0.5.1
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 +53 -82
- package/dist/build/assemble.d.ts +7 -5
- package/dist/build/assemble.d.ts.map +1 -1
- package/dist/build/assemble.js +29 -9
- package/dist/build/cli.d.ts +20 -12
- package/dist/build/cli.d.ts.map +1 -1
- package/dist/build/cli.js +34 -16
- package/dist/build/elements.d.ts +15 -29
- package/dist/build/elements.d.ts.map +1 -1
- package/dist/build/elements.js +25 -111
- package/dist/build/expand.d.ts +1 -1
- package/dist/build/expand.js +1 -1
- package/dist/build/format.d.ts +6 -3
- package/dist/build/format.d.ts.map +1 -1
- package/dist/build/format.js +6 -3
- package/dist/build/locations.d.ts +14 -37
- package/dist/build/locations.d.ts.map +1 -1
- package/dist/build/locations.js +31 -69
- package/dist/build/origins.d.ts +109 -0
- package/dist/build/origins.d.ts.map +1 -0
- package/dist/build/origins.js +270 -0
- package/dist/core/lb-constants.d.ts +1 -0
- package/dist/core/lb-constants.d.ts.map +1 -1
- package/dist/core/lb-constants.js +15 -8
- package/dist/core/lb-types.d.ts +2 -2
- package/dist/core/lb-types.d.ts.map +1 -1
- package/dist/hub/lb-apply.js +1 -1
- package/dist/hub/lb-hub.d.ts.map +1 -1
- package/dist/hub/lb-hub.js +44 -17
- package/dist/hub/lb-rows.d.ts.map +1 -1
- package/dist/hub/lb-rows.js +3 -3
- package/dist/server/lb-express.d.ts.map +1 -1
- package/dist/server/lb-server.d.ts +5 -4
- package/dist/server/lb-server.d.ts.map +1 -1
- package/dist/tests/assemble.test.js +11 -4
- package/dist/tests/elements.test.js +47 -51
- package/dist/tests/expand.test.d.ts +1 -1
- package/dist/tests/expand.test.js +2 -2
- package/dist/tests/fixtures/elements/collision/imports.d.ts +3 -0
- package/dist/tests/fixtures/elements/collision/imports.d.ts.map +1 -0
- package/dist/tests/fixtures/elements/collision/imports.js +1 -0
- package/dist/tests/fixtures/elements/manifest/imports.d.ts +3 -0
- package/dist/tests/fixtures/elements/manifest/imports.d.ts.map +1 -0
- package/dist/tests/fixtures/elements/manifest/imports.js +1 -0
- package/dist/tests/fixtures/elements/manifest-bad-entry/imports.d.ts +3 -0
- package/dist/tests/fixtures/elements/manifest-bad-entry/imports.d.ts.map +1 -0
- package/dist/tests/fixtures/elements/manifest-bad-entry/imports.js +1 -0
- package/dist/tests/fixtures/elements/{collision/elements.d.ts → manifest-not-array/imports.d.ts} +1 -1
- package/dist/tests/fixtures/elements/manifest-not-array/imports.d.ts.map +1 -0
- package/dist/tests/fixtures/elements/manifest-not-array/imports.js +1 -0
- package/dist/tests/fixtures/elements/pkg/acme-widget.d.ts +2 -0
- package/dist/tests/fixtures/elements/pkg/acme-widget.d.ts.map +1 -0
- package/dist/tests/fixtures/elements/pkg/acme-widget.js +1 -0
- package/dist/tests/lb-express.test.js +1 -1
- package/dist/tests/origins.test.d.ts +10 -0
- package/dist/tests/origins.test.d.ts.map +1 -0
- package/dist/tests/origins.test.js +326 -0
- package/dist/tests/pages.test.js +3 -3
- package/dist/tests/styles.test.js +7 -4
- package/docs/reference/builder.md +128 -0
- package/docs/reference/chrome.md +75 -0
- package/docs/reference/css.md +44 -0
- package/docs/reference/custom-elements.md +327 -0
- package/docs/reference/data-binding.md +240 -0
- package/docs/reference/overview.md +38 -0
- package/docs/reference/page-files.md +175 -0
- package/docs/reference/server.md +123 -0
- package/docs/reference/widgets.md +163 -0
- package/docs/roadmap.md +130 -0
- package/docs/testing.md +228 -0
- package/docs/theory.md +344 -223
- package/docs/tutorials/000-getting-started.md +86 -0
- package/docs/tutorials/010-pages-and-navigation.md +129 -0
- package/docs/tutorials/020-css.md +103 -0
- package/docs/tutorials/030-html-decomposition.md +79 -0
- package/docs/tutorials/040-displaying-data.md +169 -0
- package/docs/tutorials/050-actions.md +77 -0
- package/docs/tutorials/060-custom-element-code.md +73 -0
- package/docs/tutorials/065-conditional-rendering.md +161 -0
- package/docs/tutorials/070-displaying-a-list.md +137 -0
- package/docs/tutorials/072-inserting-into-a-list.md +88 -0
- package/docs/tutorials/074-deleting-from-a-list.md +77 -0
- package/docs/tutorials/076-updating-a-list-item.md +86 -0
- package/docs/tutorials/080-widget-requests.md +124 -0
- package/docs/tutorials/090-using-widget-libraries.md +75 -0
- package/package.json +10 -18
- package/dist/client.js +0 -522
- package/dist/demo-static/src/widgets/app-box.d.ts +0 -15
- package/dist/demo-static/src/widgets/app-box.d.ts.map +0 -1
- package/dist/demo-static/src/widgets/app-box.js +0 -19
- package/dist/tests/fixtures/elements/collision/elements.d.ts.map +0 -1
- package/dist/tests/fixtures/elements/collision/elements.js +0 -3
- package/dist/tests/fixtures/elements/manifest/elements.d.ts +0 -5
- package/dist/tests/fixtures/elements/manifest/elements.d.ts.map +0 -1
- package/dist/tests/fixtures/elements/manifest/elements.js +0 -3
- package/dist/tests/fixtures/elements/manifest-bad-tag/elements.d.ts +0 -5
- package/dist/tests/fixtures/elements/manifest-bad-tag/elements.d.ts.map +0 -1
- package/dist/tests/fixtures/elements/manifest-bad-tag/elements.js +0 -3
- package/dist/tests/fixtures/elements/manifest-bad-value/elements.d.ts +0 -5
- package/dist/tests/fixtures/elements/manifest-bad-value/elements.d.ts.map +0 -1
- package/dist/tests/fixtures/elements/manifest-bad-value/elements.js +0 -3
- package/dist/tests/golden.test.d.ts +0 -19
- package/dist/tests/golden.test.d.ts.map +0 -1
- package/dist/tests/golden.test.js +0 -60
- package/dist/tests/helpers/window.d.ts +0 -43
- package/dist/tests/helpers/window.d.ts.map +0 -1
- package/dist/tests/helpers/window.js +0 -78
- package/dist/tests/lb-input.test.d.ts +0 -9
- package/dist/tests/lb-input.test.d.ts.map +0 -1
- package/dist/tests/lb-input.test.js +0 -78
- package/dist/tests/lb-list.test.d.ts +0 -12
- package/dist/tests/lb-list.test.d.ts.map +0 -1
- package/dist/tests/lb-list.test.js +0 -44
- package/dist/tests/lb-options.test.d.ts +0 -10
- package/dist/tests/lb-options.test.d.ts.map +0 -1
- package/dist/tests/lb-options.test.js +0 -121
- package/dist/tests/lb-picker.test.d.ts +0 -14
- package/dist/tests/lb-picker.test.d.ts.map +0 -1
- package/dist/tests/lb-picker.test.js +0 -59
- package/dist/tests/lb-select.test.d.ts +0 -9
- package/dist/tests/lb-select.test.d.ts.map +0 -1
- package/dist/tests/lb-select.test.js +0 -71
- package/dist/tests/lb-table.test.d.ts +0 -15
- package/dist/tests/lb-table.test.d.ts.map +0 -1
- package/dist/tests/lb-table.test.js +0 -205
- package/dist/widgets/index.d.ts +0 -7
- package/dist/widgets/index.d.ts.map +0 -1
- package/dist/widgets/index.js +0 -6
- package/dist/widgets/lb-input.d.ts +0 -2
- package/dist/widgets/lb-input.d.ts.map +0 -1
- package/dist/widgets/lb-input.js +0 -48
- package/dist/widgets/lb-list.d.ts +0 -2
- package/dist/widgets/lb-list.d.ts.map +0 -1
- package/dist/widgets/lb-list.js +0 -17
- package/dist/widgets/lb-options.d.ts +0 -26
- package/dist/widgets/lb-options.d.ts.map +0 -1
- package/dist/widgets/lb-options.js +0 -72
- package/dist/widgets/lb-picker.d.ts +0 -2
- package/dist/widgets/lb-picker.d.ts.map +0 -1
- package/dist/widgets/lb-picker.js +0 -25
- package/dist/widgets/lb-select.d.ts +0 -2
- package/dist/widgets/lb-select.d.ts.map +0 -1
- package/dist/widgets/lb-select.js +0 -43
- package/dist/widgets/lb-table.d.ts +0 -2
- package/dist/widgets/lb-table.d.ts.map +0 -1
- package/dist/widgets/lb-table.js +0 -113
- package/docs/application-chrome.md +0 -36
- package/docs/building-html-pages.md +0 -130
- package/docs/getting-started.md +0 -120
- package/docs/guide.md +0 -1164
- package/docs/hosting.md +0 -218
- package/docs/latent-risks.md +0 -20
- package/widgets/index.ts +0 -6
- package/widgets/lb-input.html +0 -1
- package/widgets/lb-input.ts +0 -64
- package/widgets/lb-list.html +0 -1
- package/widgets/lb-list.ts +0 -21
- package/widgets/lb-options.html +0 -4
- package/widgets/lb-options.ts +0 -88
- package/widgets/lb-picker.html +0 -7
- package/widgets/lb-picker.ts +0 -27
- package/widgets/lb-select.html +0 -4
- package/widgets/lb-select.ts +0 -55
- package/widgets/lb-table.html +0 -8
- package/widgets/lb-table.ts +0 -126
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# Getting Started
|
|
2
|
+
|
|
3
|
+
This first tutorial sets us up with an empty application shell
|
|
4
|
+
and a simple express server. This will be the foundation for
|
|
5
|
+
creating a full database application.
|
|
6
|
+
|
|
7
|
+
## The chrome file
|
|
8
|
+
|
|
9
|
+
Write a file `src/chrome.html`. The location is convention, in
|
|
10
|
+
fact Loadbare only requires exactly one file named `chrome.html`
|
|
11
|
+
anywhere in the `src/` tree.
|
|
12
|
+
|
|
13
|
+
The chrome is plain old HTML. All Loadbare chrome, pages, and
|
|
14
|
+
widgets are plain old HTML and custom elements.
|
|
15
|
+
|
|
16
|
+
At minimum, the chrome must contain:
|
|
17
|
+
|
|
18
|
+
- a link to the `client.js` script
|
|
19
|
+
- The `<lb-hub>` custom element just inside `<body>`
|
|
20
|
+
- an empty `<main>` inside `<lb-hub>`
|
|
21
|
+
|
|
22
|
+
```html
|
|
23
|
+
<!doctype html>
|
|
24
|
+
<html lang="en">
|
|
25
|
+
<head>
|
|
26
|
+
<meta charset="utf-8" />
|
|
27
|
+
<title>@Loadbare/app Tutorials</title>
|
|
28
|
+
<script src="/client.js" defer></script>
|
|
29
|
+
</head>
|
|
30
|
+
<body>
|
|
31
|
+
<lb-hub>
|
|
32
|
+
<main></main>
|
|
33
|
+
</lb-hub>
|
|
34
|
+
</body>
|
|
35
|
+
</html>
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
The client script `client.js` is generated by the builder. In loadbare,
|
|
39
|
+
`client.js` is the combination of Javascript class definitions for all of
|
|
40
|
+
the custom elements used anywhere in `src/`. In this basic starting point,
|
|
41
|
+
we have `<lb-hub>` as the only custom element, so `client.js` will just
|
|
42
|
+
contain the Javascript class `LbHub`.
|
|
43
|
+
|
|
44
|
+
## The dev script
|
|
45
|
+
|
|
46
|
+
Add a command to package.json that builds the app.
|
|
47
|
+
|
|
48
|
+
```json
|
|
49
|
+
{
|
|
50
|
+
"scripts": {
|
|
51
|
+
"dev": "loadbare-app-build --watch & node server.js"
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
By default the builder puts all output into `dist/`.
|
|
57
|
+
|
|
58
|
+
## The express server
|
|
59
|
+
|
|
60
|
+
Create `server.js` as an express server:
|
|
61
|
+
|
|
62
|
+
```js
|
|
63
|
+
import express from "express";
|
|
64
|
+
import { readFileSync } from "node:fs";
|
|
65
|
+
|
|
66
|
+
const app = express();
|
|
67
|
+
|
|
68
|
+
app.use("/client.js", express.static("dist/client.js"));
|
|
69
|
+
|
|
70
|
+
app.get("/", (_req, res) =>
|
|
71
|
+
res.type("html").send(readFileSync("dist/app.html", "utf-8")),
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
app.listen(8787);
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Run it
|
|
78
|
+
|
|
79
|
+
Try `npm run dev`, and navigate the browser to localhost:8787, you
|
|
80
|
+
should see an empty page, with a console message, and the view source
|
|
81
|
+
option will show exactly the chrome as it was written on disk
|
|
82
|
+
with its empty `<main>`.
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
Next: [Pages and Navigation](./010-pages-and-navigation.md)
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
# Pages and Navigation
|
|
2
|
+
|
|
3
|
+
In our first tutorial, [Getting Started](./000-getting-started.md), we
|
|
4
|
+
create an empty
|
|
5
|
+
`<main>` with no content.
|
|
6
|
+
|
|
7
|
+
Now we will see how to make pages and navigate between them by creating
|
|
8
|
+
two simple static pages.
|
|
9
|
+
|
|
10
|
+
## Two page files
|
|
11
|
+
|
|
12
|
+
Write two files ending `.page.html` for two pages,
|
|
13
|
+
`src/pages/index.page.html` and `src/pages/about.page.html`. Like the
|
|
14
|
+
chrome, a page is found by its filename, not its directory; we use
|
|
15
|
+
the `src/pages` directory by convention and for convenience.
|
|
16
|
+
|
|
17
|
+
The file `index.page.html` is special in Loadbare. Loadbare considers
|
|
18
|
+
this the landing page. If a user navigates to `myapp.example.com` without
|
|
19
|
+
a page specified, Loadbare displays `index.page.html`.
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
```html
|
|
23
|
+
<!-- src/pages/index.page.html -->
|
|
24
|
+
<h1>Home</h1>
|
|
25
|
+
<p>This is the home page.</p>
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
```html
|
|
29
|
+
<!-- src/pages/about.page.html -->
|
|
30
|
+
<h1>About</h1>
|
|
31
|
+
<p>This is the about page.</p>
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Linking between them
|
|
35
|
+
|
|
36
|
+
Add a `<nav>` to the chrome, with one anchor per page.
|
|
37
|
+
|
|
38
|
+
Also add the `<dialog lb-unknown-page>` element, so the hub `<lb-hub>` can
|
|
39
|
+
display something to the user if they type a URL that has no matching page.
|
|
40
|
+
|
|
41
|
+
```html
|
|
42
|
+
<!-- src/chrome.html -->
|
|
43
|
+
<!doctype html>
|
|
44
|
+
<html lang="en">
|
|
45
|
+
<head>
|
|
46
|
+
<meta charset="utf-8" />
|
|
47
|
+
<title>Pages and Navigation</title>
|
|
48
|
+
<script src="/client.js" defer></script>
|
|
49
|
+
</head>
|
|
50
|
+
<body>
|
|
51
|
+
<lb-hub>
|
|
52
|
+
<nav>
|
|
53
|
+
<!-- a bare link to "/" goes to page "index" -->
|
|
54
|
+
<a href="/" lb-nav-link>Home</a>
|
|
55
|
+
<a href="/about" lb-nav-link>About</a>
|
|
56
|
+
</nav>
|
|
57
|
+
<main></main>
|
|
58
|
+
<dialog lb-unknown-page>The page <span lb-cell="page"></span> is not in this app.</dialog>
|
|
59
|
+
</lb-hub>
|
|
60
|
+
</body>
|
|
61
|
+
</html>
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Here we introduce the attribute `lb-nav-link`, which indicates the link
|
|
65
|
+
should navigate within the app. When the user clicks, the hub `<lb-hub>`
|
|
66
|
+
intercepts the event and swaps the page's HTML into the `<main>` element.
|
|
67
|
+
The hub ensures the back button works by calling `history.pushState` on
|
|
68
|
+
navigation, and catching the browser's `popstate` event.
|
|
69
|
+
|
|
70
|
+
An anchor without `lb-nav-link` is left alone and behaves like an ordinary
|
|
71
|
+
link.
|
|
72
|
+
|
|
73
|
+
If a `<dialog lb-unknown-page>` element is present in the HTML, it will be
|
|
74
|
+
displayed to the user when a URL is entered that has no matching page in the app.
|
|
75
|
+
The `lb-cell` attribute will be explained when we get to data binding.
|
|
76
|
+
|
|
77
|
+
## Serving every route
|
|
78
|
+
|
|
79
|
+
The server sends the same document for every route, which is a monolithic
|
|
80
|
+
assembly of chrome and all pages (as `<template>` elements). The hub
|
|
81
|
+
`<lb-hub>` picks which page to show from the URL.
|
|
82
|
+
|
|
83
|
+
With this in mind, we modify the code from the previous example to
|
|
84
|
+
serve the monolithic HTML for any get route.
|
|
85
|
+
|
|
86
|
+
```js
|
|
87
|
+
// server.js
|
|
88
|
+
import express from "express";
|
|
89
|
+
import { readFileSync } from "node:fs";
|
|
90
|
+
|
|
91
|
+
const app = express();
|
|
92
|
+
|
|
93
|
+
app.use("/client.js", express.static("dist/client.js"));
|
|
94
|
+
|
|
95
|
+
// Getting Started had app.get("/", ...); this catches every path instead
|
|
96
|
+
app.get(/.*/, (_req, res) =>
|
|
97
|
+
res.type("html").send(readFileSync("dist/app.html", "utf-8")),
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
app.listen(8787);
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
The static route for `/client.js` has to come before the catch-all, or the
|
|
104
|
+
catch-all answers that request too.
|
|
105
|
+
|
|
106
|
+
## If you look at the console
|
|
107
|
+
|
|
108
|
+
On every navigation, the hub also asks the server for that page's data, at
|
|
109
|
+
`/lb/data?page=<name>`. Neither of these pages is serving any data, and our
|
|
110
|
+
Express server has no route handler for /lb/*, so the catch-all route is currently
|
|
111
|
+
returning the same `app.html` for data requests as it does for the inital
|
|
112
|
+
app load. This is expected in this early stage of the tutorial, we will fix
|
|
113
|
+
it when we get to data binding and handling.
|
|
114
|
+
|
|
115
|
+
## Run it
|
|
116
|
+
|
|
117
|
+
```
|
|
118
|
+
npm run dev
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
Open `http://localhost:8787/`. Click between Home and About — `<main>`
|
|
122
|
+
swaps instantly, with no server request for markup. View source still shows
|
|
123
|
+
exactly what's on disk: the chrome, both page hosts (each inside its own
|
|
124
|
+
`<template>`), and the `<dialog>`. Then try a path neither page defines,
|
|
125
|
+
like `http://localhost:8787/badpagename`, and confirm the dialog announces it.
|
|
126
|
+
|
|
127
|
+
---
|
|
128
|
+
Prev: [Getting Started](./000-getting-started.md)
|
|
129
|
+
Next: [Adding CSS](./020-css.md)
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# Adding CSS
|
|
2
|
+
|
|
3
|
+
Loadbare has a precise precedence opinion for CSS — see [CSS](../reference/css.md)
|
|
4
|
+
for the full rule and why. For now we just want to load some styles onto
|
|
5
|
+
the two pages from [Pages and Navigation](./010-pages-and-navigation.md).
|
|
6
|
+
|
|
7
|
+
The Loadbare build concatenates every `.css` file anywhere under `src`
|
|
8
|
+
into one bundle, sorted alphabetically by filename; a
|
|
9
|
+
directory never affects that order, so a global stylesheet sorts first by
|
|
10
|
+
naming itself something like `00-reset.css`.
|
|
11
|
+
|
|
12
|
+
## Adding CSS to our two static pages
|
|
13
|
+
|
|
14
|
+
We'll add two stylesheets: one global, one specific to a single page.
|
|
15
|
+
|
|
16
|
+
```css
|
|
17
|
+
/* src/00-global.css */
|
|
18
|
+
body {
|
|
19
|
+
font-family: system-ui, sans-serif;
|
|
20
|
+
margin: 2rem;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
nav a {
|
|
24
|
+
margin-right: 1rem;
|
|
25
|
+
}
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
```css
|
|
29
|
+
/* src/pages/about.css */
|
|
30
|
+
.about-note {
|
|
31
|
+
color: #555;
|
|
32
|
+
font-style: italic;
|
|
33
|
+
}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Loadbare doesn't pair a stylesheet with a page.
|
|
37
|
+
The name `about.css` for the page `about.page.html` is only a convention we're
|
|
38
|
+
choosing for easy identification. Loadbare never enforces or
|
|
39
|
+
even notices that the file has the same stem as an HTML page.
|
|
40
|
+
|
|
41
|
+
Use the class in the page:
|
|
42
|
+
|
|
43
|
+
```html
|
|
44
|
+
<!-- src/pages/about.page.html -->
|
|
45
|
+
<h1>About</h1>
|
|
46
|
+
<p class="about-note">This is the about page.</p>
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Linking the bundle
|
|
50
|
+
|
|
51
|
+
Add the stylesheet link to the chrome:
|
|
52
|
+
|
|
53
|
+
```html
|
|
54
|
+
<!-- src/chrome.html -->
|
|
55
|
+
<!doctype html>
|
|
56
|
+
<html lang="en">
|
|
57
|
+
<head>
|
|
58
|
+
<meta charset="utf-8" />
|
|
59
|
+
<title>Pages and Navigation</title>
|
|
60
|
+
<script src="/client.js" defer></script>
|
|
61
|
+
<!-- Add a standard stylesheet link -->
|
|
62
|
+
<link rel="stylesheet" href="/app.css" />
|
|
63
|
+
</head>
|
|
64
|
+
<body>
|
|
65
|
+
<lb-hub>
|
|
66
|
+
<nav>
|
|
67
|
+
<a href="/" lb-nav-link>Home</a>
|
|
68
|
+
<a href="/about" lb-nav-link>About</a>
|
|
69
|
+
</nav>
|
|
70
|
+
<main></main>
|
|
71
|
+
<dialog lb-unknown-page>The page <span lb-cell="page"></span> is not in this app.</dialog>
|
|
72
|
+
</lb-hub>
|
|
73
|
+
</body>
|
|
74
|
+
</html>
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
And a static route for it in the server, the same way `/client.js` already
|
|
78
|
+
has one:
|
|
79
|
+
|
|
80
|
+
```js
|
|
81
|
+
// server.js
|
|
82
|
+
app.use("/client.js", express.static("dist/client.js"));
|
|
83
|
+
app.use("/app.css", express.static("dist/app.css"));
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Like `/client.js`, this has to come before the catch-all route, or the
|
|
87
|
+
catch-all answers that request too.
|
|
88
|
+
|
|
89
|
+
## Run it
|
|
90
|
+
|
|
91
|
+
```
|
|
92
|
+
npm run dev
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Open `http://localhost:8787/`. Both pages should now use the system font
|
|
96
|
+
and the wider margin from `00-global.css`; the About page's note should be
|
|
97
|
+
grey and italic. View `dist/app.css` — it's `00-global.css`'s contents
|
|
98
|
+
followed by `about.css`'s, in exactly that order, because `00-global.css`
|
|
99
|
+
sorts first by name.
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
Prev: [Pages and Navigation](./010-pages-and-navigation.md)
|
|
103
|
+
Next: [HTML Decomposition](./030-html-decomposition.md)
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# HTML Decomposition
|
|
2
|
+
|
|
3
|
+
So far we have been building a monolithic chrome, but this will soon
|
|
4
|
+
become unwieldy. We need to break it up so we can work with subtrees
|
|
5
|
+
of the HTML in isolation.
|
|
6
|
+
|
|
7
|
+
Loadbare interprets custom elements as server-side HTML
|
|
8
|
+
includes. When the HTML contains `<my-custom-widget>`, and there
|
|
9
|
+
is a file `my-custom-widget.html` anywhere in `src/`, the
|
|
10
|
+
Loadbare builder inserts the contents of `my-custom-widget.html` as the
|
|
11
|
+
children of `<my-custom-widget>`.
|
|
12
|
+
|
|
13
|
+
This HTML include feature has options for attributes and slots, but
|
|
14
|
+
for now we will just do a static example.
|
|
15
|
+
|
|
16
|
+
## Static decomposition of the navigation bar
|
|
17
|
+
|
|
18
|
+
We begin by replacing the literal nav bar with an empty custom
|
|
19
|
+
element, not yet defined:
|
|
20
|
+
|
|
21
|
+
```html
|
|
22
|
+
<!-- src/chrome.html -->
|
|
23
|
+
<!doctype html>
|
|
24
|
+
<html lang="en">
|
|
25
|
+
<head>
|
|
26
|
+
<meta charset="utf-8" />
|
|
27
|
+
<title>Pages and Navigation</title>
|
|
28
|
+
<script src="/client.js" defer></script>
|
|
29
|
+
<link rel="stylesheet" href="/app.css" />
|
|
30
|
+
</head>
|
|
31
|
+
<body>
|
|
32
|
+
<lb-hub>
|
|
33
|
+
<!-- replace this from the previous tutorial...
|
|
34
|
+
<nav>
|
|
35
|
+
<a href="/" lb-nav-link>Home</a>
|
|
36
|
+
<a href="/about" lb-nav-link>About</a>
|
|
37
|
+
</nav>
|
|
38
|
+
-->
|
|
39
|
+
<!-- ...with this empty custom element: -->
|
|
40
|
+
<app-nav></app-nav>
|
|
41
|
+
<main></main>
|
|
42
|
+
<dialog lb-unknown-page>The page <span lb-cell="page"></span> is not in this app.</dialog>
|
|
43
|
+
</lb-hub>
|
|
44
|
+
</body>
|
|
45
|
+
</html>
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Now we specify the HTML that should be inserted as children
|
|
49
|
+
of `<app-nav>` by writing `src/app-nav.html`.
|
|
50
|
+
|
|
51
|
+
```html
|
|
52
|
+
<nav>
|
|
53
|
+
<a href="/" lb-nav-link>Home</a>
|
|
54
|
+
<a href="/about" lb-nav-link>About</a>
|
|
55
|
+
</nav>
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
During the build, Loadbare locates `app-nav.html` and inserts its contents
|
|
59
|
+
into the `<app-nav>` custom element, so the actual result will look like this:
|
|
60
|
+
|
|
61
|
+
```html
|
|
62
|
+
<app-nav>
|
|
63
|
+
<nav>
|
|
64
|
+
<a href="/" lb-nav-link>Home</a>
|
|
65
|
+
<a href="/about" lb-nav-link>About</a>
|
|
66
|
+
</nav>
|
|
67
|
+
</app-nav>
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Dynamic Composition
|
|
71
|
+
|
|
72
|
+
There is much more to HTML decomposition. Custom elements can have
|
|
73
|
+
slots and attributes that are resolved at build time. Read all about
|
|
74
|
+
them in [Custom Elements](../reference/custom-elements.md#html).
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
Prev: [CSS](./020-css.md)
|
|
78
|
+
Next: [Displaying Data](040-displaying-data.md)
|
|
79
|
+
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
# Displaying Data
|
|
2
|
+
|
|
3
|
+
We now have the basics of a Loadbare app: the chrome, the express server,
|
|
4
|
+
some CSS, and two pages. But they are static, and the entire point of
|
|
5
|
+
Loadbare is to build data-centric apps. So now we add the first half of
|
|
6
|
+
data-centric apps, displaying data.
|
|
7
|
+
|
|
8
|
+
## Data Binding
|
|
9
|
+
|
|
10
|
+
Loadbare can understand and process scalar cells, entities, and
|
|
11
|
+
collections. We begin with a simple scalar cell.
|
|
12
|
+
|
|
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`.
|
|
17
|
+
|
|
18
|
+
```html
|
|
19
|
+
<!-- src/pages/about.page.html -->
|
|
20
|
+
<h1>About</h1>
|
|
21
|
+
<p class="about-note">This is the about page.</p>
|
|
22
|
+
|
|
23
|
+
<div lb-query="visits">
|
|
24
|
+
<p>This page has been visited <span lb-cell="count"></span> times.</p>
|
|
25
|
+
</div>
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Write the Query
|
|
29
|
+
|
|
30
|
+
Put the query into `about.queries.ts`, which uses the same page stem,
|
|
31
|
+
`about`, as `about.page.html`.
|
|
32
|
+
|
|
33
|
+
```ts
|
|
34
|
+
// src/pages/about.queries.ts
|
|
35
|
+
export const queries = {
|
|
36
|
+
visits: async (ctx) => ({ count: String(await ctx.db.visitCount()) }),
|
|
37
|
+
};
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
We have not yet implemented `ctx.db`, we will do that shortly.
|
|
41
|
+
|
|
42
|
+
## Write the Before Get Hook
|
|
43
|
+
|
|
44
|
+
When the user navigates to a page, the loadbare hub `<lb-hub>` swaps
|
|
45
|
+
the contents of `<main>` and sends a request to the server for the
|
|
46
|
+
query results defined for that page.
|
|
47
|
+
|
|
48
|
+
For a visit counter, we need to write a `beforeGet` hook to fire first
|
|
49
|
+
and increment the counter:
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
```ts
|
|
53
|
+
// src/pages/about.hooks.ts
|
|
54
|
+
export const hooks = {
|
|
55
|
+
beforeGet: (ctx) => ctx.db.recordVisit(),
|
|
56
|
+
};
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Once again we are referring to a method on `ctx.db`, which we must now
|
|
60
|
+
implement.
|
|
61
|
+
|
|
62
|
+
## Implement a simple database
|
|
63
|
+
|
|
64
|
+
In a real application we would be connecting to some database server,
|
|
65
|
+
but we want to keep the tutorial simple and step-by-step, so we will mock
|
|
66
|
+
up a file-based database server that only does what the tutorial needs.
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
```ts
|
|
70
|
+
// src/database.ts
|
|
71
|
+
import { readFile, writeFile } from "node:fs/promises";
|
|
72
|
+
|
|
73
|
+
const DATA_FILE = new URL("./.lb-data.json", import.meta.url);
|
|
74
|
+
|
|
75
|
+
async function read() {
|
|
76
|
+
try {
|
|
77
|
+
return JSON.parse(await readFile(DATA_FILE, "utf-8"));
|
|
78
|
+
} catch {
|
|
79
|
+
return { visitCount: 0 };
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function openDb() {
|
|
84
|
+
return {
|
|
85
|
+
async visitCount() {
|
|
86
|
+
return (await read()).visitCount;
|
|
87
|
+
},
|
|
88
|
+
async recordVisit() {
|
|
89
|
+
const current = await read();
|
|
90
|
+
await writeFile(
|
|
91
|
+
DATA_FILE,
|
|
92
|
+
JSON.stringify({ visitCount: current.visitCount + 1 }),
|
|
93
|
+
"utf-8",
|
|
94
|
+
);
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Nothing here is Loadbare's concern — it never looks inside `ctx`. A real
|
|
101
|
+
application would put a real database behind `openDb`, and would open it for
|
|
102
|
+
the authenticated caller instead of the same file for everyone.
|
|
103
|
+
|
|
104
|
+
## Wiring the server
|
|
105
|
+
|
|
106
|
+
The Loadbare builder compiles a list of all hooks and queries.
|
|
107
|
+
Add the four
|
|
108
|
+
lines below, and our server will correctly route all data channel
|
|
109
|
+
requests to the correct page code.
|
|
110
|
+
|
|
111
|
+
```js
|
|
112
|
+
// server.js
|
|
113
|
+
import express from "express";
|
|
114
|
+
import { readFileSync } from "node:fs";
|
|
115
|
+
import { hubRoutes } from "@loadbare/app/express";
|
|
116
|
+
import { hub } from "./dist/pages";
|
|
117
|
+
import { openDb } from "./src/database";
|
|
118
|
+
|
|
119
|
+
const app = express();
|
|
120
|
+
|
|
121
|
+
app.use("/client.js", express.static("dist/client.js"));
|
|
122
|
+
app.use("/app.css", express.static("dist/app.css"));
|
|
123
|
+
|
|
124
|
+
// New: answers GET /lb/data?page=<name> and POST /lb?page=<name>.
|
|
125
|
+
// Must come before the catch-all, or the catch-all answers these too.
|
|
126
|
+
function contextFor(_req) {
|
|
127
|
+
return { db: openDb() };
|
|
128
|
+
}
|
|
129
|
+
app.use(hubRoutes(hub, contextFor));
|
|
130
|
+
|
|
131
|
+
app.get(/.*/, (_req, res) =>
|
|
132
|
+
res.type("html").send(readFileSync("dist/app.html", "utf-8")),
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
app.listen(8787);
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
`dist/pages.ts` and our own `.ts` files mean `server.js` is no longer
|
|
139
|
+
runnable by plain `node`. Swap the dev script to a TypeScript-capable
|
|
140
|
+
runner:
|
|
141
|
+
|
|
142
|
+
```json
|
|
143
|
+
{
|
|
144
|
+
"scripts": {
|
|
145
|
+
"dev": "loadbare-app-build --watch & tsx server.js"
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
## Run it
|
|
151
|
+
|
|
152
|
+
```
|
|
153
|
+
npm run dev
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
Open `http://localhost:8787/` and navigate to About a few times, using the
|
|
157
|
+
nav links so `<main>` swaps in place. The count rises by one on every visit
|
|
158
|
+
to About.
|
|
159
|
+
|
|
160
|
+
View source on either page: the host HTML is exactly what's on disk. Only
|
|
161
|
+
the `<span>`'s text changes, never the markup around it.
|
|
162
|
+
|
|
163
|
+
This tutorial only used `lb-query` 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).
|
|
166
|
+
|
|
167
|
+
---
|
|
168
|
+
Prev: [HTML Decomposition](./030-html-decomposition.md)
|
|
169
|
+
Next: [Actions](./050-actions.md)
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# Actions
|
|
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.
|
|
7
|
+
|
|
8
|
+
## An action in the client
|
|
9
|
+
|
|
10
|
+
Modify the about page to include a button that requests the server
|
|
11
|
+
to reset the visit count.
|
|
12
|
+
|
|
13
|
+
```html
|
|
14
|
+
<!-- src/pages/about.page.html -->
|
|
15
|
+
<h1>About</h1>
|
|
16
|
+
<p class="about-note">This is the about page.</p>
|
|
17
|
+
|
|
18
|
+
<div lb-query="visits">
|
|
19
|
+
<p>This page has been visited <span lb-cell="count"></span> times.</p>
|
|
20
|
+
<button lb-action="resetVisits">Reset count</button>
|
|
21
|
+
</div>
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Implement the action on the server
|
|
25
|
+
|
|
26
|
+
Actions go into the page's hooks file:
|
|
27
|
+
|
|
28
|
+
```ts
|
|
29
|
+
// src/pages/about.hooks.ts
|
|
30
|
+
export const hooks = {
|
|
31
|
+
beforeGet: (ctx) => ctx.db.recordVisit(),
|
|
32
|
+
actions: {
|
|
33
|
+
resetVisits: {
|
|
34
|
+
run: (ctx) => ctx.db.resetVisits(),
|
|
35
|
+
refresh: ["visits"],
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
An action is a `run` paired with a `refresh` list. After the `run` code
|
|
42
|
+
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.
|
|
45
|
+
|
|
46
|
+
## Extending the database
|
|
47
|
+
|
|
48
|
+
We have to extend our bespoke database with `resetVisits`.
|
|
49
|
+
|
|
50
|
+
```ts
|
|
51
|
+
// src/database.ts
|
|
52
|
+
export function openDb() {
|
|
53
|
+
return {
|
|
54
|
+
// ...visitCount() and recordVisit() unchanged...
|
|
55
|
+
async resetVisits() {
|
|
56
|
+
await write({ visitCount: 0 });
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
`write` already merges over whatever's stored, so this only ever touches
|
|
63
|
+
`visitCount`.
|
|
64
|
+
|
|
65
|
+
## Run it
|
|
66
|
+
|
|
67
|
+
```
|
|
68
|
+
npm run dev
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Open the About page a few times to raise the count, then click "Reset
|
|
72
|
+
count" — it drops to zero in place, no reload. Reload anyway: it stays at
|
|
73
|
+
zero, because the reset happened on the server, not just in the browser.
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
Prev: [Displaying Data](./040-displaying-data.md)
|
|
77
|
+
Next: [Custom Element Code](./060-custom-element-code.md)
|