@gramlot/gramlot-examples 0.2.12 → 0.2.14

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.
@@ -25,11 +25,8 @@ export class GalleryPage extends IndexPage {
25
25
  footer(footer) {
26
26
  super.footer(footer);
27
27
  const inspector = footer.button({type: 'button', id: 'gallery-inspector', class: 'gallery-inspector',
28
- title: 'Inspect the gallery or the visible example', aria_label: 'Inspector', aria_pressed: 'false', disabled: true});
29
- const icon = inspector.svg({viewBox: '0 0 24 24', width: '18', height: '18', aria_hidden: 'true'});
30
- icon.rect({x: '3', y: '3', width: '18', height: '18', rx: '2', fill: 'none', stroke: 'currentColor', stroke_width: '1.5'});
31
- icon.path({d: 'M9 3v18M12 8h6M12 12h6M12 16h4', fill: 'none', stroke: 'currentColor', stroke_width: '1.5'});
32
- inspector.span('Inspector');
28
+ title: 'Open inspector', aria_label: 'Inspector', aria_pressed: 'false', disabled: true});
29
+ inspector.span('🔍', {aria_hidden: 'true'});
33
30
 
34
31
  }
35
32
  behavior(shell) { shell.script({src: this.constructor.galleryScript}); }
@@ -33,6 +33,7 @@
33
33
  .gallery-source-tabs button:focus-visible { outline: 2px solid var(--gramlot-focus-ring); outline-offset: -2px; }
34
34
  .gallery-source-tabs button[hidden] { display: none; }
35
35
  .gallery-explanation { padding: .4rem; }
36
- .gallery-inspector { display: inline-flex; align-items: center; gap: .35rem; margin-inline-start: auto; padding: .15rem .5rem; min-block-size: 26px; font-size: inherit; }
37
- .gallery-inspector svg { flex: none; }
36
+ .gallery-inspector { display: inline-flex; align-items: center; justify-content: center; flex: none; margin-inline-start: auto; padding: 0; inline-size: 28px; block-size: 28px; min-block-size: 28px; border: 0; background: transparent; box-shadow: none; font-size: 16px; line-height: 1; }
37
+ .gallery-inspector:hover { background: var(--gramlot-surface); }
38
+ .gallery-inspector:focus-visible { outline: 2px solid var(--gramlot-focus-ring); outline-offset: 2px; }
38
39
  .gallery-inspector[aria-pressed="true"] { background: var(--gramlot-action); color: var(--gramlot-navigation-text); }
@@ -93,8 +93,9 @@ from [the gallery guide](https://github.com/gramlot-org/gramlot-examples/blob/ma
93
93
  c13/c14 separate the basic trial from advanced cache/error/lifetime exercises.
94
94
  In c13, “Mounted fragments” counts successful mounts; HTTP calls must be observed
95
95
  independently. c14 has an explicit read button to distinguish a stored snapshot
96
- from a later response. Neither page claims every environment has been qualified. The corrected cache behavior documented in
97
- the development core is not automatically present in published packages.
96
+ from a later response. The corrected cache behavior is included from core 0.2.17. Update older
97
+ installations before following the advanced trials; a startup check alone does
98
+ not qualify every exercise in an environment.
98
99
 
99
100
  The common FileServer gallery has no named resource groups: css_requires,
100
101
  js_requires and named connect_on handlers need genro-kajenn's resource host.
@@ -48,7 +48,7 @@ The corrected identity is the method name plus the **TYTX snapshot of parameters
48
48
 
49
49
  ## Advanced practice
50
50
 
51
- **Development distinction:** the corrected cache identity is locally implemented in the core stabilization work; it is not a new published release. Check the runtime you are using before interpreting these advanced trials. Installed released packages may retain the earlier defects.
51
+ **Release history:** core 0.2.17 includes the corrected cache identity and cancellation behavior. Update an older runtime before trying these advanced cases.
52
52
 
53
53
  Run these separately after the basic trial, using that core cache correction. Reload between source edits so each trial has a known starting state.
54
54
 
@@ -0,0 +1,36 @@
1
+ import {Page as BasePage} from '@gramlot/gramlot/page';
2
+
3
+ export class Page extends BasePage {
4
+ static title = 'Publish and subscribe';
5
+ static css = ['/themes/gramlot-base/theme.css'];
6
+ main(root) {
7
+ const pane = root.main({class: 'example-page stack', datapath: 'messages'});
8
+ pane.h1('The same action, two entry points');
9
+ pane.p('Compare the direct controller with a page topic. Both update the same summary.');
10
+ pane.dataSetter({destination_path: '.text', value: 'Hello'});
11
+ pane.html_label('Message', {for: 'message'});
12
+ pane.input({id: 'message', value: '^.text', live: true});
13
+ pane.button('Direct controller', {id: 'direct'}).dataController({func: 'save', text: '=.text'});
14
+ pane.button('Publish', {id: 'publish'}).dataController({func: 'publishMessage', text: '=.text'});
15
+ pane.dataController({func: 'save', subscribe_saved: true});
16
+ pane.p('^.summary', {id: 'summary'});
17
+ pane.button('Ask, then publish', {id: 'ask'}).dataController({func: 'publishMessage',
18
+ _ask: {title: 'Message', fields: [{name: 'text', lbl: 'Message', required: true}]}, text: '=.text'});
19
+ pane.h2('Two independent receivers');
20
+ for (const name of ['left', 'right']) {
21
+ const box = pane.section({class: 'card stack', datapath: `.${name}`});
22
+ box.h3(name[0].toUpperCase() + name.slice(1));
23
+ const target = box.dataController({func: 'receive', selfsubscribe_refresh: true});
24
+ box.button(`Send to ${name}`, {id: `send-${name}`}).dataController({func: 'sendToNode', target, text: '=.text'});
25
+ box.p('^.received', {id: `received-${name}`});
26
+ box.dataSetter({destination_path: '.text', value: `Hello ${name}`});
27
+ }
28
+ }
29
+ }
30
+
31
+ export class Logic {
32
+ save(node, {text, _reason}) { node.SET('.summary', `${text} (${_reason})`); }
33
+ publishMessage(node, {text}) { this.page.publish('saved', {text}); }
34
+ sendToNode(node, {target, text}) { target.publish('refresh', {text}); }
35
+ receive(node, {text}) { node.SET('.received', text); }
36
+ }
@@ -0,0 +1,20 @@
1
+ # 18 · Publish and subscribe
2
+
3
+ Revisit **Button controller**: one button calls a controller directly, another publishes a message to the same behavior. Then address two independent receivers without looking up their IDs, and collect a message in a dialog.
4
+
5
+ **Uses:** Source, Data, named Logic, page/self subscriptions, authored references and native `ask` — core 0.2.18. No endpoint calls. Earlier lessons remain the 0.2.0 stage.
6
+
7
+ **Try it:** edit Message, press Direct controller, then Publish. The summary is the same except for the trigger reason. Send to left: only the left receiver changes. Ask, then publish lets you edit the outgoing message; Cancel leaves the summary unchanged.
8
+
9
+ <details>
10
+ <summary>More detail</summary>
11
+
12
+ The direct controller keeps the c06 click mechanism. The publishing controller knows the topic `saved`; `subscribe_saved=True` connects the receiver. The publisher does not need a Data trigger path to activate that receiver. Repeating the same message still delivers it.
13
+
14
+ Each receiver declares `selfsubscribe_refresh=True`. Python passes that controller as `target`; Gramlot resolves the authored reference in the browser. `target.publish('refresh', {text})` addresses only that node. The JavaScript Page makes the same declaration, and both use the shared Logic class.
15
+
16
+ `_ask` adds native field collection to the existing controller. It runs the controller once after confirmation and never after cancellation. This example uses named methods throughout; it does not need inline JavaScript evaluation. For a conditional confirmation use a string `_ask` with `_ask_if`; those inline conditions need the corresponding CSP policy.
17
+
18
+ **Exercise:** subscribe a second controller to `saved`, writing another Data path. Predict whether both run when Publish is pressed. **Answer:** both run, in registration order; each should write its own result. A self-subscription does not also subscribe to the plain page topic.
19
+
20
+ </details>
@@ -0,0 +1,39 @@
1
+ import {Page as BasePage} from '@gramlot/gramlot/page';
2
+
3
+ export class Page extends BasePage {
4
+ static title = 'Parent and child messages';
5
+ static css = ['/themes/gramlot-base/theme.css'];
6
+ main(root) {
7
+ const pane = root.main({class: 'example-page stack', datapath: 'frames'});
8
+ pane.h1('Parent and child messages');
9
+ pane.dataSetter({destination_path: '.child', value: false});
10
+ pane.dataSetter({destination_path: '.text', value: 'Hello'});
11
+ pane.html_label('Message', {for: 'message'});
12
+ pane.input({id: 'message', value: '^.text', live: true});
13
+ const parent = pane.section({hidden: '^.child'});
14
+ const frame = parent.iframe({id: 'detail', title: 'Child page', style: 'width:100%;height:260px;border:1px solid #aab4c2'});
15
+ parent.button('Send to child', {id: 'send-child'}).dataController({func: 'sendChild', target: frame, text: '=.text'});
16
+ pane.button('Reply to parent', {id: 'reply'}).dataController({func: 'reply', text: '=.text', child: '=.child'});
17
+ pane.p('^.received', {id: 'received'});
18
+ pane.dataController({func: 'receive', subscribe_message: true});
19
+ pane.dataController({func: 'setup', frame, _onStart: true});
20
+ }
21
+ }
22
+
23
+ export class Logic {
24
+ setup(node, {frame}) {
25
+ const url = new URL(this.page.dom.getDomNode(node.parentNode).ownerDocument.location.href);
26
+ const child = url.hash === '#child';
27
+ node.SET('.child', child);
28
+ if (!child) { url.hash = 'child'; frame.setAttr({src: url.href}); }
29
+ }
30
+ sendChild(node, {target, text}) {
31
+ try { this.page.publish({topic: 'message', iframe: target}, {text}); }
32
+ catch (error) { node.SET('.received', error.message); }
33
+ }
34
+ reply(node, {text, child}) {
35
+ if (child) this.page.publish({topic: 'message', parent: true}, {text});
36
+ else node.SET('.received', 'Use Reply inside the child page.');
37
+ }
38
+ receive(node, {text}) { node.SET('.received', text); }
39
+ }
@@ -0,0 +1,20 @@
1
+ # 19 · Parent and child messages
2
+
3
+ Send a message to the embedded page, then reply from that page. The example reuses the page-topic receiver from **Publish and subscribe** and adds a destination to `publish`.
4
+
5
+ **Uses:** Source, Data, named Logic, authored iframe references and direct parent/child `postMessage` — core 0.2.18. No endpoint calls or server messaging.
6
+
7
+ **Try it:** wait for the child field to appear, edit the outer Message and press Send to child. Both summaries change: publication is local first, then reaches the selected child. Edit the child's Message and press its Reply to parent. The child publishes locally first and the parent receives the reply.
8
+
9
+ <details>
10
+ <summary>More detail</summary>
11
+
12
+ The same page opens with `#child` inside the iframe. Its setup controller hides the parent section and does not load another iframe, so nesting stops after one child. The URL hash is only this example's mode selector; it is not a messaging address.
13
+
14
+ Python passes the iframe Source as `target`. Logic uses `{topic: 'message', iframe: target}`. The child uses `{topic: 'message', parent: true}`. All cross-frame calls use postMessage, including same-origin pages; each receiving page activates its ordinary subscribe_message controller.
15
+
16
+ The child must be connected and ready. An early click reports the readiness error; retry after the child appears. There is no automatic queue or broadcast. Source/DOM objects stay local: the iframe reference selects a local destination, while only the message data crosses the connection. Reloading the child establishes a new connection.
17
+
18
+ **Exercise:** add another owned child and send only to its reference. **Answer:** local delivery still occurs, but only the selected child receives the remote copy. There is no recursive broadcast in this release.
19
+
20
+ </details>
@@ -9,6 +9,8 @@ Add calculations and actions to bound pages, then calls to their host. Each stag
9
9
  - **0.2.15:** the later part of **Call on click** adds Source authorization.
10
10
  - **0.2.16:** **Remote**, **Cached remote** and **Data remote** add Source fragments and values loaded on demand.
11
11
 
12
+ - **0.2.18:** revisit **Button controller** in **Publish and subscribe**, then use **Parent and child messages** for direct iframe exchange. These pages require core 0.2.18 or later.
13
+
12
14
  The host is Python, Node/Bun or a standalone browser Worker. Local Logic runs in the browser. Stable example numbers identify pages; the menu follows release order.
13
15
 
14
16
  <details>
@@ -33,8 +35,8 @@ c12 teaches Source replacement in the later 0.2.16 stage.
33
35
  The Page/Logic module arrangement is a 0.2.5 addition to the named-logic lesson. c08's first pass combines local binding/lifecycle. Return after c09/c11 for its
34
36
  0.2.14 quote/result/error section; it does not run automatically or add remote/lazy Data. After b02 and c12, use c13
35
37
  for a first cache trial; c14 needs the endpoint concepts of c09/c10. Advanced
36
- cache/lifecycle exercises are separate in c13/c14; development-only cache
37
- corrections and pending environment-wide qualification are explicitly labelled. See the [release-by-release route](https://github.com/gramlot-org/gramlot/blob/main/docs/public/025-try.md#gc-025-015). Prerequisites support the
38
+ cache/lifecycle exercises are separate in c13/c14; their corrected cache behavior
39
+ is available from core 0.2.17. See the [release-by-release route](https://github.com/gramlot-org/gramlot/blob/main/docs/public/025-try.md#gc-025-015). Prerequisites support the
38
40
  chronological sequence; they do not replace it. The maintained files may use
39
41
  later APIs, so they are not snapshots of the earliest release.
40
42