how to construct a select form with JavaScript?

I would like to build from @mootari’s Combined And Custom Forms by adding in a select input, but can’t make it work.

I start by trying to add in all required values for the select input type, which I understand to be name, id, form. Per the Mozilla documentation, I am aware that there’s a relationship between form and select that I am probably not setting correctly.

${row('new_id', inputs.new_id = html`<input
    type=select
    name="new_id"
    id="new_id"
    form="new_id"
    multiple=true
    options= ["1", "2"]
    value="1"
    >`)}

Searching the Internet for a solution, I keep finding examples that show me the element as an HTML element, but not so much on how to construct one in this way from JavaScript. The closest I have found is this fiddle, but I haven’t managed to massage a solution from it.

Any insights?

There is no such thing. All supported input types are listed here, under the section “ types”.

You create the <select> element and create, then append, each <option> element.

This can be simplified a lot with Observable’s html template tag function. E.g., given an object of values and labels, and a selected value, we can write:

function mySelect(options, name, defaultValue = null) {
  const elements = Array.from(Object.entries(options), ([value, label]) => {
    const attrSelected = value === defaultValue ? 'selected' : null;
    return html`<option value=${value} ${attrSelected}>${label}`;
  });
  return html`<select name=${name}>${elements}`;
}

and use the function as:

mySelect({
  value1: "Label for value 1",
  value2: "Label for value 2"
}, "the_element_name", "value2")
1 Like

Thank you for this, @mootari!

It definitely helps to realize it’s no select input type.

I continue to stumble trying to integrate this function within your ‘Combined And Custom Forms’ Notebook. I can get the dropdown to show up, but I can’t pass a title value into it in any meaningful way, and the data values aren’t being returned as part of the form object.

I basically tried declaring the function separately and then adding in a row that contains it:

${row('My Selection',
    mySelect({
    value1: "Label for value 1",
    value2: "Label for value 2"
    }, "the_element_name", "value2"))}

producing this:

And here’s the notebook:

And tips?

The only purpose of that row() function is to generate the markup for a two-column row. The element’s ID is only used to link the generated <label> element to the form element.

The magic lies in these line:

const values = linkViews({}, inputs);
form.value = {...values};

For each property of the inputs object a getter is defined on the passed-in object {}. Let’s call {} our view_value. If we access, e.g., view_value.camera_id, then that property’s get() callback will dynamically look up the corresponding value in inputs.camera_id.value.


If you take a look at the original form, you can see that each element is assigned on-the-fly to the inputs object:

  ${row('Surveyor ID', inputs.surveyor_ID = html`<input
  ${row('Camera ID', inputs.camera_id = html`<input
  ${row('Interview Date', inputs.interview_date = html`<input

So for ${row('Camera ID', inputs.camera_id = html`<input ... the order is:

  1. html`<input ... creates the element (or view, if you will). Note that the element already has a .value property.
  2. inputs.camera_id = html`<input ... assigns the element to the camera_id property on the inputs object.
  3. ${row('Camera ID', inputs.camera_id = ... pass the value of inputs.camera_id to the function row().
  4. row() wraps the element in the row/column markup and returns the result, which is what ends up in ${ ... } and ultimately places the element in the form variable’s DOM tree.

If you feel stuck while reading someone’s code, try to explain it to yourself (or write down the explanations, e.g. in code comments). This will give you some orientation and focus, and allow you to narrow down where you may have been misled by a wrong assumption.

1 Like