Perhaps a silly question, but I would like to place multiple inputs, or inputs and text, next to each other in the same line, rather than vertically. In something like:
viewof clicks = Inputs.button("Click me!")
md`Clicked ${clicks} times`
have the text be next to the button. I’ve tried using the htl.html
keyword, but then I cannot assign the inputs to variables.
Inputs.form is a good way to go for this kind of thing. it accepts a template that you can use to thread your form elements into your content.
viewof form = Inputs.form(
{
button1: Inputs.button("Button 1"),
button2: Inputs.button("Button 2"),
button3: Inputs.button("Button 3"),
select: Inputs.select(["Cat", "Dog"])
},
{
template: (formParts) => htl.html`
<div>
<h3>Some Text</h3>
<div style="
width: 400px;
display: flex;
">
${Object.values(formParts)}
</div>
</div>`
}
)
2 Likes
Still, I can’t figure out how to access the value of the inputs from within the template? For instance, if I want to include number of clicks ${button1 clicks}
inside the template. That is, not just inputs, but text containing the result of the buttons.
here is a way to do that with a mutable cell:
viewof form = {
const button1 = Inputs.button("Button 1");
d3.select(button1).on("click", () => {
mutable count++;
});
return Inputs.form(
{
button1,
button2: Inputs.button("Button 2"),
button3: Inputs.button("Button 2"),
select: Inputs.select(["Cat", "Dog"])
},
{
template: (formParts) => htl.html`
<div>
<h3>Some Text: ${count}</h3>
<div style="
width: 400px;
display: flex;
">
${Object.values(formParts)}
</div>
</div>`
}
);
}
mutable count = 0
You picked a somewhat tricky example, as Inputs.button() has a minimum width that doesn’t work well when inlined. Let’s create our own button counter instead:
viewof clicks = htl.html`<button ${{
value: 0,
onclick() { this.value++ }
}}>Click me!`
You can reference viewof
cells in two ways:
- the value, via
clicks
- the element, via
viewof clicks
Elements can be pulled out of their original cell by including them in the output of another cell:
md`${viewof clicks} Clicked ${clicks} times`
You’ll notice a faint label <detached>
in place of the original viewof clicks
output.
Note that this approach has a downside: Because this cell depends on the value of the view, it invalidates whenever the value changes. When it invalidates, the cell’s element is recreated, and any form elements lose focus. This may be acceptable for clickable form inputs like radios, checkboxes or buttons, but doesn’t work well with text inputs or sliders.
A better approach may be to just create your own widget that includes the text:
viewof clicks2 = {
const counter = htl.html`<output>0`;
const button = htl.html`<button>Click me!`;
button.onclick = () => {
counter.value++;
// This is the event type that Observable's Runtime listens to by default.
button.dispatchEvent(new Event("input", {bubbles: true}));
};
const view = md`${button} clicked ${counter} times`;
return Object.defineProperty(view, "value", { get: () => +counter.value });
}
And if you don’t care about making the count available outside of the cell, you can simplify it to
{
const counter = htl.html`<output>0`;
const button = htl.html`<button onclick=${ () => counter.value++ }>Click me!</button>`;
return md`${button} clicked ${counter} times`;
}
1 Like