Public
Edited
Apr 24, 2024
Insert cell
Insert cell
viewof mas = multiAutoSelect({
options: states,
placeholder: "Select some US states . . .",
value: ["California", "Washington", "Alabama"],
label: "State"
})
Insert cell
Insert cell
viewof statewrtgender = multiAutoSelect({
options: states,
placeholder: "Select some US states . . .",
value: ["California", "Washington", "Alabama"],
postRender: postRender({ options: ["🚹", "🚺", "⚧️"] }) // Custom function for rendering
})
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
// Function that takes options and return function for inner element rendering
function postRender({ options = ["♂️", "♀️", "⚧️"] } = {}) {
return (d, button, ele, fm) => {
// Initialize the sort order
if (!fm.order) {
fm.order = [];
}

//Get order when there is a change in order or selection
function getOrder() {
fm.order = [];
console.log("node count", fm[1].childNodes);
for (let child of fm[1].childNodes) {
fm.order.push(child.childNodes[1]?.nodeValue);
}
}

// Update the order if the elements are re-ordered
fm.output.addEventListener("update", () => getOrder());

ele.addEventListener("click", () => {
// Initialize the nested option only when the length is 2
if (ele.childNodes.length === 2) {
//Get random option
let randoption = options[Math.floor(Math.random() * options.length)];
//Insert before the delete button
ele.insertBefore(html`${randoption}`, button);
fm.order.push(randoption);
fm.dispatchEvent(new Event("input", { bubbles: true }));

return;
}
// Get the new option
let newoption =
(options.indexOf(ele.childNodes[1]?.nodeValue) + 1) % options.length;
ele.removeChild(ele.childNodes[1]);
ele.insertBefore(html`${options[newoption]}`, button);
fm.dispatchEvent(new Event("input", { bubbles: true }));
getOrder();
});
// For the 1st time initialization
ele.click();
};
}
Insert cell
states = usa.objects.states.geometries.map((d) => d.properties.name)
Insert cell
Insert cell
viewof eg2 = multiAutoSelect({ options: objects, attr: (d) => d.name })
Insert cell
objects = [
{ id: 1, name: "John" },
{ id: 2, name: "Alexis" },
{ id: 3, name: "Guerra" },
{ id: 4, name: "Gomez" }
]
Insert cell
eg2
Insert cell
function multiAutoSelect() {
let config;
// To behave like other Input.checkboxes with two parameters
if (arguments.length === 1) {
config = arguments[0];
} else {
config = arguments[1];
config.options = arguments[0];
}
const {
value,
title,
description,
disabled,
autocomplete = "off",
placeholder,
size,
options,
label = "",
list = DOM.uid("autoSelect").id,
attr = (d) => d, // an accessor on what attribute of the object to use
postRender = (d) => d // A function called after each element has been rendered, receives:
// d: datum
// button: the button used for deleting
// ele: the html element
// fm: the overall widget, useful for changing the return value of the widget using fm.value
} = Array.isArray(config) ? { options: config } : config;

const optionsMap = new Map(options.map((o) => ["" + attr(o), o])); // We need the map with strings

const onAction = (fm) => {
const addToSelected = (d) => {
if (
optionsMap.has(fm.input.value) && // It is an option
fm.value.map((d) => "" + d).indexOf(fm.input.value) === -1 // If it hasn't been selected. Need to convert to strings to do the comparison for numbers
) {
fm.value.push(optionsMap.get(fm.input.value));
renderSelection();
fm.input.value = "";
fm.dispatchEvent(new Event("input", { bubbles: true }));
}
};
const renderSelected = (d) => {
const button = html`<button type="button" style="margin:0px; padding:0px;">✖️</button>`;
const ele = html`<span style="display: inline-block; margin: 7px 2px; border: solid 1px #ccc; border-radius: 5px;padding: 3px 6px; cursor:move; box-shadow: 1px 1px 1px #777; background: white">${attr(
d
)} ${button}</span>`;
button.addEventListener("click", (e) => {
fm.value.splice(fm.value.indexOf(d), 1);
// remove the element directly
ele.remove();
fm.dispatchEvent(new Event("input", { bubbles: true }));
fm.input.focus();
});

// Highlight the dragging element position
ele.addEventListener("dragover", (e) => {
ele.style["border-color"] = "orange";
});

// Update back to default color
ele.addEventListener("dragleave", (e) => {
ele.style["border-color"] = "#ccc";
});

// Update back to default color
ele.addEventListener("dragend", (e) => {
ele.style["border-color"] = "#ccc";
});

// Call nested selection to re-render inner elements
postRender(d, button, ele, fm);

return ele;
};

function renderSelection() {
for (let o of fm.value) {
//Check if the element is already selected
if (
![...fm.output.childNodes]
.map((d) => {
// + for integer
let data = d.firstChild?.data?.trim();
return isNaN(+data) ? data : +data;
})
.includes(attr(o))
) {
fm.output.appendChild(renderSelected(o));
}
}

Sortable.create(fm.output);
//Update event will be triggered when the user drag the selected components
Sortable.utils.on(fm.output, "update", () => {
fm.value = [...fm.output.childNodes].map((a) =>
a.childNodes[0].nodeValue.trim()
);
fm.dispatchEvent(new Event("input", { bubbles: true }));
});
}

fm.input.value = "";
fm.value = value
? Array.isArray(value)
? value.filter((d) => optionsMap.has(d))
: [value]
: [];
fm.onsubmit = (e) => {
e.preventDefault();
addToSelected();
};
fm.input.addEventListener("input", function (e) {
e.stopPropagation();
console.log("input", e);

// Avoid adding the selection when still writing. Happens with numbers 🤷🏼‍♂️
if (
e.inputType === "insertReplacementText" ||
e.inputType === undefined // triggered when selecting from the datalist
) {
addToSelected();
}
});


renderSelection();
};

const form = input({
type: "text",
title,
description,
attributes: { disabled },
action: onAction,
form: html`
<form>
${label ? `<label>${label}</label>` : ""}
<input name="input" type="text" autocomplete="off"
placeholder="${
placeholder || ""
}" style="font-size: 1em;" list=${list}>
<datalist id="${list}">
${options.map((d) =>
Object.assign(html`<option>`, {
value: attr(d)
})
)}
</datalist>
<br/>
</form>
`
});

form.output.style["margin-left"] = "0px";
form.style["min-height"] = "2.5em";

return form;
}
Insert cell
## Testing with numbers
Insert cell
multiAutoSelect({ options: d3.range(20).slice(1, 20) })
Insert cell
import { input, usa } from "@jashkenas/inputs"
Insert cell
Sortable = require("sortablejs")
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more