Published
Edited
Feb 6, 2021
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
viewof nativeSlider = html`<input type="range" min="0" max="1" step="0.01">`
Insert cell
nativeSlider
Insert cell
updateButton = (slider) => {
const button = html`<button>Update</button>`
button.addEventListener("click", () => {
slider.value = Math.random();
slider.dispatchEvent(new CustomEvent("input"));
});
return button;
}
Insert cell
updateButton(viewof nativeSlider)
Insert cell
Insert cell
viewof fancySlider = slider({
title: "Fancy slider",
description: "As a combination of multiple HTML elements within a form"
})
Insert cell
fancySlider
Insert cell
updateButton(viewof fancySlider)
Insert cell
Insert cell
Insert cell
viewof anotherFancySlider = slider()
Insert cell
anotherFancySlider
Insert cell
{
const button = html`<button>Update</button>`;
button.addEventListener("click", () => {
// anotherFancySlider is actually a form, holding an input (i.a.)
viewof anotherFancySlider.input.value = Math.random();
viewof anotherFancySlider.dispatchEvent(new CustomEvent("input"));
});
return button;
}
Insert cell
Insert cell
viewof doublingSlider = {
const secondarySlider = happyCaseSlider({ value: viewof anotherFancySlider.value });
secondarySlider.addEventListener("input", (e) => {
// avoid endless loops
if (e.detail !== "ignore") {
viewof anotherFancySlider.input.value = secondarySlider.value;
viewof anotherFancySlider.dispatchEvent(new CustomEvent("input"));
}
});

viewof anotherFancySlider.addEventListener("input", () => {
secondarySlider.input.value = viewof anotherFancySlider.value;
secondarySlider.dispatchEvent(new CustomEvent("input", { detail: "ignore" }));
});
return secondarySlider;
}
Insert cell
doublingSlider
Insert cell
Insert cell
happyCaseSlider = () =>
happyCaseInput({
type: "range",
attributes: {
min: 0,
max: 1,
step: "any",
value: 0.5
},
getValue: input => Math.round(input.valueAsNumber * 100) / 100
})
Insert cell
function happyCaseInput(config) {
let {
type,
attributes,
getValue
} = config;

// named form elements are accessible as form.name
const form = html`
<form>
<input name="input" type="${type}" />
</form>`;
for (const [key, val] of Object.entries(attributes)) {
form.input.setAttribute(key, val);
}
// inline styles extracted (not feasible for an export-oriented solution)
form.append(html`<output name="output" class="value"></output>`);
const verb = "oninput";
form[verb] = e => {
const value = getValue(form.input);
form.output.value = value;
form.value = value;
};
form[verb]();
return form;
}
Insert cell
Insert cell
viewof lastSlider = customSlider({
title: "Happy case",
value: 0.75,
format: d => `${Math.round(d * 100)}%`,
description: "We are almost there"
})
Insert cell
lastSlider
Insert cell
updateButton(viewof lastSlider)
Insert cell
customSlider = (options = {}) => {
const {
title,
min = 0,
max = 1,
value = (min + max) / 2,
step = "any",
precision = 2,
format,
description
} = options;
const slider = html`<div class="slider"></div>`;
const id = Date.now();
if (title) {
// hovering the label highlights / clicking the label gives focus to the associated input
slider.append(html`<label for="${id}" class="title">${title}</label>`);
}
const input = html`<input id="${id}" type="range" min="${min}" max="${max}" value="${value}" step="${step}">`;
slider.append(input);
const output = html`<output class="value"></output>`;
slider.append(output);
if (description) {
slider.append(html`<small class="description">${description}</small>`);
}
// slider is the EventTarget for external cells to dispatch input events on
slider.addEventListener("input", (e) => {
const roundedValue = round(slider.value, precision);
slider.value = roundedValue;
input.value = roundedValue;
output.innerText = format ? format(roundedValue) : roundedValue;
});
input.addEventListener("input", () => {
slider.value = round(input.valueAsNumber, precision);
slider.dispatchEvent(new CustomEvent("input"));
});
// set initial value
input.dispatchEvent(new CustomEvent("input"));
return slider;
}
Insert cell
Insert cell
round = (number, precision) => {
const magicRatio = Math.pow(10, precision); // see: https://stackoverflow.com/a/11832950
return Math.round((number + Number.EPSILON) * magicRatio) / magicRatio;
}
Insert cell
round(0.1679477857769509, 2)
Insert cell
html`<style>
label.title {
display: block;
margin-bottom: 3px;
font: 700 0.9rem sans-serif;
}

output.value {
margin-left: 0.5em;
font: 14px Menlo, Consolas, monospace;
}

small.description {
display: block;
margin-top: 3px;
font-size: 0.85rem;
font-style: italic;
}
</style>`
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