I am trying to convert observablehq 'Scrubber' notebook into vanilla JS. My current code looks like this:
<!DOCTYPE html>
<meta charset="utf-8">
<title>Scrubber</title>
<link rel="stylesheet" type="text/css" href="./inspector.css">
<body>
<div class="test"></div>
<script>
dates = ["2018-12-31T23:00:00.000Z","2019-01-01T23:00:00.000Z"]
function Scrubber(container, values,
format = value => value,
initial = 0,
delay = null,
autoplay = true,
loop = true,
loopDelay = null,
alternate = false) {
values = Array.from(values);
const form = '<form style="font: 12px var(--sans-serif); font-variant-numeric: tabular-nums; display: flex; height: 33px; align-items: center;">\
<button name=b type=button style="margin-right: 0.4em; width: 5em;"></button>\
<label style="display: flex; align-items: center;">\
<input name=i type=range min=0 max=${values.length - 1} value=${initial} step=1 style="width: 180px;">\
<output name=o style="margin-left: 0.4em;"></output>\
</label>\
</form>';
let frame = null;
let timer = null;
let interval = null;
let direction = 1;
function start() {
form.b.textContent = "Pause";
if (delay === null) frame = requestAnimationFrame(tick);
else interval = setInterval(tick, delay);
}
function stop() {
form.b.textContent = "Play";
if (frame !== null) cancelAnimationFrame(frame), frame = null;
if (timer !== null) clearTimeout(timer), timer = null;
if (interval !== null) clearInterval(interval), interval = null;
}
function running() {
return frame !== null || timer !== null || interval !== null;
}
function tick() {
if (form.i.valueAsNumber === (direction > 0 ? values.length - 1 : direction < 0 ? 0 : NaN)) {
if (!loop) return stop();
if (alternate) direction = -direction;
if (loopDelay !== null) {
if (frame !== null) cancelAnimationFrame(frame), frame = null;
if (interval !== null) clearInterval(interval), interval = null;
timer = setTimeout(() => (step(), start()), loopDelay);
return;
}
}
if (delay === null) frame = requestAnimationFrame(tick);
step();
}
function step() {
form.i.valueAsNumber = (form.i.valueAsNumber + direction + values.length) % values.length;
form.i.dispatchEvent(new CustomEvent("input", {bubbles: true}));
}
form.i.oninput = event => {
if (event && event.isTrusted && running()) stop();
form.value = values[form.i.valueAsNumber];
form.o.value = format(form.value, form.i.valueAsNumber, values);
};
form.b.onclick = () => {
if (running()) return stop();
direction = alternate && form.i.valueAsNumber === values.length - 1 ? -1 : 1;
form.i.valueAsNumber = (form.i.valueAsNumber + direction) % values.length;
form.i.dispatchEvent(new CustomEvent("input", {bubbles: true}));
start();
};
form.i.oninput();
if (autoplay) start();
else stop();
Inputs.disposal(form).then(stop);
return form;
}
Scrubber(".test", dates)
</script>
</body>
But I get the following error : Uncaught TypeError: Cannot set properties of undefined (setting 'oninput') at Scrubber.
It seems that this should be a new function but how can I do that?
Thank you in advance for your help.
form was just a string, which was getting undefined. i appended the form string in DOM and accessed form node, maybe its gonna help you.
<!DOCTYPE html>
<meta charset="utf-8">
<title>Scrubber</title>
<link rel="stylesheet" type="text/css" href="./inspector.css">
<body>
<div class="test"></div>
<script>
dates = ["2018-12-31T23:00:00.000Z","2019-01-01T23:00:00.000Z"]
function Scrubber(container, values,
format = value => value,
initial = 0,
delay = null,
autoplay = true,
loop = true,
loopDelay = null,
alternate = false) {
values = Array.from(values);
const formString = '<form style="font: 12px var(--sans-serif); font-variant-numeric: tabular-nums; display: flex; height: 33px; align-items: center;">\
<button name=b type=button style="margin-right: 0.4em; width: 5em;"></button>\
<label style="display: flex; align-items: center;">\
<input name=i type=range min=0 max=${values.length - 1} value=${initial} step=1 style="width: 180px;">\
<output name=o style="margin-left: 0.4em;"></output>\
</label>\
</form>';
document.querySelector(container).innerHTML = formString;
const form = document.querySelector(container + ' form');
let frame = null;
let timer = null;
let interval = null;
let direction = 1;
function start() {
form.b.textContent = "Pause";
if (delay === null) frame = requestAnimationFrame(tick);
else interval = setInterval(tick, delay);
}
function stop() {
form.b.textContent = "Play";
if (frame !== null) cancelAnimationFrame(frame), frame = null;
if (timer !== null) clearTimeout(timer), timer = null;
if (interval !== null) clearInterval(interval), interval = null;
}
function running() {
return frame !== null || timer !== null || interval !== null;
}
function tick() {
if (form.i.valueAsNumber === (direction > 0 ? values.length - 1 : direction < 0 ? 0 : NaN)) {
if (!loop) return stop();
if (alternate) direction = -direction;
if (loopDelay !== null) {
if (frame !== null) cancelAnimationFrame(frame), frame = null;
if (interval !== null) clearInterval(interval), interval = null;
timer = setTimeout(() => (step(), start()), loopDelay);
return;
}
}
if (delay === null) frame = requestAnimationFrame(tick);
step();
}
function step() {
form.i.valueAsNumber = (form.i.valueAsNumber + direction + values.length) % values.length;
form.i.dispatchEvent(new CustomEvent("input", {bubbles: true}));
}
form.i.oninput = (event) => {
if (event && event.isTrusted && running()) stop();
form.value = values[form.i.valueAsNumber];
form.o.value = format(form.value, form.i.valueAsNumber, values);
};
form.b.onclick = () => {
if (running()) return stop();
direction = alternate && form.i.valueAsNumber === values.length - 1 ? -1 : 1;
form.i.valueAsNumber = (form.i.valueAsNumber + direction) % values.length;
form.i.dispatchEvent(new CustomEvent("input", {bubbles: true}));
start();
};
form.i.oninput();
if (autoplay) start();
else stop();
Inputs.disposal(form).then(stop);
return form;
}
Scrubber(".test", dates)
</script>
</body>
There are other issues with the code, but original issue is fixed and slider is working now.