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

One platform to build and deploy the best data apps

Experiment and prototype by building visualizations in live JavaScript notebooks. Collaborate with your team and decide which concepts to build out.
Use Observable Framework to build data apps locally. Use data loaders to build in any language or library, including Python, SQL, and R.
Seamlessly deploy to Observable. Test before you ship, use automatic deploy-on-commit, and ensure your projects are always up-to-date.
Learn more