@gramlot/gramlot-examples 0.2.10 → 0.2.11
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 +4 -3
- package/package.json +2 -2
- package/src/gramlot_examples/catalog.json +21 -0
- package/src/gramlot_examples/pages/README.md +5 -4
- package/src/gramlot_examples/pages/controllers/12_remote.js +33 -0
- package/src/gramlot_examples/pages/controllers/12_remote.md +13 -0
- package/src/gramlot_examples/pages/controllers/13_cached_remote.js +44 -0
- package/src/gramlot_examples/pages/controllers/13_cached_remote.md +12 -0
- package/src/gramlot_examples/pages/controllers/14_data_remote.js +31 -0
- package/src/gramlot_examples/pages/controllers/14_data_remote.md +10 -0
- package/src/gramlot_examples/pages/controllers/README.md +3 -3
package/README.md
CHANGED
|
@@ -12,7 +12,7 @@ npm install @gramlot/gramlot-examples
|
|
|
12
12
|
[](https://pypi.org/project/gramlot-examples/)
|
|
13
13
|
[](https://www.npmjs.com/package/@gramlot/gramlot-examples)
|
|
14
14
|
|
|
15
|
-
Current release: 0.2.
|
|
15
|
+
Current release: 0.2.11.
|
|
16
16
|
|
|
17
17
|
The repositories of the family are described in
|
|
18
18
|
[The Gramlot family](https://github.com/gramlot-org/gramlot/blob/main/docs/public/055-family.md)
|
|
@@ -26,8 +26,9 @@ same-name README:
|
|
|
26
26
|
|
|
27
27
|
- `html_svg/`: thirteen HTML and SVG pages, without binding (`e01`–`e13`);
|
|
28
28
|
- `binding/`: eleven pages whose DOM follows the Data (`b01`–`b11`);
|
|
29
|
-
- `controllers/`:
|
|
30
|
-
end-to-end story
|
|
29
|
+
- `controllers/`: fourteen pages with formulas, controllers, named logic, events, the
|
|
30
|
+
end-to-end story, `dataRpc` calls to the endpoints of the page, `remote` fragments and
|
|
31
|
+
`dataRemote` values (`c01`–`c14`).
|
|
31
32
|
|
|
32
33
|
Each `NN_name.js` exports `Page` and `Logic`; the Python page `NN_name.py` takes its
|
|
33
34
|
`Logic` from the module beside it.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gramlot/gramlot-examples",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.11",
|
|
4
4
|
"description": "Gramlot example pages, the gallery page and its catalogue.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
"prepack": "node scripts/build-browser.mjs"
|
|
15
15
|
},
|
|
16
16
|
"dependencies": {
|
|
17
|
-
"@gramlot/gramlot": ">=0.2.
|
|
17
|
+
"@gramlot/gramlot": ">=0.2.16"
|
|
18
18
|
},
|
|
19
19
|
"devDependencies": {
|
|
20
20
|
"@genrojs/bag": ">=0.10.1",
|
|
@@ -260,6 +260,27 @@
|
|
|
260
260
|
"folder": "11_call_on_click",
|
|
261
261
|
"description": "A dataRpc nested in a button, with _onError for a failing and a protected endpoint.",
|
|
262
262
|
"explanation": "Each click calls the endpoint. An exception answers application_error; an endpoint with auth answers not_authenticated on a server that knows no identity. _onError receives the error."
|
|
263
|
+
},
|
|
264
|
+
{
|
|
265
|
+
"key": "c12",
|
|
266
|
+
"title": "Remote",
|
|
267
|
+
"folder": "12_remote",
|
|
268
|
+
"description": "remote makes the content of an element a fragment built by the server.",
|
|
269
|
+
"explanation": "The section declares remote('details', record_id='^.record_id'). The page builds the fragment with @source in Python and Page.registerSource in JavaScript; each change of the record requests it again."
|
|
270
|
+
},
|
|
271
|
+
{
|
|
272
|
+
"key": "c13",
|
|
273
|
+
"title": "Cached remote",
|
|
274
|
+
"folder": "13_cached_remote",
|
|
275
|
+
"description": "remote with _cachedRemote and _onRemote, under a variable datapath.",
|
|
276
|
+
"explanation": "The rules fragment depends on the category of the product. _cachedRemote makes no request when the category is the same; _onRemote is inline code that counts the mounted fragments and writes the status."
|
|
277
|
+
},
|
|
278
|
+
{
|
|
279
|
+
"key": "c14",
|
|
280
|
+
"title": "Data remote",
|
|
281
|
+
"folder": "14_data_remote",
|
|
282
|
+
"description": "dataRemote computes a Data value with an endpoint when the value is read.",
|
|
283
|
+
"explanation": "The menu path holds a resolver of the endpoint load_menu with cacheTime 5. The ^.menu paragraph reads it: the answer arrives with the Data event. A change of the table loads the menu again."
|
|
263
284
|
}
|
|
264
285
|
]
|
|
265
286
|
}
|
|
@@ -7,16 +7,17 @@ JavaScript, in three families:
|
|
|
7
7
|
- [`binding/`](binding/): eleven pages whose DOM follows the Data through pointers,
|
|
8
8
|
editing, booleans, style, SVG and freeze. The last three take the content of
|
|
9
9
|
`html_svg` examples 06, 08 and 12 and add the binding.
|
|
10
|
-
- [`controllers/`](controllers/):
|
|
10
|
+
- [`controllers/`](controllers/): fourteen pages with formulas, controllers, named
|
|
11
11
|
logic in the `Logic` class of the page module, inline expressions, node methods, buttons,
|
|
12
|
-
events, the end-to-end story
|
|
12
|
+
events, the end-to-end story, `dataRpc` calls to the endpoints of the page, `remote`
|
|
13
|
+
fragments and `dataRemote` values.
|
|
13
14
|
|
|
14
15
|
Each example is a file page: `NN_name.py`, its JavaScript equivalent `NN_name.js`,
|
|
15
16
|
its README `NN_name.md`, and where needed a same-name stylesheet `NN_name.css`.
|
|
16
17
|
`NN_name.js` exports `Page` and `Logic`: the JavaScript page uses both, the Python
|
|
17
18
|
page takes `Logic` from it. The pages use only what the core `GramlotFileServer` serves:
|
|
18
|
-
`Page.css`, the same-name stylesheet, the `Logic` of the page module
|
|
19
|
-
of the page. The gallery
|
|
19
|
+
`Page.css`, the same-name stylesheet, the `Logic` of the page module, the endpoints
|
|
20
|
+
and the Source fragments of the page. The gallery
|
|
20
21
|
adds its own files: the frame script, a staged `<key>_aux.js` per example and its
|
|
21
22
|
assets ([GE-010 §025](https://github.com/gramlot-org/gramlot-examples/blob/main/docs/010-gallery.md#ge-010-025)).
|
|
22
23
|
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import {Page as BasePage} from '@gramlot/gramlot/page';
|
|
2
|
+
|
|
3
|
+
const RECORDS = {lamp: ['Lamp', 3], desk: ['Desk', 0], chair: ['Chair', 12]};
|
|
4
|
+
|
|
5
|
+
export class Page extends BasePage {
|
|
6
|
+
static title = 'Remote';
|
|
7
|
+
static css = ['/themes/gramlot-base/theme.css'];
|
|
8
|
+
|
|
9
|
+
main(root) {
|
|
10
|
+
const pane = root.div({class: 'example-page stack', datapath: 'catalog'});
|
|
11
|
+
pane.h1('Remote');
|
|
12
|
+
|
|
13
|
+
const form = pane.form({class: 'card stack'});
|
|
14
|
+
form.html_label('Record', {for: 'record'});
|
|
15
|
+
const choice = form.select({value: '^.record_id', id: 'record'});
|
|
16
|
+
for (const [key, [name]] of Object.entries(RECORDS)) choice.option(name, {value: key});
|
|
17
|
+
|
|
18
|
+
const details = pane.section('Waiting for the server', {class: 'card stack', id: 'details'});
|
|
19
|
+
details.remote('details', {record_id: '^.record_id'});
|
|
20
|
+
|
|
21
|
+
pane.dataSetter({destination_path: '.record_id', value: 'lamp'});
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
details(root, {record_id}) {
|
|
25
|
+
const [name, stock] = RECORDS[record_id];
|
|
26
|
+
root.h2(name, {id: 'name'});
|
|
27
|
+
root.p(`${stock} in stock`, {id: 'stock'});
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
Page.registerSource('details');
|
|
31
|
+
|
|
32
|
+
/** The page logic: this page names no methods. */
|
|
33
|
+
export class Logic {}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# 12 · Remote
|
|
2
|
+
|
|
3
|
+
`remote` makes the content of an element a Source fragment that a method of the page builds on the server. The node method `remote(name, **params)` writes it on the element.
|
|
4
|
+
|
|
5
|
+
- **Declare the fragment.** Python marks the method with `@source`; JavaScript calls `Page.registerSource('details')` after the class. The method receives the root of the fragment and the parameters, and populates the root.
|
|
6
|
+
- **Parameters.** `record_id='^.record_id'` is a parameter of the call. A `^` parameter requests the fragment again at every change; a `=` parameter is only read.
|
|
7
|
+
- **When.** The fragment is requested when the element starts. The authored content, `Waiting for the server`, stays until the first fragment arrives; each fragment replaces the content of the element.
|
|
8
|
+
- **`**kwargs`.** A Python fragment method declares `**kwargs`, as a Python endpoint does.
|
|
9
|
+
- **Latest wins.** A new request supersedes the one in flight; removing the element cancels its request.
|
|
10
|
+
|
|
11
|
+
The request travels as one envelope on `POST /gramlot/rpc`; the page writes no `fetch`.
|
|
12
|
+
|
|
13
|
+
**Try it:** Choose another record: the section shows the name and the stock that the server returns for it.
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import {Page as BasePage} from '@gramlot/gramlot/page';
|
|
2
|
+
|
|
3
|
+
const PRODUCTS = {
|
|
4
|
+
lamp: {name: 'Lamp', category: 'lighting'},
|
|
5
|
+
spot: {name: 'Spotlight', category: 'lighting'},
|
|
6
|
+
chair: {name: 'Chair', category: 'seating'},
|
|
7
|
+
};
|
|
8
|
+
const RULES = {lighting: 'Ships in two days, bulbs not included.', seating: 'Ships in a week, assembly required.'};
|
|
9
|
+
const ON_REMOTE = "this.SET('requests', (this.GET('requests') ?? 0) + 1); this.SET('status', 'Mounted the ' + category + ' rules')";
|
|
10
|
+
|
|
11
|
+
export class Page extends BasePage {
|
|
12
|
+
static title = 'Cached remote';
|
|
13
|
+
static css = ['/themes/gramlot-base/theme.css'];
|
|
14
|
+
|
|
15
|
+
main(root) {
|
|
16
|
+
const pane = root.div({class: 'example-page stack'});
|
|
17
|
+
pane.h1('Cached remote');
|
|
18
|
+
pane.html_label('Product', {for: 'product'});
|
|
19
|
+
const choice = pane.select({id: 'product', value: '^current'});
|
|
20
|
+
for (const [key, product] of Object.entries(PRODUCTS)) choice.option(product.name, {value: `products.${key}`});
|
|
21
|
+
|
|
22
|
+
const card = pane.article({datapath: '^current', class: 'card stack'});
|
|
23
|
+
card.h2('^.name', {id: 'name'});
|
|
24
|
+
const rules = card.div('Waiting for the server', {id: 'rules'});
|
|
25
|
+
rules.remote('rules', {category: '^.category', _cachedRemote: true, _onRemote: ON_REMOTE});
|
|
26
|
+
|
|
27
|
+
const facts = pane.dl();
|
|
28
|
+
for (const [label, key] of [['Requests', 'requests'], ['Status', 'status']]) {
|
|
29
|
+
facts.dt(label);
|
|
30
|
+
facts.dd(`^${key}`, {id: key});
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
pane.dataSetter({destination_path: 'products', value: PRODUCTS});
|
|
34
|
+
pane.dataSetter({destination_path: 'current', value: 'products.lamp'});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
rules(root, {category}) {
|
|
38
|
+
root.p(RULES[category], {id: 'rule'});
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
Page.registerSource('rules');
|
|
42
|
+
|
|
43
|
+
/** The page logic: this page names no methods. */
|
|
44
|
+
export class Logic {}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# 13 · Cached remote
|
|
2
|
+
|
|
3
|
+
The control attributes of `remote` act on the requests of the element. The `article` has a variable datapath, as in binding example 02; the rules fragment depends only on the category of the product.
|
|
4
|
+
|
|
5
|
+
- **A change of the Data context** requests the fragment again: choosing another product moves the datapath of the `article`.
|
|
6
|
+
- **`_cachedRemote=True`** makes no request when the parameters equal those of the last request. Lamp and Spotlight share the category `lighting`: switching between them makes no request.
|
|
7
|
+
- **`_onRemote`** is inline code that runs after the fragment is mounted, with the parameters as locals: here it counts the mounted fragments and writes the status.
|
|
8
|
+
- **`_if` and `_else`** are the other control attributes: a false `_if` makes no request and runs `_else`.
|
|
9
|
+
|
|
10
|
+
The callbacks are inline code: the page needs a Content Security Policy that allows it.
|
|
11
|
+
|
|
12
|
+
**Try it:** Choose Spotlight, then Chair. Requests stays at 1 for Spotlight and becomes 2 for Chair.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import {Page as BasePage} from '@gramlot/gramlot/page';
|
|
2
|
+
|
|
3
|
+
const MENUS = {dishes: ['Soup', 'Risotto', 'Fish'], drinks: ['Water', 'Lemonade', 'Coffee']};
|
|
4
|
+
|
|
5
|
+
export class Page extends BasePage {
|
|
6
|
+
static title = 'Data remote';
|
|
7
|
+
static css = ['/themes/gramlot-base/theme.css'];
|
|
8
|
+
|
|
9
|
+
main(root) {
|
|
10
|
+
const pane = root.div({class: 'example-page stack', datapath: 'kitchen'});
|
|
11
|
+
pane.h1('Data remote');
|
|
12
|
+
|
|
13
|
+
const form = pane.form({class: 'card stack'});
|
|
14
|
+
form.html_label('Table', {for: 'table'});
|
|
15
|
+
const choice = form.select({value: '^.table', id: 'table'});
|
|
16
|
+
for (const key of Object.keys(MENUS)) choice.option(key[0].toUpperCase() + key.slice(1), {value: key});
|
|
17
|
+
|
|
18
|
+
const result = pane.section({class: 'card stack'});
|
|
19
|
+
result.h2('Menu, loaded by the server when it is read');
|
|
20
|
+
result.p('^.menu', {id: 'menu'});
|
|
21
|
+
|
|
22
|
+
pane.dataRemote({destination_path: '.menu', method: 'load_menu', cacheTime: 5, table: '^.table'});
|
|
23
|
+
pane.dataSetter({destination_path: '.table', value: 'dishes'});
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
load_menu({table}) { return MENUS[table].join(', '); }
|
|
27
|
+
}
|
|
28
|
+
Page.registerEndpoint('load_menu');
|
|
29
|
+
|
|
30
|
+
/** The page logic: this page names no methods. */
|
|
31
|
+
export class Logic {}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# 14 · Data remote
|
|
2
|
+
|
|
3
|
+
`dataRemote(destination_path, method, cacheTime=None, **params)` makes a Data path a value computed by an endpoint of the page. The endpoint is called when the path is read, not when the page starts.
|
|
4
|
+
|
|
5
|
+
- **Declare the endpoint** as for `dataRpc`: Python `@endpoint`, with `**kwargs`; JavaScript `Page.registerEndpoint('load_menu')`.
|
|
6
|
+
- **A read never waits.** The paragraph `^.menu` reads the path: the read returns the current value, empty before the first call, and starts the call. The answer is written with the Data event, so the paragraph shows the menu.
|
|
7
|
+
- **`cacheTime`** is in seconds. `5` serves the stored value for five seconds, then the next read calls again. `None` or a negative value calls once; `0` calls at every read and writes the answer without the Data event.
|
|
8
|
+
- **`table='^.table'`** is an argument of the endpoint. After the first call, a change of a `^` argument loads the value again at once and the paragraph receives it.
|
|
9
|
+
|
|
10
|
+
**Try it:** Choose Drinks: the menu changes to the drinks that the server returns.
|
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
The pages of this family run code when the Data changes.
|
|
4
4
|
|
|
5
|
-
`dataFormula` computes a value and writes it to its result path. `dataController` runs code for its effects. `dataRpc` calls an endpoint of the page on the server and writes the answer to its result path. A button runs a nested controller at every click; `connect_on<event>` attributes connect other DOM events. The code is inline in the page, or a method of the `Logic` class that the page module `NN_name.js` exports; the Python page takes it from there.
|
|
5
|
+
`dataFormula` computes a value and writes it to its result path. `dataController` runs code for its effects. `dataRpc` calls an endpoint of the page on the server and writes the answer to its result path. `remote` makes the content of an element a fragment built by the page on the server; `dataRemote` makes a Data path a value computed by an endpoint when it is read. A button runs a nested controller at every click; `connect_on<event>` attributes connect other DOM events. The code is inline in the page, or a method of the `Logic` class that the page module `NN_name.js` exports; the Python page takes it from there.
|
|
6
6
|
|
|
7
7
|
Inline code needs a Content Security Policy that allows it. Named logic in the `Logic` class also runs under a strict policy.
|
|
8
8
|
|
|
9
|
-
Start with **Formula** and **Controller**, then move the code to the `Logic` class with **Named logic**. **End-to-end story** puts every piece of the two families on one page.
|
|
9
|
+
Start with **Formula** and **Controller**, then move the code to the `Logic` class with **Named logic**. **End-to-end story** puts every piece of the two families on one page. Six pages call the server. **Endpoint**, **Call on change** and **Call on click** use `dataRpc`; **Remote** and **Cached remote** mount a fragment of the page with `remote`; **Data remote** computes a Data value with `dataRemote`.
|
|
10
10
|
|
|
11
|
-
Each example has a Python page and its JavaScript equivalent. Both use the `Logic` class of the JavaScript module. An endpoint is a method of the page in each language: Python `@endpoint`, JavaScript `Page.registerEndpoint`.
|
|
11
|
+
Each example has a Python page and its JavaScript equivalent. Both use the `Logic` class of the JavaScript module. An endpoint is a method of the page in each language: Python `@endpoint`, JavaScript `Page.registerEndpoint`. A fragment method is declared with Python `@source`, JavaScript `Page.registerSource`.
|