I would like to implement a simple ‘all of the above’ checkbox using Observable inputs (and nothing else too fancy… seems most answers to this on the Internet involve jQuery).
I imagined this problem to be conceptually easy, but I didn’t understand the mechanics of the checkbox input. While I can set an array of values as the initial value for the notebook, I can’t figure out how to change the output to change all boxes with a corresponding value to ‘selected’.
Here’s my misguided attempt:
Each value is it’s own array object, so with my attempt the value comes out looking: Array(3) ["1", "2", "1,2,3,4,5,6"]
Makes sense. So my attempt fails.
Is it possible to ‘overwrite’ the values or to ‘tick’ each box? Other easy avenues for ‘select all’ ?
It’s not exactly trivial, especially if you insist on adding the functionality on top of Jeremy’s widget. Regardless, here’s how you’d do it:
viewof options_list = {
const options = {
"1": "Red",
"2": "Orange",
"3": "Yellow",
// ...
};
// Useful convention: Prefix variables with "$" if they contain DOM nodes.
// Also useful to mark D3 and jQuery collections.
const $form = checkbox({
title: "Colors",
description: "Please select your favorite colors",
// Convert the options on the fly to {value, label} definitions.
options: Array.from(Object.entries(options), ([value, label]) => ({value, label})),
value: ["2"],
});
// We could also obtain a FormCollection via $form.elements, at the risk of
// getting extra elements like submit buttons.
const inputs = $form.querySelectorAll(`input[type="checkbox"]`);
// Obtain the inline style from an input and its label.
const inputCSS = inputs[0].style.cssText,
labelCSS = inputs[0].parentElement.style.cssText;
// Our "All of the above" checkbox. We'll wrap it in a label in another step.
const $selectAll = html`<input type=checkbox style="${inputCSS}">`;
// Inject our custom element and markup right between the last checkbox label
// and the description.
inputs[0].parentElement.parentElement.insertBefore(
html`<div><label style="${labelCSS}">${$selectAll} All of the above`,
inputs[inputs.length-1].parentElement.nextElementSibling
);
// Wrap the widget form so that we can expose our own .value property.
const $wrap = html`<div>${$form}`;
// What we'll return whenever $selectAll is checked.
const allValues = Array.from(Object.keys(options));
// Define a dynamic getter for .value to avoid having to maintain additional state.
Object.defineProperty($wrap, 'value', {
get() {
return $selectAll.checked
// If "All of the above" is checked, return all values.
// We call .slice() to avoid handing out the same array reference over again.
? allValues.slice()
// Else, Return the current widget value.
: $form.value;
}
});
// Usability improvement: Enable/disable all widget checkboxes whenever
// "All of the above" gets toggled.
// Important to note: We let the "input" event bubble up all the way to $wrap.
// Also, function() is used instead of an arrow function so that we can comfortably
// reference this instead of $selectAll.
$selectAll.oninput = function(e) {
for(const $i of inputs) $i.disabled = this.checked;
};
return $wrap;
}