Place inputs and text next to each other?

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

Awesome, thanks!

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:

  1. the value, via clicks
  2. 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