Input.form Template option modification and examples

For the Documentation of Inputs.form(), it indicates that we can use a template to rearrange how the form controls are laid out.

The template code seems to show an overly simple example such as

function arrayTemplate(inputs) {
  return html`<div>${inputs}`;
}
...

function objectTemplate(inputs) {
  return html`<div>${Object.values(inputs)}`;
}

My understanding is that we would just be passing in the html literal value, I just included the whole functions as-is. The biggest issue is that I am relearning HTML and CSS and I am uncertain how to interop between the html and the Input.form controls

As an example, right now I have the following Input.form, and I am wondering how I would construct a template.

viewof modifier = Inputs.form({
  width: Inputs.range([0.1, 1], {
    label: "Tree Width Modifier",
    value: 1,
    step: 0.1
  }),
  height: Inputs.range([0.6, 4], {
    label: "Tree Height Modifier",
    value: 1,
    step: 0.1
  }),
  IndicatorEnabled: Inputs.toggle({
    label: "Enable Indicators?",
    value: false
  }),
  weekIndicator: Inputs.date({
    label: "Indicators Since Week Of:",
    min: "2021-11-24",
    max: new Date()
  })
})

Are there some good notebook examples that aren’t linked in the documentation? I assume after seeing some examples, I could create some templates by myself.

For some concrete layout ideas for templates:

  1. How would I go about creating 2 colomns and grouping the last 2 Input.form controls together and shifting them to the top-right?
  2. Is it possible to control the sizing of each of the 3 components for the Inputs.range control?
  3. What about having 3 columns where the Input.toggle is in the middle and the Input.date along with an Input.color control are grouped together in the 3rd column?

Edit: One thing that I assume is that I don’t have to define the whole html inputs using pure html only, as can be seen in Form Input / Mike Bostock | Observable. I assume there is some interop between the html literal and the existing Input controls within Input.form

For example by using the following template callback:

({IndicatorEnabled, weekIndicator, ...inputs}) => htl.html`<div style="display:flex; gap: 20px; justify-content: space-between">
  <div>${Object.values(inputs)}</div>
  <div>${IndicatorEnabled}${weekIndicator}</div>
`

You can pass in the width option to each, but this will also scale the label. Ideally you’d set a custom scope class on your container element (e.g. via DOM.uid().id) and insert a <style> element into your container that defines some local overrides. Have a look at this addCss helper.

For an example on how to target Inputs styles, see this notebook.

In general, you can do pretty much anything. Some examples:

At some point however it makes more sense to use native inputs instead of relying on Inputs widgets. Also keep in mind that you can pass arrays and and nest Inputs.form() calls, which further expands your options.

2 Likes

Thank you for all the examples! I got the 2 column template up and running. It took a few tries to get rid of any errors. The rest is just my commentary trying to summarize what I believe is happening for others that come across similar issues.

Note: there are many aspects of javascript, html, and css that I am unsure of.


Here is the full Input.form code as I think looking at the whole thing is so interesting.

viewof modifier = Inputs.form(
  {
    width: Inputs.range([0.2, 1], {
      label: "Width Multiplier",
      value: 1,
      step: 0.1
    }),
    height: Inputs.range([0.6, 4], {
      label: "Height Coefficient",
      value: 1,
      step: 0.1
    }),
    IndicatorEnabled: Inputs.toggle({
      label: "Enable Indicators?",
      value: false
    }),
    weekIndicator: Inputs.date({
      label: "Since Week Of:",
      min: "2021-11-24",
      max: new Date()
    })
  },
  {
    template: ({ IndicatorEnabled, weekIndicator, ...inputs }) =>
      htl.html`<div style="display:flex; gap: 20px; justify-content: space-between; width: 650px">
        <div>${Object.values(inputs)}</div>
        <div>${IndicatorEnabled}${weekIndicator}</div>`
  }
)

Note: Just for reference, here is Input.form’s signature

Inputs.form(inputs, options)

I had some trouble integrating the template correctly into the arguments. I am using the Object syntax for inputs, so I needed 2 objects. This is where most of my errors when integrating the suggested code above came from; not using 2 separate objects.


{
    template: ({ IndicatorEnabled, weekIndicator, ...inputs }) =>
      htl.html`<div style="display:flex; gap: 20px; justify-content: space-between; width: 650px">
        <div>${Object.values(inputs)}</div>
        <div>${IndicatorEnabled}${weekIndicator}</div>`
  }

So for the lambda, since I wanted to specifically modify the last 2 items in my inputs object, you passed the names of only the last 2 items along with the rest of the inputs.

Note: using ...inputs like is shown no longer contains the last 2 items. That is why using <div>${Object.values(inputs)}</div> doesn’t just output all 4 inputs. That is cool!

1 Like

You can read more about the “rest property” on MDN. I recommend to read up about the spread syntax as well, since both are valuable language features to keep your code concise.

When things start to get too convoluted it can also help to skip the short arrow form and instead use a function body:

template: (inputs) => {
  const left = Object.values(inputs).slice(0, -2);
  const right = Object.values(inputs).slice(-2);
  return htl.html`<div style="display:flex; gap: 20px; justify-content: space-between; width: 650px">
        <div>${left}</div>
        <div>${right}</div>`;
}

CSS has quite a few options to arrange elements (e.g. grid, columns) so that it may not even be necessary to pick out individual inputs.

2 Likes

Ahh, we can rename inputs whatever we want to in this instance; it doesn’t have to be called inputs. One reason I posted the signature of Inputs.form was because I thought it grabbed that content somehow and was using a spread operator, but that is just destructuring + rest property syntax. That makes a lot more sense now.

And again, thanks for the alternative solution and the pointer for CSS.