I’m trying to make a custom input that is a box that the user can click on that returns the relative position (in percent) within the box. However all I get out when I try to use the variable is
AsyncGenerator {
<prototype>:
AsyncGenerator {}
}
Also, I’m trying to eventually bind the two values to two different sliders (so that the user can either select a value by individually moving the sliders or clicking in the box), so I’m also not even sure if this is the best way to go about that.
Here’s my code:
import {html} from "htl";
function clickBox() {
let value;
const style = {
width: "100px",
height: "100px",
cursor: "crosshair",
border: "1px solid black"
};
const box = html`<div style=${style} onclick=${event => {
const rect = event.currentTarget.getBoundingClientRect();
const x = (event.clientX - rect.left) / (rect.right - rect.left);
const y = (event.clientY - rect.top) / (rect.bottom - rect.top);
value = [x, y];
}}>`;
return Object.defineProperty(box, "value", {
get: () => value,
set: newValue => value = newValue
});
}
const cb = view(clickBox());
display(cb);
If I move display to its own cell, nothing shows up.
Thank you all so much!
Your onclick
callback needs to dispatch an “input” event to let the view()
generator know that it needs to produce a new value:
box.dispatchEvent(new Event("input", {bubbles: true}));
Depending on your requirements you may also want to assign an initial value
, otherwise downstream (i.e. dependent) blocks won’t resolve until after the first click.
view()
returns an async generator that should not be consumed directly in the same block.
Normally a generator can only have one consumer because it yields each value only once, but when cb
is referenced from other blocks, Framework’s Runtime will take care of resolving the generator’s promises and storing the values so that they can be passed to multiple blocks.
Thank you, that fixed it.
Do you know how I can link the value to my two existing sliders which look like:
const sin2 = view(Inputs.range([0, 1], {label: tex`\sin^2(\theta_{14})`, step: 0.01}));
const deltam = view(Inputs.range([0, 5], {label: tex`\Delta m_{14}^2 [eV^2]`, step: 0.05}));
I’ve tried assigning the views to Mutables, and using Inputs.bind(), but I can’t get either to work. Basically I can’t figure out a good way to extract the array to two values.
If there’s some way to set a Mutable from an input all my problems would be solved.
Ok I figured it out.
For reference, this is how I ended up doing it.
let sin2 = Mutable(0);
const setSin2 = (value) => {
sin2.value = value;
}
let deltam = Mutable(0);
const setDeltam = (value) => {
deltam.value = value;
}
const L = view(Inputs.range([0, 50], {label: html`Distance <i>[m]</i>`, step: 0.1}));
const sin2slider = view(Inputs.range([0, 1], {label: tex`\sin^2(\theta_{14})`, step: 0.01}));
const deltamslider = view(Inputs.range([0, 5], {label: tex`\Delta m_{14}^2 [eV^2]`, step: 0.05}));
setSin2(sin2slider);
setDeltam(deltamslider);
document.getElementById('clickme').onclick = function(e) {
// e = Mouse click event.
var rect = e.currentTarget.getBoundingClientRect();
var x = (e.clientX - rect.left)/(rect.right - rect.left); //x position within the element.
var y = (e.clientY - rect.top)/(rect.bottom - rect.top); //y position within the element.
setSin2(x);
setDeltam(5*y);
}
Please let me know if there’s a better way!!
The only problem is it doesn’t update the value next to the slider 
Here’s my take on it:
https://observablehq.observablehq.cloud/framework-example-custom-input-2d/
The custom input is essentially the same as described above. I declared the custom input and its value like so:
const xyInput = Range2D();
const xy = view(xyInput);
Then I separately declared the 1D range inputs:
const xInput = Inputs.range([0, 1], {label: "x", step: 0.01});
const yInput = Inputs.range([0, 1], {label: "y", step: 0.01});
const x = view(xInput);
const y = view(yInput);
Finally I used event listeners for bidirectional binding, checking if the event bubbles to distinguish direct and indirect input:
xyInput.oninput = (event) => {
if (!event.bubbles) return;
xInput.value = xyInput.value[0];
yInput.value = xyInput.value[1];
xInput.dispatchEvent(new Event("input"));
yInput.dispatchEvent(new Event("input"));
};
xInput.oninput = yInput.oninput = (event) => {
if (!event.bubbles) return;
xyInput.value = [xInput.value, yInput.value];
xyInput.dispatchEvent(new Event("input"));
};
There’s probably another way to do it, but, hey, it works. 