Skip to content

Commit 0f392a3

Browse files
committed
Improve custom elements page
1 parent a079dec commit 0f392a3

File tree

3 files changed

+137
-22
lines changed

3 files changed

+137
-22
lines changed

lib/components_guide_web/controllers/wasm_controller.ex

+3-1
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,9 @@ defmodule ComponentsGuideWeb.WasmController do
6767
@articles ["custom_elements"]
6868

6969
def show(conn, %{"id" => article}) when article in @articles do
70-
render(conn, article <> ".html")
70+
render(conn, article <> ".html",
71+
main_class: "max-w-2xl mt-16 mb-8 mx-auto prose md:prose-xl prose-invert"
72+
)
7173
end
7274

7375
def module(conn, %{"module" => name}) when is_map_key(@modules, name) do

lib/components_guide_web/controllers/wasm_html/custom_elements.html.md

+130
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,135 @@
11
# WebAssembly Custom Elements
22

3+
Extend HTML with Custom HTML Elements, and make them interactive with JavaScript.
4+
5+
## `<wasm-html>`
6+
7+
The element lets you use a WebAssembly instance that renders HTML. It loads your WebAssembly module for you, and instantiates it hooking into your exports. It expects specific exports:
8+
9+
- a `memory` exported under the name `"memory"`, which it will read the HTML text from (using the `MemoryIO` helper class below).
10+
- a `to_html()` function that returns the memory offset to your built HTML. This will be called on each render.
11+
- an optional `free_all()` function that is called at the start of each render, used to free memory if you need.
12+
13+
If your rendered HTML includes a `<button>` with a `data-action` attribute, then a click listener will be added. The value of this attribute if set to the name of an exported function, will be called every time the button is clicked. The HTML will also be re-rendered for you.
14+
15+
For example a `<button data-action="increment">Increment counter</button>` will call the `increment()` function you export, plus call your `to_html()` function, allowing you to re-render say a counter from `<output>1</output>` to `<output>2</output>`. Note: you must render the button each time: this allow you to change which buttons are available depending on your internal state.
16+
17+
### Usage
18+
19+
```html
20+
<wasm-html class="block">
21+
<source src="url/to/your/module.wasm" type="application/wasm" />
22+
</wasm-html>
23+
```
24+
25+
### Source
26+
27+
```js
28+
class WasmHTML extends HTMLElement {
29+
connectedCallback() {
30+
const wasmURL =
31+
this.getAttribute("src") ??
32+
this.querySelector("source[type='application/wasm']")?.src;
33+
if (!wasmURL) throw Error("Expected wasm URL as 'src' attribute or child <source>");
34+
35+
const wasmModulePromise = WebAssembly.compileStreaming(
36+
fetch(wasmURL, { credentials: "omit" });
37+
initWasmHTML(this, wasmModulePromise);
38+
}
39+
}
40+
41+
async function initWasmHTML(el, wasmModulePromise) {
42+
const wasmModule = await wasmModulePromise;
43+
44+
let memoryIO;
45+
const imports = {
46+
math: {
47+
powf32: (x, y) => Math.pow(x, y),
48+
},
49+
format: {
50+
f32: (f, memoryOffset) => {
51+
let s = String(f);
52+
// We always want a `.0` suffix, even for integers.
53+
if (!/[.]/.test(s)) {
54+
s = f.toFixed(1);
55+
}
56+
return memoryIO.writeStringAt(s, memoryOffset);
57+
},
58+
},
59+
log: {
60+
i32: (i) => console.log("wasm", i),
61+
f32: (f) => console.log("wasm", f),
62+
},
63+
};
64+
const instance = await WebAssembly.instantiate(wasmModule, imports);
65+
66+
memoryIO = new MemoryIO(instance.exports);
67+
const { to_html: toHTML, free_all: freeAll } = instance.exports;
68+
69+
function update() {
70+
freeAll?.apply();
71+
const html = memoryIO.readString(toHTML());
72+
el.innerHTML = html;
73+
}
74+
75+
// See definition below.
76+
addEventListenersToWasmInstance(instance, update);
77+
78+
// Schedule initial update.
79+
queueMicrotask(update);
80+
}
81+
82+
customElements.define("wasm-html", WasmHTML);
83+
```
84+
85+
## Event listeners
86+
87+
```js
88+
function addEventListenersToWasmInstance(instance, update) {
89+
el.addEventListener("click", (event) => {
90+
const action = event.target.dataset.action;
91+
if (typeof action === "string") {
92+
instance.exports[action]?.apply();
93+
update();
94+
}
95+
});
96+
97+
el.addEventListener("pointerdown", (event) => {
98+
if (event.buttons === 1) {
99+
const actionTarget = event.target.closest("[data-action");
100+
if (actionTarget == null) return;
101+
102+
const action = actionTarget.dataset.pointerdown;
103+
if (typeof action === "string") {
104+
instance.exports[action]?.apply();
105+
instance.exports["pointerdown_offset"]?.apply(null, [
106+
event.offsetX,
107+
event.offsetY,
108+
]);
109+
update();
110+
}
111+
}
112+
});
113+
114+
el.addEventListener("pointermove", (event) => {
115+
if (event.buttons === 1) {
116+
const actionTarget = event.target.closest("[data-action");
117+
if (actionTarget == null) return;
118+
119+
const action = actionTarget.dataset["pointerdown+pointermove"];
120+
if (typeof action === "string") {
121+
// instance.exports[action]?.apply();
122+
instance.exports["pointermove_offset"]?.apply(null, [
123+
event.offsetX,
124+
event.offsetY,
125+
]);
126+
update();
127+
}
128+
}
129+
});
130+
}
131+
```
132+
3133
## MemoryIO
4134
5135
```js

lib/components_guide_web/controllers/wasm_html/index.html.heex

+4-21
Original file line numberDiff line numberDiff line change
@@ -19,28 +19,11 @@
1919
</p>
2020

2121
<p>
22-
Custom HTML elements are used to load and interact with the Wasm modules in the browser:
22+
<.link href="/wasm/custom_elements">
23+
Custom HTML elements
24+
</.link>
25+
are used to load and interact with the Wasm modules in the browser.
2326
</p>
24-
<ul>
25-
<li>
26-
<a href="https://github.com/ComponentsGuide/components_guide/blob/master/assets/js/customElements/wasm-html.js">
27-
<code><%= "<wasm-chunked-html>" %></code>
28-
</a>
29-
for Wasm modules that render HTML components.
30-
</li>
31-
<li>
32-
<a href="https://github.com/ComponentsGuide/components_guide/blob/master/assets/js/customElements/wasm-state-machine.js">
33-
<code><%= "<wasm-state-machine>" %></code>
34-
</a>
35-
for Wasm modules that model state machines.
36-
</li>
37-
<li>
38-
<a href="https://github.com/ComponentsGuide/components_guide/blob/master/assets/js/customElements/wasm-string-builder.js">
39-
<code><%= "<wasm-string-builder>" %></code>
40-
</a>
41-
for Wasm modules that build strings.
42-
</li>
43-
</ul>
4427

4528
<hr />
4629
</header>

0 commit comments

Comments
 (0)