Harris L created a cool List Input, which I would like to adapt to accept Jeremey’s inputs. Specifically, I would like to define a generic set of values, and allow users to add list items by selecting those values.
If I create this inputs as viewof select_input = select(select_values) I am creating a single value and DOM element, so if I try using that in the List Input, it will fail – just dragging that input down the list:
I thought that I could get around this by defining a function that generates the input and then using that:
function selectFunction() {
return select(select_values)
}
But this doesn’t work because the changes to the input values don’t persist (try the dropdown):
Here’s the notebook:
How does one create a “reusable” input element in this context?
… I realize that I keep asking variations on the same question, which is embarrassing for me and probably frustrating for you. I apologize. I feel that I am close to understanding the basics, but there’s just some bits still not clicking.
Sorry, I should have played more with the ‘See in Action’ example before posting. This takes me pretty far in understanding the pattern.
Here’s the functioning rendition:
Here’s the code:
viewof selectList5 = listInput({
value: ['Spring', 'Winter'],
defaultValue: null,
input: (value, i, values) => html.fragment`
<select style="margin-left: 1em;">
<option value=${null}>---</option>
${select_values.map(d => html.fragment` /// it's not exactly the same as a 'reusable input',
/// but we can specify a 'reusable options list'
/// and pass values from it input an HTML input
<option value=${d} selected=${d === value}>
${d}
</option>
`)}
</select>
`,
})
I will leave this as ‘unsolved’ b/c I haven’t quite gotten this to jive with Jeremy’s inputs… and it would be really nice to be able just to plug them in vs. re-creating the input each time (especially more complex ones line autoSelect)
The function receives a callback, which, when called, returns an object of form elements, where the property names correspond to the desired names in the values object.
The function returns a builder function which, when called, constructs a new form row.
function multiRow(inputs) {
// Return the "input" callback for listInput().
return function(value) {
// Fetch the object of form elements.
const $inputs = inputs(value);
// Assign default values. Note: will only work for elements that have a value property.
// Will not work for, e.g., checkboxes.
for(const [name, $input] of Object.entries($inputs)) {
if(name in value) $input.value = value[name];
}
// Drop all inputs into the wrapping container that handles the combined
// value and manages events.
const $view = html`<div>${Object.values($inputs)}`;
// Transform the inputs object into an object of property definitions.
const props = Object.fromEntries(Object.entries($inputs).map(([name, $input]) => [
// The property name.
name,
// The property definition.
{
// Value getter. Return the input's value.
get: () => $input.value,
// Make the property visible.
enumerable: true,
},
]));
// Construct the value object with its dynamic property getters.
$view.value = Object.defineProperties({}, props);
// Capture change events.
$view.onchange = e => {
// If we dispatched the event directly, do nothing.
if(e.target === $view) return;
// Catch change events that bubbled up from any of the inputs.
e.stopPropagation();
// Dispatch a new change event, this time from the wrapper so
// that the wrapper value gets processed instead.
$view.dispatchEvent(new Event('change', {bubbles: true}));
};
// Return wrapper.
return $view;
}
}
viewof myList = listInput({
// Create the input callback.
input: multiRow(function() {
// The form row's elements, indexed by their name.
return {
mySelect: DOM.select(['one', 'two']),
myText: html`<input>`,
};
}),
// An example default value, matching the form element properties.
defaultValue: {
mySelect: 'two',
}
})