simonw / datasette

An open source multi-tool for exploring and publishing data
https://datasette.io
Apache License 2.0
9.6k stars 690 forks source link

Autocomplete text entry for filter values that correspond to facets #1890

Closed fgregg closed 2 years ago

fgregg commented 2 years ago

datasette allows users to enter in the value for named parameters into a free-text form field.

I think it would add a lot of usability, if the form field could be a drop down of options when query value is already a faceted column.

simonw commented 2 years ago

Oh interesting... this doesn't even need to be attached to the visible faceting feature, necessarily: Datasette could try to detect when a column has a limited number of options (which the faceting code handles already) and could turn those into an auto-complete interface.

There's actually a native HTML element for this these days: the <datalist> https://developer.mozilla.org/en-US/docs/Web/HTML/Element/datalist

simonw commented 2 years ago

I tried this out on https://congress-legislators.datasettes.com/legislators/legislator_terms for the party column - here's the demo:

datalist

I made this work by dropping the following HTML into the page in the browser DevTools:

<datalist id="party">
<option value="Anti-Administration">
<option value="Pro-Administration">
<option value="Republican">
<option value="Federalist">
<option value="Democratic Republican">
<option value="Pro-administration">
<option value="Anti-administration">
<option value="Unknown">
<option value="Adams">
<option value="Jackson">
<option value="Jackson Republican">
<option value="Crawford Republican">
<option value="Whig">
<option value="Jacksonian Republican">
<option value="Jacksonian">
<option value="Anti-Jacksonian">
<option value="Adams Democrat">
<option value="Nullifier">
<option value="Anti Mason">
<option value="Anti Masonic">
<option value="Anti Jacksonian">
<option value="Democrat">
<option value="Anti Jackson">
<option value="Union Democrat">
<option value="Conservative">
<option value="Ind. Democrat">
<option value="Independent">
<option value="Law and Order">
<option value="American">
<option value="Liberty">
<option value="Free Soil">
<option value="Ind. Republican-Democrat">
<option value="Ind. Whig">
<option value="Unionist">
<option value="States Rights">
<option value="Anti-Lecompton Democrat">
<option value="Constitutional Unionist">
<option value="Independent Democrat">
<option value="Unconditional Unionist">
<option value="Conservative Republican">
<option value="Ind. Republican">
<option value="Liberal Republican">
<option value="National Greenbacker">
<option value="Readjuster Democrat">
<option value="Readjuster">
<option value="Union">
<option value="Union Labor">
<option value="Populist">
<option value="Silver Republican">
<option value="Free Silver">
<option value="Silver">
<option value="Democratic and Union Labor">
<option value="Progressive Republican">
<option value="Progressive">
<option value="Prohibitionist">
<option value="Socialist">
<option value="Farmer-Labor">
<option value="American Labor">
<option value="Nonpartisan">
<option value="Coalitionist">
<option value="Popular Democrat">
<option value="Liberal">
<option value="New Progressive">
<option value="Republican-Conservative">
<option value="Democrat-Liberal">
<option value="AL">
<option value="Libertarian">
</datalist>

And then adding list="party" to the input element in the filter form.

simonw commented 2 years ago

This could start out as a purely JavaScript enhancement for pages that already figured out the available values through faceting, like you suggested.

simonw commented 2 years ago

This finds the right links on the page:

document.querySelectorAll('.facet-results [data-column] li:not(.facet-truncated) a')
simonw commented 2 years ago

Here's a prototype:

function createDataLists() {
  var facetResults = document.querySelectorAll(".facet-results [data-column]");
  Array.from(facetResults).forEach(function (facetResult) {
    // Use link text from all links in the facet result
    var linkTexts = Array.from(
      facetResult.querySelectorAll("li:not(.facet-truncated) a")
    ).map(function (link) {
      return link.textContent;
    });
    // Create a datalist element
    var datalist = document.createElement("datalist");
    datalist.id = "datalist-" + facetResult.dataset.column;
    // Create an option element for each link text
    linkTexts.forEach(function (linkText) {
      var option = document.createElement("option");
      option.value = linkText;
      datalist.appendChild(option);
    });
    // Add the datalist to the facet result
    facetResult.appendChild(datalist);
  });
}
createDataLists();

// When any select with name=_filter_column changes, update the datalist
document.body.addEventListener("change", function (event) {
  if (event.target.name === "_filter_column") {
    event.target
      .closest(".filter-row")
      .querySelector(".filter-value")
      .setAttribute("list", "datalist-" + event.target.value);
  }
});
simonw commented 2 years ago

That prototype actually works really well! I'm going to add that to table.js.

simonw commented 2 years ago

Wrote a TIL about <datalist>: https://til.simonwillison.net/html/datalist

simonw commented 2 years ago

Demo now live here: https://congress-legislators.datasettes.com/legislators/legislator_terms?_facet=party - select party and start typing.

simonw commented 2 years ago

Spotted a bug with this on https://latest.datasette.io/fixtures/facetable?_facet=_city_id - the _city_id column is a foreign key, so you need to type 1 or 2 - but the autocomplete list shows the full text names for the cities.

simonw commented 2 years ago

Looks like I can fix that like so:

<datalist id="datalist-_city_id">
    <option label="San Francisco" value="1"></option>
    <option label="Los Angeles" value="2"></option>
    <option label="Detroit" value="3"></option>
    <option label="Memnonia" value="4"></option>
</datalist>
simonw commented 2 years ago

Annoying: Mobile Safari doesn't seem to support separate labels and values. I should probably disable this feature on that browser, at least for foreign key facets (for the moment).

simonw commented 2 years ago

Oops, introduced a test failure:

    def test_table_html_foreign_key_facets(app_client):
        response = app_client.get(
            "/fixtures/foreign_key_references?_facet=foreign_key_with_blank_label"
        )
        assert response.status == 200
>       assert (
            '<li><a href="http://localhost/fixtures/foreign_key_references?_facet=foreign_key_with_blank_label&amp;foreign_key_with_blank_label=3">'
            "-</a> 1</li>"
        ) in response.text
E       assert '<li><a href="http://localhost/fixtures/foreign_key_references?_facet=foreign_key_with_blank_label&amp;foreign_key_with_blank_label=3">-</a> 1</li>' in '<!DOCTYPE html>\n<html>\n<head>\n    <title>fixtures: foreign_key_references: 2 rows</title>\n    <link rel="styleshe.../script>\n\n\n<!-- Templates considered: table-fixtures-foreign_key_references.html, *table.html -->\n</body>\n</html>'
E        +  where '<!DOCTYPE html>\n<html>\n<head>\n    <title>fixtures: foreign_key_references: 2 rows</title>\n    <link rel="styleshe.../script>\n\n\n<!-- Templates considered: table-fixtures-foreign_key_references.html, *table.html -->\n</body>\n</html>' = <datasette.utils.testing.TestResponse object at 0x7fd1b0080640>.text

Need to fix this test:

https://github.com/simonw/datasette/blob/eac028d3f77aa5473a5fcf59240635a1bca80f7d/tests/test_table_html.py#L616-L624

simonw commented 2 years ago

Here's a polyfill for <datalist>: https://github.com/mfranzke/datalist-polyfill

It shouldn't be necessary now that Safari has shipped support (apparently added in https://developer.apple.com/documentation/safari-release-notes/safari-12_1-release-notes#3130314 Safari 12.1 in March 2019).

But it does look like Safari doesn't support differing label and value attributes, though documentation about this is hard to come by.

simonw commented 2 years ago

https://bugs.webkit.org/show_bug.cgi?id=201768 - " Datalist option's label not used" - marked as RESOLVED FIXED on March 31st 2020.

The commit: https://trac.webkit.org/changeset/259330/webkit

And here's the test mirrored on GitHub: https://cs.github.com/qtwebkit/webkit-mirror/blob/cc3fcd0b4bad1f7cf77c26e34aa01d16618d6d5e/LayoutTests/fast/forms/datalist/datalist-option-labels.html?q=datalist-option-labels.html

simonw commented 2 years ago

Actually this works as it should in desktop Safari:

autocomplete-safari

I'm going to just put up with the weird behaviour in Mobile Safari.

fgregg commented 2 years ago

amazing! thanks @simonw

tddschn commented 9 months ago

Demo now live here: https://congress-legislators.datasettes.com/legislators/legislator_terms?_facet=party - select party and start typing.

The demo works, but it doesn't seem to work for me on this one: https://congress-legislators.datasettes.com/legislators/legislator_terms?_facet=type&_facet=party&_facet=state&type=sen&party=Republican&_facet_size=max&state=NC&_sort_desc=start

And when I run datasette 0.64.6 locally, the autocompletion doesn't seem to work either.

Browser: Google Chrome Canary 123.0.6298.0 on macOS 14.3