Public
Edited
May 23, 2023
Insert cell
Insert cell
selected
Insert cell
viewof selected = searchCheckbox(
data, // What elements to chose from
{// Any other options you pass here will be passed to the checkbox and search inputs
value: data, // Start with everything selected
label: "Variables",
height: 200
// You can also pass specific options for the search and the checkboxes
// optionsSearch: {
// filter: fullSearchFilter
// }
}
)
Insert cell
selected
Insert cell
data = ["ID","Name","Age","Photo","Nationality","Flag","Overall","Potential","Club","Club Logo","Value","Wage","Special","Preferred Foot","International Reputation","Weak Foot","Skill Moves","Work Rate","Body Type","Real Face","Position","Jersey Number","Joined","Loaned From","Contract Valid Until","Height","Weight","LS","ST","RS","LW","LF","CF","RF","RW","LAM","CAM","RAM","LM","LCM","CM","RCM","RM","LWB","LDM","CDM","RDM","RWB","LB","LCB","CB","RCB","RB","Crossing","Finishing","HeadingAccuracy","ShortPassing","Volleys","Dribbling","Curve","FKAccuracy","LongPassing","BallControl","Acceleration","SprintSpeed","Agility","Reactions","Balance","ShotPower","Jumping","Stamina","Strength","LongShots","Aggression","Interceptions","Positioning","Vision","Penalties","Composure","Marking","StandingTackle","SlidingTackle","GKDiving","GKHandling","GKKicking","GKPositioning","GKReflexes","Release Clause"]
Insert cell
Insert cell
Insert cell
function searchCheckbox(
data, // An array of possible selectable options
options
) {
options = {
value: [],
optionsCheckboxes: undefined, // use this if you want to pass specific options to the checkboxes or the search
optionsSearch: {
filter: fullSearchFilter // searches in the whole word
},
height: 300,
...options
};

// To remove the label from the options for the checkboxes
function cloneIgnoring(obj, attrToIgnore) {
const { [attrToIgnore]: _, ...rest } = obj;
return rest;
}
data = Array.from(data);
// options.value = options.value === undefined ? [] : options.value;
let checkboxes = Inputs.checkbox(
data,
options.optionsCheckboxes || cloneIgnoring(options, "label")
);
const search = Inputs.search(data, options.optionsSearch || options);
const btnAll = html`<button>All</button>`;
const btnNone = html`<button>None</button>`;

let selected = new Map(Array.from(options.value).map((d) => [d, true]));

function countSelected() {
return Array.from(selected.entries()).filter(([k, v]) => v).length;
}

function changeSome(sel, changeTo) {
for (let o of sel) selected.set(o, changeTo);
}

function selectedFromArray(sel) {
changeSome(data, false);
changeSome(sel, true);
}

function selectedToArray() {
return Array.from(selected.entries())
.filter(([k, v]) => v)
.map(([k, v]) => k);
}

// HTML
let output = htl.html`<output style="font-size: 80%; font-style: italics">(${countSelected()} of ${
data.length
} selected)</output>`;
const component = htl.html`${
options.label ? htl.html`<label>${options.label}</label>` : ""
}

${output}
<div style="display:flex">
${search}
<div style="margin: 0 5px"> ${btnAll} </div>
<div> ${btnNone} </div>
</div>
<div style="max-height: ${options.height}px; overflow: auto">${checkboxes}</div>`;

// Update the display whenever the value changes
Object.defineProperty(component, "value", {
get() {
return selectedToArray();
},
set(v) {
selectedFromArray(v);
}
});

function updateValueFromSelected() {
debugger;
checkboxes.value = selectedToArray();
output.innerHTML = `(${countSelected()} of ${data.length} selected)`;
component.dispatchEvent(new Event("input", { bubbles: true }));
}

btnAll.addEventListener("click", () => {
changeSome(search.value, true);
updateValueFromSelected();
});
btnNone.addEventListener("click", () => {
changeSome(search.value, false);
console.log("None", selectedToArray());
updateValueFromSelected();
});

component.value = selectedToArray();

search.addEventListener("input", (evt) => {
evt.stopPropagation();
// Hide all the checkboxes that aren't in the searchbox result
for (let check of checkboxes.querySelectorAll("input")) {
if (search.value.includes(data[+check.value])) {
check.parentElement.style.display = "inline-block";
} else {
check.parentElement.style.display = "none";
}
}
// We don't really need to update when value when searching
// component.dispatchEvent(new Event("input", { bubbles: true }));
});

checkboxes.addEventListener("input", (evt) => {
// avoids duplicated events
evt.stopPropagation();

selectedFromArray(checkboxes.value);
updateValueFromSelected();
});

return component;
}
Insert cell
Insert cell
function termFilter(term) {
return new RegExp(`(?:^.*|[^\\p{L}-])${escapeRegExp(term)}`, "iu");
}
Insert cell

function escapeRegExp(text) {
return text.replace(/[\\^$.*+?()[\]{}|]/g, "\\$&");
}
Insert cell
// https://github.com/observablehq/inputs/blob/main/src/search.js
function fullSearchFilter(query) {
const filters = `${query}`
.split(/\s+/g)
.filter((t) => t)
.map(termFilter);
return (d) => {
if (d == null) return false;
if (typeof d === "object") {
out: for (const filter of filters) {
for (const value of valuesof(d)) {
if (filter.test(value)) {
continue out;
}
}
return false;
}
} else {
for (const filter of filters) {
if (!filter.test(d)) {
return false;
}
}
}
return true;
};
}
Insert cell
function* valuesof(d) {
for (const key in d) {
yield d[key];
}
}
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