@gramlot/gramlot-examples 0.2.9 → 0.2.10

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 CHANGED
@@ -12,7 +12,7 @@ npm install @gramlot/gramlot-examples
12
12
  [![PyPI](https://img.shields.io/pypi/v/gramlot-examples)](https://pypi.org/project/gramlot-examples/)
13
13
  [![npm](https://img.shields.io/npm/v/@gramlot/gramlot-examples)](https://www.npmjs.com/package/@gramlot/gramlot-examples)
14
14
 
15
- Current release: 0.2.9.
15
+ Current release: 0.2.10.
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,8 @@ 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/`: eight pages with formulas, controllers, named logic, events and
30
- the end-to-end story (`c01`–`c08`).
29
+ - `controllers/`: eleven pages with formulas, controllers, named logic, events, the
30
+ end-to-end story and `dataRpc` calls to the endpoints of the page (`c01`–`c11`).
31
31
 
32
32
  Each `NN_name.js` exports `Page` and `Logic`; the Python page `NN_name.py` takes its
33
33
  `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.9",
3
+ "version": "0.2.10",
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.12"
17
+ "@gramlot/gramlot": ">=0.2.14"
18
18
  },
19
19
  "devDependencies": {
20
20
  "@genrojs/bag": ">=0.10.1",
@@ -239,6 +239,27 @@
239
239
  "folder": "08_end_to_end",
240
240
  "description": "Title, input, radios, checkbox, formula, SVG, button and freeze together.",
241
241
  "explanation": "Setters after the controls, live editing, a radio pair, a formula and controller driving text and SVG, a counting button and freeze."
242
+ },
243
+ {
244
+ "key": "c09",
245
+ "title": "Endpoint",
246
+ "folder": "09_endpoint",
247
+ "description": "dataRpc calls an endpoint of the page and writes the value at its result path.",
248
+ "explanation": "The page declares with_vat with @endpoint in Python and Page.registerEndpoint in JavaScript. The author attributes of the dataRpc are the arguments; _init=True makes the call when the page starts."
249
+ },
250
+ {
251
+ "key": "c10",
252
+ "title": "Call on change",
253
+ "folder": "10_call_on_change",
254
+ "description": "dataRpc follows a ^ argument, with _delay, _onCalling and _onResult.",
255
+ "explanation": "Each change of the quantity calls the endpoint 500 ms after the last edit. _onCalling and _onResult are inline code that write the status; the latest call wins."
256
+ },
257
+ {
258
+ "key": "c11",
259
+ "title": "Call on click",
260
+ "folder": "11_call_on_click",
261
+ "description": "A dataRpc nested in a button, with _onError for a failing and a protected endpoint.",
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."
242
263
  }
243
264
  ]
244
265
  }
@@ -7,15 +7,16 @@ 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/): eight pages with formulas, controllers, named
10
+ - [`controllers/`](controllers/): eleven pages with formulas, controllers, named
11
11
  logic in the `Logic` class of the page module, inline expressions, node methods, buttons,
12
- events and the end-to-end story.
12
+ events, the end-to-end story and `dataRpc` calls to the endpoints of the page.
13
13
 
14
14
  Each example is a file page: `NN_name.py`, its JavaScript equivalent `NN_name.js`,
15
15
  its README `NN_name.md`, and where needed a same-name stylesheet `NN_name.css`.
16
16
  `NN_name.js` exports `Page` and `Logic`: the JavaScript page uses both, the Python
17
17
  page takes `Logic` from it. The pages use only what the core `GramlotFileServer` serves:
18
- `Page.css`, the same-name stylesheet and the `Logic` of the page module. The gallery
18
+ `Page.css`, the same-name stylesheet, the `Logic` of the page module and the endpoints
19
+ of the page. The gallery
19
20
  adds its own files: the frame script, a staged `<key>_aux.js` per example and its
20
21
  assets ([GE-010 §025](https://github.com/gramlot-org/gramlot-examples/blob/main/docs/010-gallery.md#ge-010-025)).
21
22
 
@@ -0,0 +1,29 @@
1
+ import {Page as BasePage} from '@gramlot/gramlot/page';
2
+
3
+ export class Page extends BasePage {
4
+ static title = 'Endpoint';
5
+ static css = ['/themes/gramlot-base/theme.css'];
6
+
7
+ main(root) {
8
+ const pane = root.div({class: 'example-page stack', datapath: 'invoice'});
9
+ pane.h1('Endpoint');
10
+
11
+ const result = pane.section({class: 'card stack'});
12
+ result.h2('Total with VAT, computed by the server');
13
+ const facts = result.dl();
14
+ for (const [label, key] of [['Price', 'price'], ['VAT rate (%)', 'rate'], ['Total with VAT', 'gross']]) {
15
+ facts.dt(label);
16
+ facts.dd(`^.${key}`, {id: key});
17
+ }
18
+
19
+ pane.dataRpc({method: 'with_vat', result_path: '.gross', price: '=.price', rate: '=.rate', _init: true});
20
+ pane.dataSetter({destination_path: '.price', value: 12.5});
21
+ pane.dataSetter({destination_path: '.rate', value: 22});
22
+ }
23
+
24
+ with_vat({price, rate}) { return Math.round(price * (100 + rate)) / 100; }
25
+ }
26
+ Page.registerEndpoint('with_vat');
27
+
28
+ /** The page logic: this page names no methods. */
29
+ export class Logic {}
@@ -0,0 +1,12 @@
1
+ # 09 · Endpoint
2
+
3
+ An endpoint is a method of the page that the browser calls for a value. `dataRpc(method, result_path, **parameters)` calls it and writes the answer at `result_path`.
4
+
5
+ - **Declare the endpoint.** Python marks the method with `@endpoint`; JavaScript calls `Page.registerEndpoint('with_vat')` after the class.
6
+ - **Arguments.** The other attributes of the `dataRpc` are the arguments of the call: `price` and `rate`, read here from the Data with `=`. Python receives them as keywords, JavaScript as one object.
7
+ - **`**kwargs`.** `dataRpc` sends every attribute the author writes, and Python rejects a keyword the method does not declare. A Python endpoint called from `dataRpc` declares `**kwargs`.
8
+ - **`_init=True`** calls the endpoint once when the page starts, after the setters. The total is Data: the `dd` shows it through `^.gross`.
9
+
10
+ The call travels as one envelope on `POST /gramlot/rpc`; the page writes no `fetch`.
11
+
12
+ **Try it:** Open the browser network panel and reload: one call to `/gramlot/rpc` carries `with_vat` with price 12.5 and rate 22.
@@ -0,0 +1,37 @@
1
+ import {Page as BasePage} from '@gramlot/gramlot/page';
2
+
3
+ export class Page extends BasePage {
4
+ static title = 'Call on change';
5
+ static css = ['/themes/gramlot-base/theme.css'];
6
+
7
+ main(root) {
8
+ const pane = root.div({class: 'example-page stack', datapath: 'quote'});
9
+ pane.h1('Call on change');
10
+
11
+ const form = pane.form({class: 'card stack'});
12
+ form.html_label('Quantity', {for: 'quantity'});
13
+ form.input({type: 'number', min: 0, value: '^.quantity', live: true, id: 'quantity'});
14
+
15
+ const result = pane.section({class: 'card stack'});
16
+ result.h2('Quote');
17
+ const facts = result.dl();
18
+ for (const [label, key] of [['Total', 'total'], ['Status', 'status']]) {
19
+ facts.dt(label);
20
+ facts.dd(`^.${key}`, {id: key});
21
+ }
22
+
23
+ pane.dataRpc({method: 'quote', result_path: '.total', quantity: '^.quantity', _delay: 500, _init: true,
24
+ _onCalling: "this.SET('.status', 'Asking the server for ' + quantity)",
25
+ _onResult: "this.SET('.status', 'Answered ' + result + ' for ' + quantity)"});
26
+ pane.dataSetter({destination_path: '.quantity', value: 1});
27
+ }
28
+
29
+ quote({quantity}) {
30
+ quantity ??= 0;
31
+ return quantity * (quantity >= 10 ? 8 : 10);
32
+ }
33
+ }
34
+ Page.registerEndpoint('quote');
35
+
36
+ /** The page logic: this page names no methods. */
37
+ export class Logic {}
@@ -0,0 +1,13 @@
1
+ # 10 · Call on change
2
+
3
+ A `dataRpc` follows the Data like a `dataController`: a `^` argument calls the endpoint again at every change. The endpoint `quote` prices 10 per piece, 8 from ten pieces on.
4
+
5
+ - **`quantity='^.quantity'`** triggers the call when the quantity changes and sends the new value.
6
+ - **`_delay=500`** calls 500 ms after the last change, once: typing `12` makes one call, not two.
7
+ - **`_onCalling`** is inline code that runs before the call, with the arguments: it writes the status.
8
+ - **`_onResult`** runs after the value is written at `result_path`, with the arguments and `result`.
9
+ - **Latest call wins.** A new trigger aborts the call in flight and its answer is dropped.
10
+
11
+ `_init=True` makes the first call when the page starts. The callbacks are inline code: the page needs a Content Security Policy that allows it.
12
+
13
+ **Try it:** Type 12 in the quantity and watch the status before and after the answer.
@@ -0,0 +1,36 @@
1
+ import {Page as BasePage} from '@gramlot/gramlot/page';
2
+
3
+ const SHOW_ERROR = "this.SET('.error', error.code + ': ' + error.message)";
4
+
5
+ export class Page extends BasePage {
6
+ static title = 'Call on click';
7
+ static css = ['/themes/gramlot-base/theme.css'];
8
+
9
+ main(root) {
10
+ const pane = root.div({class: 'example-page stack', datapath: 'stock'});
11
+ pane.h1('Call on click');
12
+
13
+ const calls = pane.section({class: 'card stack'});
14
+ calls.h2('A dataRpc nested in the button');
15
+ const reserve = calls.button('Reserve the item', {id: 'reserve'});
16
+ reserve.dataRpc({method: 'reserve', result_path: '.reserved', item: '=.item', _onError: SHOW_ERROR});
17
+ const cancel = calls.button('Cancel the order (auth)', {id: 'cancel'});
18
+ cancel.dataRpc({method: 'cancel', result_path: '.reserved', item: '=.item', _onError: SHOW_ERROR});
19
+ const facts = calls.dl();
20
+ for (const [label, key] of [['Item', 'item'], ['Reserved', 'reserved'], ['Error', 'error']]) {
21
+ facts.dt(label);
22
+ facts.dd(`^.${key}`, {id: key});
23
+ }
24
+
25
+ pane.dataSetter({destination_path: '.item', value: 'Lamp'});
26
+ }
27
+
28
+ reserve({item}) { throw new Error(`${item} is out of stock`); }
29
+
30
+ cancel() { return false; }
31
+ }
32
+ Page.registerEndpoint('reserve');
33
+ Page.registerEndpoint('cancel', {auth: 'admin'});
34
+
35
+ /** The page logic: this page names no methods. */
36
+ export class Logic {}
@@ -0,0 +1,11 @@
1
+ # 11 · Call on click
2
+
3
+ A `dataRpc` nested in a `button` is the click mechanism of the button: each click calls the endpoint. A button has one mechanism, as in example 06.
4
+
5
+ - **`_onError`** is inline code that runs when the call fails, with the arguments and `error`. `error.code` is the outcome, `error.message` the message of the server, `error.remoteName` the name of the exception (`LookupError` in Python, `Error` in JavaScript).
6
+ - **An endpoint that raises.** `reserve` raises: the outcome is `application_error` and nothing is written at `result_path`.
7
+ - **A protected endpoint.** `cancel` declares the rule `auth="admin"` (Python `@endpoint(auth="admin")`, JavaScript `Page.registerEndpoint('cancel', {auth: 'admin'})`). A server without the `auth` capability knows no identity: the call answers `not_authenticated` and the method does not run.
8
+
9
+ Without `_onError` the failure reaches the browser console as an unhandled rejection.
10
+
11
+ **Try it:** Press each button and read the error.
@@ -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. 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. 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**. The last page is the end-to-end story: 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. The last three pages call the server: **Endpoint**, **Call on change** and **Call on click**.
10
10
 
11
- Each example has a Python page and its JavaScript equivalent. Both use the `Logic` class of the JavaScript module.
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`.