Linked Slider & Select List

I can’t figure out how to link a slider and a select list so that they both provide a way of selecting an index of the same array. Any ideas?

I have this:

countryRangeSelect = html`<input type=range min=1 max=${data.length} value=${defaultCountryIndex} style="width:100%; max-width:640px;"/>`

And this:

countrySelect = {
  var countries = data.slice(0);;
  countries.sort(function(a, b) {
    var textA = a.Country.toUpperCase();
    var textB = b.Country.toUpperCase();
    return (textA < textB) ? -1 : (textA > textB) ? 1 : 0;
  });
  return html`<select>${countries.map((country, index) => `
  <option value="${index}" ${country.Rank == countryIndex+1 ? "selected" : ""}>${country.Country}</option>`)}
</select>`
}

And this:

countryIndex = Generators.input(countryRangeSelect)

One way to do it is to put both the input type=range and the select in the same cell, and then coordinate them through event listeners:

A more general way is to use a separate cell to store the (shared) value of your inputs, and then synchronize the inputs across cells using listeners:

1 Like

also see this:

not countrywide, but it has both select and input slider for you to zoom in… w/apache arrow arrays if you bold enough to go there

… talk about input slider, select, apache arrow js & deck.gl pwning it…

Top this map dataViz wiz shit! :slight_smile:

Awesome. Thanks guys. I’ve updated my page with the first solution:

But I can’t figure out why changing the select list multiple times seems to have a one event lag. It’s always returning the previous value. Bizarre. What am I doing wrong?

viewof countryIndex = {
  // Copy country data
  var countries = data.slice(0);
  // Make an alphabetical version
  countries.sort(function(a, b) {
    var textA = a.Country.toUpperCase();
    var textB = b.Country.toUpperCase();
    return (textA < textB) ? -1 : (textA > textB) ? 1 : 0;
  });
  const div = html`
    <select>${countries.map((country, index) => `
      <option value="${country.Rank}">${country.Country}</option>`)}
    </select><br/>
    <input type=range min=1 max=${data.length} style="width:100%; max-width:640px;"/>
  `;
  const range = div.querySelector("[type=range]");
  const select = div.querySelector("select");
  div.value = range.value = select.value = defaultCountryIndex;
  range.addEventListener("input", () => select.value = div.value = range.valueAsNumber);
  select.addEventListener("change", () => range.value = div.value = parseInt(select.value));
  return div;
}

Edit: Use @bgchen’s simpler solution!

It’s because only input events trigger the view to update, and SELECT elements (for whatever reason) use change events rather than input events. So you need to modify your change event listener to dispatch an input event, like so:

select.addEventListener("change", () => {
  range.value = div.value = parseInt(select.value);
  div.dispatchEvent(new CustomEvent("input"));
});

This isn’t necessary with the range input because it’ll bubble up automatically.

The following also seems to work; try changing this line:

select.addEventListener("change", () => range.value = div.value = parseInt(select.value));

to

select.addEventListener("input", () => range.value = div.value = parseInt(select.value));
2 Likes

Somewhat related, I made an input that could be used with viewOf for the specific case of a slider, a text box, and a few quick access buttons. It is here:

https://observablehq.com/@chonghorizons/slider-with-number-input-box-and-quick-select-buttons