From 7c6e231d09115a4084113b47af273c00ad6419f7 Mon Sep 17 00:00:00 2001 From: DarkWiiPlayer Date: Sun, 6 Feb 2022 12:56:54 +0100 Subject: [PATCH] Rework skooma.bind function --- page/skooma.html | 73 +++++++++++++++++++++++++++++------------------- page/style.css | 37 +++++++++++++++--------- skooma.js | 30 ++++++++++---------- 3 files changed, 83 insertions(+), 57 deletions(-) diff --git a/page/skooma.html b/page/skooma.html index f310dff..e03dfe6 100644 --- a/page/skooma.html +++ b/page/skooma.html @@ -195,49 +195,66 @@

The bind helper

+
+ +
bind
+
transform-function ⟶ update-function
+
+
+

-
Callback registration function
-
- A function that takes a callback as its single argument and returns - an initial state as an array of elements. The inital state will be - used to generate the bound element for the first time. The callback - function should be called whenever an update in the UI is desired - and the new state should be passed as its argument. -
Transform function
+ +
...data ⟶ new-element
+
- A function that takes the initial or updated state and returns a - new HTML element. - If the function returns a non-truthy value, the element won't be - replaced. + A function that takes the current state and returns a new HTML element. + If the function returns a non-truthy value, the element won't be replaced. +
+ Note: the function must return a single element. + Therefore one cannot use tagged template literals with text + as this would return a document fragment which cannot be replaced. +
+
+
Update function
+ +
...data ⟶ new-element
+
+
+ A function that passes its arguments to the transform function and + returns its results while also taking care of replacing the old + element with the new one and injecting the current + attribute into it.

- Imagine counter to be an object with a count - attribute representing the current count and onIncrement to - be a function to register a callback to be called whenever the counter - gets updated. + A simple self-contained incrementing counter button could be implemented like this:

- // onIncrement doesn't return an initial state, so we have to wrap it: - let bindCount = bind(callback => counter.onIncrement(callback) || [counter.count]) - - let counterMessage = count => text`Current count: ${html.b(count)}` - - return bindCount(counterMessage) + let update = bind(count => html.button(`Count: ${count}`, {click: event => update(count+1)})) + document.body.append(update(1)) -

- This can also be broken down to text nodes for more atomic updates. + The initial call of update sets the initial count of the + button, and the attached event handler updates the button every time it + is clicked, thereby replacing it with a new one. +

+

+ For this next example, imagine a counter object that works like this: +

+ The following code could be used to display the current count in the application:

- // onIncrement doesn't return an initial state, so we have to wrap it: - let bindCount = bind(callback => counter.onIncrement(callback) || [counter.count]) - - return text`Current count: ${bindCount(text)}` + let update = bind(text) + counter.onIncrement(update) + return text`Current count: ${update(counter.count)}`

diff --git a/page/style.css b/page/style.css index a24b8ba..ffa8999 100644 --- a/page/style.css +++ b/page/style.css @@ -85,10 +85,30 @@ span[title] { border-bottom: dotted currentcolor .16em; } -.all-unset { - all: unset; +code-block:not(:defined) { + font-family: monospace; + white-space: pre-line; } +dl, dt, dd { all: unset; } +dt, dd { display: block; } +dl { + display: flex; + flex-flow: column; +} +dt { + font-weight: bold; + font-style: italic; +} +dl>*+dt { margin-top: .8em; } +dl>dt+* { margin-top: .6em; } +dl>*+* { margin-top: .4em; } + +code { font-size: 1.1em; } + +dl>code dt, dl>code dd { all: unset;} +dl>code dt::after { content: ' : ';} + .columns { display: grid; grid-template-columns: repeat(2, 1fr); @@ -96,17 +116,8 @@ span[title] { } .columns>* { margin: 0; -} - -.columns > * { flex: 100%; } -.full-width { - grid-column: 1/-1; -} - -code-block:not(:defined) { - font-family: monospace; - white-space: pre-line; -} +.all-unset { all: unset; } +.full-width { grid-column: 1/-1; } diff --git a/skooma.js b/skooma.js index 76d5591..bb093b2 100644 --- a/skooma.js +++ b/skooma.js @@ -83,21 +83,19 @@ const nameSpacedProxy = (options={}) => new Proxy(Window, { has: (target, prop) => true, }) -export const bind = register => transform => { +export const bind = transform => { let element - const addCurrent = current => Object.defineProperty(current, 'current', {get: () => element}) - element = transform(...register((...values) => { - try { - const next = transform(...values) - if (next) { - element.replaceWith(addCurrent(next)) - element = next - } - } catch (error) { - console.error(error) + const inject = next => Object.defineProperty(next, 'current', {get: () => element}) + const update = (...data) => { + const next = transform(...data) + if (next) { + console.log(element) + if (element) element.replaceWith(next) + element = inject(next) + return element } - })) - return addCurrent(element) + } + return update } export const handle = fn => event => { event.preventDefault(); return fn(event) } @@ -116,6 +114,6 @@ const textFromTemplate = (literals, items) => { } export const text = (data="", ...items) => - typeof data == "string" - ? document.createTextNode(data) - : textFromTemplate(data, items) + typeof data == "object" && "at" in data + ? textFromTemplate(data, items) + : document.createTextNode(data)