From 74377b87f60d56856f75fc9e690046d254bff86e Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Thu, 9 Dec 2021 22:15:47 +0100 Subject: [PATCH] Port html package (and one example) --- packages/html/examples/table/index.html | 9 +- packages/html/examples/table/package.json | 2 + packages/html/examples/table/src/index.ts | 96 +++++----- packages/html/src/index.ts | 212 ++++++++++++---------- 4 files changed, 173 insertions(+), 146 deletions(-) diff --git a/packages/html/examples/table/index.html b/packages/html/examples/table/index.html index 26590fa..413b66a 100644 --- a/packages/html/examples/table/index.html +++ b/packages/html/examples/table/index.html @@ -3,9 +3,8 @@ Syndicate: Table Example - - - + +

Table Example

@@ -27,8 +26,6 @@ Source code: index.ts

- + diff --git a/packages/html/examples/table/package.json b/packages/html/examples/table/package.json index dd960b7..22d0497 100644 --- a/packages/html/examples/table/package.json +++ b/packages/html/examples/table/package.json @@ -6,7 +6,9 @@ "scripts": { "prepare": "yarn compile && yarn rollup", "compile": "syndicate-tsc", + "compile:watch": "syndicate-tsc -w --verbose --intermediate-directory src.ts", "rollup": "rollup -c", + "rollup:watch": "rollup -c -w", "clean": "rm -rf lib/ index.js index.js.map" }, "author": "Tony Garnock-Jones ", diff --git a/packages/html/examples/table/src/index.ts b/packages/html/examples/table/src/index.ts index bda1b2e..94b9063 100644 --- a/packages/html/examples/table/src/index.ts +++ b/packages/html/examples/table/src/index.ts @@ -1,56 +1,60 @@ /// SPDX-License-Identifier: GPL-3.0-or-later /// SPDX-FileCopyrightText: Copyright © 2016-2021 Tony Garnock-Jones -import { Embedded, Value } from '@syndicate-lang/core'; -activate import { UIEvent, GlobalEvent, HtmlFragments, template, Anchor } from '@syndicate-lang/html'; +import { Dataspace, Embedded, Value, Ref } from '@syndicate-lang/core'; +import { boot as bootHtml, UIEvent, GlobalEvent, HtmlFragments, template, Anchor } from '@syndicate-lang/html'; assertion type Person(id, firstName, lastName, address, age); message type SetSortColumn(number); -boot { - function newRow(id: number, firstName: string, lastName: string, address: string, age: number) { - spawn named ('model' + id) { - assert Person(id, firstName, lastName, address, age); +Dataspace.boot(ds => { + bootHtml(ds); + at ds { + function newRow(id: number, firstName: string, lastName: string, address: string, age: number) { + spawn named ('model' + id) { + assert Person(id, firstName, lastName, address, age); + } + } + + newRow(1, 'Keith', 'Example', '94 Main St.', 44); + newRow(2, 'Karen', 'Fakeperson', '5504 Long Dr.', 34); + newRow(3, 'Angus', 'McFictional', '2B Pioneer Heights', 39); + newRow(4, 'Sue', 'Donnem', '1 Infinite Loop', 104); + newRow(5, 'Boaty', 'McBoatface', 'Arctic Ocean', 1); + + spawn named 'view' { + let ui = new Anchor(); + field orderColumn: number = 2; + + function cell(text: Value): HtmlFragments { + return template`${text.toString()}`; + } + + on message SetSortColumn($c: number) => orderColumn.value = c; + + during Person($id: number, $firstName: string, $lastName: string, $address: string, $age: number) => { + assert ui.context(id).html( + 'table#the-table tbody', + template`${[id, firstName, lastName, address, age].map(cell)}`, + [id, firstName, lastName, address, age][orderColumn.value]); + } + } + + spawn named 'controller' { + on message GlobalEvent('table#the-table th', 'click', $e) => { + const event = (e as Embedded).embeddedValue.target.data as Event; + send message SetSortColumn(JSON.parse((event.target as HTMLElement).dataset.column!)); + } + } + + spawn named 'alerter' { + let ui = new Anchor(); + assert ui.html('#extra', template``); + + on message UIEvent(ui.fragmentId, '.', 'click', $_e) => { + alert("Hello!"); + } } } +}); - newRow(1, 'Keith', 'Example', '94 Main St.', 44); - newRow(2, 'Karen', 'Fakeperson', '5504 Long Dr.', 34); - newRow(3, 'Angus', 'McFictional', '2B Pioneer Heights', 39); - newRow(4, 'Sue', 'Donnem', '1 Infinite Loop', 104); - newRow(5, 'Boaty', 'McBoatface', 'Arctic Ocean', 1); - - spawn named 'view' { - let ui = new Anchor(); - field orderColumn: number = 2; - - function cell(text: Value): HtmlFragments { - return template`${text.toString()}`; - } - - on message SetSortColumn($c: number) => this.orderColumn = c; - - during Person($id: number, $firstName: string, $lastName: string, $address: string, $age: number) => { - assert ui.context(id) - .html('table#the-table tbody', - template`${[id, firstName, lastName, address, age].map(cell)}`, - [id, firstName, lastName, address, age][this.orderColumn]); - } - } - - spawn named 'controller' { - on message GlobalEvent('table#the-table th', 'click', $e) => { - send message SetSortColumn( - JSON.parse(((e as Embedded).embeddedValue.target as HTMLElement).dataset.column!)); - } - } - - spawn named 'alerter' { - let ui = new Anchor(); - assert ui.html('#extra', template``); - - on message UIEvent(ui.fragmentId, '.', 'click', $_e) => { - alert("Hello!"); - } - } -} diff --git a/packages/html/src/index.ts b/packages/html/src/index.ts index 2f8e46d..0c88716 100644 --- a/packages/html/src/index.ts +++ b/packages/html/src/index.ts @@ -1,7 +1,8 @@ /// SPDX-License-Identifier: GPL-3.0-or-later /// SPDX-FileCopyrightText: Copyright © 2016-2021 Tony Garnock-Jones -import { randomId, Facet, Observe, FlexMap, Value, embed, Embedded } from "@syndicate-lang/core"; +import { randomId, Observe, FlexMap, embed, Embedded, Ref, Turn, AnyValue } from "@syndicate-lang/core"; +import { QuasiValue as Q } from "@syndicate-lang/core"; import * as P from "./protocol"; export * from "./protocol"; @@ -9,15 +10,15 @@ export * from "./protocol"; import { HtmlFragments } from "./html"; export * from "./html"; -boot { - spawnGlobalEventFactory(thisFacet); - spawnWindowEventFactory(thisFacet); - spawnUIFragmentFactory(thisFacet); - spawnUIAttributeFactory(thisFacet); - spawnUIPropertyFactory(thisFacet); - spawnUIChangeablePropertyFactory(thisFacet); - spawnLocationHashTracker(thisFacet); - spawnAttributeUpdater(thisFacet); +export function boot(ds: Ref) { + spawnGlobalEventFactory(ds); + spawnWindowEventFactory(ds); + spawnUIFragmentFactory(ds); + spawnUIAttributeFactory(ds); + spawnUIPropertyFactory(ds); + spawnUIChangeablePropertyFactory(ds); + spawnLocationHashTracker(ds); + spawnAttributeUpdater(ds); } //--------------------------------------------------------------------------- @@ -32,16 +33,20 @@ export function newFragmentId() { //--------------------------------------------------------------------------- -export function spawnGlobalEventFactory(thisFacet: Facet) { +export function spawnGlobalEventFactory(ds: Ref) { spawn named 'GlobalEventFactory' { - during Observe(P.GlobalEvent($selector: string, $eventType: string, _)) => - spawn named ['GlobalEvent', selector, eventType] { - let sender = thisFacet.wrapExternal((thisFacet, e: Event) => { - send message P.GlobalEvent(selector, eventType, embed(e)); - }); + at ds { + during Observe({ + "pattern": :pattern P.GlobalEvent(\Q.lit($selector: string), + \Q.lit($eventType: string), + \_) + }) => spawn named ['GlobalEvent', selector, eventType] { + const facet = Turn.activeFacet; function handler(event: Event) { - sender(event); + facet.turn(() => { + send message P.GlobalEvent(selector, eventType, embed(create ({ data: event }))); + }); return dealWithPreventDefault(eventType, event); } @@ -50,7 +55,7 @@ export function spawnGlobalEventFactory(thisFacet: Facet) { eventUpdater(cleanEventType(eventType), handler, install)); } - on start updateEventListeners(true); + updateEventListeners(true); on stop updateEventListeners(false); on asserted P.UIFragmentVersion($_i, $_v) => updateEventListeners(true); @@ -58,19 +63,22 @@ export function spawnGlobalEventFactory(thisFacet: Facet) { // lets us ignore UIFragmentVersion records coming and going; on // the other hand, we do potentially a lot of redundant work. } + } } } -export function spawnWindowEventFactory(thisFacet: Facet) { +export function spawnWindowEventFactory(ds: Ref) { spawn named 'WindowEventFactory' { - during Observe(P.WindowEvent($eventType: string, _)) => - spawn named ['WindowEvent', eventType] { - let sender = thisFacet.wrapExternal((thisFacet, e: Event) => { - send message P.WindowEvent(eventType, embed(e)); - }); + at ds { + during Observe({ + "pattern": :pattern P.WindowEvent(\Q.lit($eventType: string), \_) + }) => spawn named ['WindowEvent', eventType] { + const facet = Turn.activeFacet; let handler = function (event: Event) { - sender(event); + facet.turn(() => { + send message P.WindowEvent(eventType, embed(create ({ data: event }))); + }); return dealWithPreventDefault(eventType, event); } @@ -82,9 +90,10 @@ export function spawnWindowEventFactory(thisFacet: Facet) { } } - on start updateEventListeners(true); + updateEventListeners(true); on stop updateEventListeners(false); } + } } } @@ -101,17 +110,17 @@ function isNodeOrderKey(x: any): x is NodeOrderKey { type HandlerClosure = (event: Event) => void; -function spawnUIFragmentFactory(thisFacet: Facet) { +function spawnUIFragmentFactory(ds: Ref) { type RegistrationKey = [string, string]; // [selector, eventType] spawn named 'UIFragmentFactory' { - during P.UIFragment($fragmentId0, _, _, _) => - spawn named ['UIFragment', fragmentId0] { + at ds { + during P.UIFragment($fragmentId0, _, _, _) => spawn named ['UIFragment', fragmentId0] { if (!isFragmentId(fragmentId0)) return; const fragmentId = fragmentId0; field version: number = 0; - assert P.UIFragmentVersion(fragmentId, this.version) when (this.version > 0); + assert P.UIFragmentVersion(fragmentId, version.value) when (version.value > 0); let selector: string; let html: Array; @@ -122,8 +131,13 @@ function spawnUIFragmentFactory(thisFacet: Facet) { on stop removeNodes(); - during Observe(P.UIEvent(fragmentId, $selector: string, $eventType: string, _)) => { - on start updateEventListeners([ selector, eventType ], true); + during Observe({ + "pattern": :pattern P.UIEvent(fragmentId, + \Q.lit($selector: string), + \Q.lit($eventType: string), + \_) + }) => { + updateEventListeners([ selector, eventType ], true); on stop updateEventListeners([ selector, eventType ], false); } @@ -133,7 +147,7 @@ function spawnUIFragmentFactory(thisFacet: Facet) { removeNodes(); selector = newSelector; - html = (newHtml as Embedded>).embeddedValue; + html = (newHtml as Embedded).embeddedValue.target.data as ChildNode[]; orderBy = newOrderBy; anchorNodes = (selector !== null) ? selectorMatch(document.body, selector) : []; @@ -153,7 +167,7 @@ function spawnUIFragmentFactory(thisFacet: Facet) { // (re)install event listeners eventRegistrations.forEach((_handler, key) => updateEventListeners(key, true)); - this.version++; + version.value++; } function removeNodes() { @@ -172,11 +186,11 @@ function spawnUIFragmentFactory(thisFacet: Facet) { let handlerClosure: HandlerClosure; if (!eventRegistrations.has(key)) { - let sender = thisFacet.wrapExternal((thisFacet, e: Event) => { - send message P.UIEvent(fragmentId, selector, eventType, embed(e)); - }); + const facet = Turn.activeFacet; function handler(event: Event) { - sender(event); + facet.turn(() => { + send message P.UIEvent(fragmentId, selector, eventType, embed(create ({ data: event }))); + }); return dealWithPreventDefault(eventType, event); } eventRegistrations.set(key, handler); @@ -203,6 +217,7 @@ function spawnUIFragmentFactory(thisFacet: Facet) { } } } + } } } @@ -303,29 +318,32 @@ function configureNode(n: ChildNode) { //--------------------------------------------------------------------------- -function spawnUIAttributeFactory(thisFacet: Facet) { +function spawnUIAttributeFactory(ds: Ref) { spawn named 'UIAttributeFactory' { - during P.UIAttribute($selector: string, $attribute: string, $value) => - spawn named ['UIAttribute', selector, attribute, value] { - _attributeLike(thisFacet, selector, attribute, value, 'attribute'); - } + at ds { + during P.UIAttribute($selector: string, $attribute: string, $value) => + spawn named ['UIAttribute', selector, attribute, value] { + _attributeLike(selector, attribute, value, 'attribute'); + } + } } } -function spawnUIPropertyFactory(thisFacet: Facet) { +function spawnUIPropertyFactory(ds: Ref) { spawn named 'UIPropertyFactory' { - during P.UIProperty($selector: string, $property: string, $value) => - spawn named ['UIProperty', selector, property, value] { - _attributeLike(thisFacet, selector, property, value, 'property'); - } + at ds { + during P.UIProperty($selector: string, $property: string, $value) => + spawn named ['UIProperty', selector, property, value] { + _attributeLike(selector, property, value, 'property'); + } + } } } -function _attributeLike(thisFacet: Facet, - selector: string, - key: string, - value: Value, - kind: 'attribute' | 'property') +function _attributeLike(selector: string, + key: string, + value: AnyValue, + kind: 'attribute' | 'property') { let savedValues: Array<{node: Element, value: any}> = []; @@ -387,7 +405,7 @@ function _attributeLike(thisFacet: Facet, }); savedValues = []; } -}; +} function splitClassValue(v: string | null): Array { v = (v ?? '').trim(); @@ -396,22 +414,28 @@ function splitClassValue(v: string | null): Array { //--------------------------------------------------------------------------- -function spawnUIChangeablePropertyFactory(thisFacet: Facet) { +function spawnUIChangeablePropertyFactory(ds: Ref) { spawn named 'UIChangeablePropertyFactory' { - during Observe(P.UIChangeableProperty($selector: string, $property: string, _)) => - spawn named ['UIChangeableProperty', selector, property] { - on start selectorMatch(document.body, selector).forEach(node => { + at ds { + during Observe({ + "pattern": :pattern P.UIChangeableProperty(\Q.lit($selector: string), + \Q.lit($property: string), + \_) + }) => spawn named ['UIChangeableProperty', selector, property] { + selectorMatch(document.body, selector).forEach(node => { react { - field value: any = (node as any)[property]; - assert P.UIChangeableProperty(selector, property, this.value); - const handlerClosure = thisFacet.wrapExternal((_thisFacet, _e: Event) => { - this.value = (node as any)[property]; + field propValue: any = (node as any)[property]; + assert P.UIChangeableProperty(selector, property, propValue.value); + const facet = Turn.activeFacet; + const handlerClosure = (_e: Event) => facet.turn(() => { + propValue.value = (node as any)[property]; }); - on start eventUpdater('change', handlerClosure, true)(node); + eventUpdater('change', handlerClosure, true)(node); on stop eventUpdater('change', handlerClosure, false)(node); } }); } + } } } @@ -463,56 +487,56 @@ export class Anchor { } html(selector: string, html: HtmlFragments, orderBy: NodeOrderKey = ''): ReturnType { - return P.UIFragment(this.fragmentId, selector, embed(html.nodes()), orderBy); + return P.UIFragment(this.fragmentId, selector, embed(create ({ data: html.nodes() })), orderBy); } } //--------------------------------------------------------------------------- -function spawnLocationHashTracker(thisFacet: Facet) { +function spawnLocationHashTracker(ds: Ref) { spawn named 'LocationHashTracker' { - field hashValue: string = '/'; - assert P.LocationHash(this.hashValue); + at ds { + field hashValue: string = '/'; + assert P.LocationHash(hashValue.value); - const loadHash = () => { - var h = window.location.hash; - if (h.length && h[0] === '#') { - h = h.slice(1); - } - this.hashValue = h || '/'; - }; + const loadHash = () => { + var h = window.location.hash; + if (h.length && h[0] === '#') { + h = h.slice(1); + } + hashValue.value = h || '/'; + }; + const facet = Turn.activeFacet; + const handlerClosure = () => facet.turn(loadHash); - let handlerClosure = thisFacet.wrapExternal(loadHash); - - on start { loadHash(); window.addEventListener('hashchange', handlerClosure); - } - on stop { - window.removeEventListener('hashchange', handlerClosure); - } + on stop window.removeEventListener('hashchange', handlerClosure); - on message P.SetLocationHash($newHash: string) => { - window.location.hash = newHash; + on message P.SetLocationHash($newHash: string) => { + window.location.hash = newHash; + } } } } //--------------------------------------------------------------------------- -function spawnAttributeUpdater(thisFacet: Facet) { +function spawnAttributeUpdater(ds: Ref) { spawn named 'AttributeUpdater' { - on message P.SetAttribute($s: string, $k: string, $v: string) => - update(s, n => n.setAttribute(k, v)); - on message P.RemoveAttribute($s: string, $k: string) => - update(s, n => n.removeAttribute(k)); - on message P.SetProperty($s: string, $k: string, $v) => - update(s, n => { (n as any)[k] = v }); - on message P.RemoveProperty($s: string, $k: string) => - update(s, n => { delete (n as any)[k]; }); + at ds { + on message P.SetAttribute($s: string, $k: string, $v: string) => + update(s, n => n.setAttribute(k, v)); + on message P.RemoveAttribute($s: string, $k: string) => + update(s, n => n.removeAttribute(k)); + on message P.SetProperty($s: string, $k: string, $v) => + update(s, n => { (n as any)[k] = v }); + on message P.RemoveProperty($s: string, $k: string) => + update(s, n => { delete (n as any)[k]; }); - function update(selector: string, nodeUpdater: (n: Element) => void) { - selectorMatch(document.body, selector).forEach(nodeUpdater); + function update(selector: string, nodeUpdater: (n: Element) => void) { + selectorMatch(document.body, selector).forEach(nodeUpdater); + } } } }