accessible-autocomplete icon indicating copy to clipboard operation
accessible-autocomplete copied to clipboard

async results

Open jojo05 opened this issue 8 years ago • 25 comments

Is there a recommended pattern when using fetch to get the results ?

jojo05 avatar Jul 28 '17 06:07 jojo05

Hi @jojo05! You can use the source function and call the populateResults callback with the results from fetch.then. This should work.

We don't have built-in options for loading spinners or other useful things for dealing with asynchronous results! This is something we definitely need.

tvararu avatar Jul 28 '17 09:07 tvararu

Ok, this is what I do. It's annoying theat preact-cli doesn't support proxy config option, so I cannot develop using my Go server

What pattern would you use for the example case, when you have a script tag for preact and aa (accessible-autocomplete) ?

jojo05 avatar Jul 28 '17 09:07 jojo05

I'd try it like this:

<Autocomplete
  source={ (query, populateResults) => {
    fetch(URL + query)
      .then(data => populateResults(data))
  } }
/>

But this may not work! We don't have an example for this. Also, dealing with loading of async results isn't also possible at the moment.

tvararu avatar Jul 28 '17 09:07 tvararu

Also when users type in multiple times, you'll get multiple fetch calls which also isn't ideal. Can do a bit of work on your end with a debounce and fetch cancelling (unsure if possible? should be)

tvararu avatar Jul 28 '17 09:07 tvararu

fetch(URL+query).then(r => r.json()).then(data => populateResults(data)) works. Thanks

jojo05 avatar Jul 28 '17 09:07 jojo05

@jojo05 that's cool! Would you like to submit a pull request to the examples demo-ing this pattern? ✨

tvararu avatar Jul 28 '17 09:07 tvararu

Sure. That would be my first PR (it's about time!) It will be like examples/react/index.html but using an external data source, like wikipedia. I will also show the table-like output and a link (basically using aa to navigate a data source). Give me a few days

jojo05 avatar Jul 28 '17 10:07 jojo05

I suggest you make the "table-like output" separate, so two examples (or 3 with the link one)! It's good for the examples to be focused on just one small feature.

tvararu avatar Jul 28 '17 10:07 tvararu

yes, let me start with the first. I need to figure out the debouncing

jojo05 avatar Jul 28 '17 10:07 jojo05

Is it possible to have an option in which clicking on an item doesn't actually select an item? This is useful to view a set of values, click on some to open links in new tabs, and go back and still see the previous list.

jojo05 avatar Jul 28 '17 16:07 jojo05

Hi, I am ready to send the Pull Request. I don't want to fork the repo so I think I need push rights.

jojo05 avatar Aug 16 '17 16:08 jojo05

What's the status here, @jojo05 ? I'd love to get some input on your apporach for debouncing the keystrokes.

boneyfantaseas avatar Jan 25 '18 11:01 boneyfantaseas

Hi @jojo05,

Apologies for picking this up so late!

You'll need to fork the repo in order to raise a PR. More info here.

It would be good to get your PR in.

Any questions, please let us know.

Cheers, Hanna

hannalaakso avatar Feb 19 '18 14:02 hannalaakso

i am finding this because I'm also interested in debounce strategies!

jrochkind avatar Jan 09 '19 18:01 jrochkind

Just noting here that this works great for asynchronously fetching results:

autocomplete( {
    source: async ( query, populateResults ) => {
        const res = await fetchSource( query );
        populateResults( res );
    }
})

async function fetchSource( query ) {
    const res  = await fetch( `[url]?query=${encodeURIComponent( query )}` );
    const data = await res.json();
    return data;
}

Also, since this is a heavy operation, I'd suggest debouncing it like this:

autocomplete({
    source: debounce( async ( query, populateResults ) => {
	const res = await this.fetchSource( query );
	populateResults( res );
    }, 300 )
})

cjkoepke avatar Nov 04 '19 17:11 cjkoepke

Oh, that's a great way to do it! I wonder if it should actually be in the docs?

(Of course you've got to import debounce from somewhere, it's not built into the browser, and I agree is required for well functioning code here. And a version of the code that can work on older JS and not use async keyword (but use promise chaining instead probably) might be good too... JS has gotten really confusing).

jrochkind avatar Nov 04 '19 17:11 jrochkind

This is a good article talking about debounce functions that you can get a function from to use: https://web.archive.org/web/20190112051125/https://remysharp.com/2010/07/21/throttling-function-calls/

function debounce(fn, delay) {
  var timer = null;
  return function () {
    var context = this, args = arguments;
    clearTimeout(timer);
    timer = setTimeout(function () {
      fn.apply(context, args);
    }, delay);
  };
}

If you want to avoid async/await entirely from the above example you'd do something like:

autocomplete({
    source: debounce(function (query, populateResults) {
        var request = new XMLHttpRequest()
        request.open('GET', '/path/to/your/data/here', true)
        // Time to wait before giving up fetching the search index
        request.timeout = 10 * 1000
        console.log('Loading search index')
        request.onreadystatechange = function () {
          // XHR client readyState DONE
          if (request.readyState === 4) {
            if (request.status === 200) {
              var response = request.responseText
              var json = JSON.parse(response)
	      populateResults(json)
            } else {
              console.log('Failed to load the search index')
            }
          }
        }
        request.send()
    }, 300 )
})

This is similar to the approach we used on the GOV.UK Design System website's search: https://github.com/alphagov/govuk-design-system/blob/b52f17e5db38feb0f9b0b1412df9649c9335a84e/src/javascripts/components/search.js#L37

NickColley avatar Nov 04 '19 17:11 NickColley

@nickcolley @jrochkind Yeah, my example would definitely require a Webpack config of sorts, but Nick's example would be great for a docs example.

cjkoepke avatar Nov 04 '19 19:11 cjkoepke

Yep, any maintainers reading this? I'd submit a PR to add this example to the README if it might be looked on favorably.

Many other autocomplete possibilities have built-in support for async remote support. If people are looking around for re-usable JS for autocomplete (there aren't a whole lot of maintained options! Especially if you don't want to assuem react/vue/or other. Especially if you'd also rather not a JQuery dependency) -- clearly documenting how to use it for remote-async use case would probably help adoption.

(Alternately, would the maintaineres look favorably on a PR that includes the ~20 lines of code from @nickcolley to actually built remote/async (with debounce) in?)

jrochkind avatar Nov 04 '19 20:11 jrochkind

I definitely like the idea of building it in, but to be honest I'm not sure it's necessary. Plus, with the plethora of config options out there, just having an example might be enough.

cjkoepke avatar Nov 04 '19 20:11 cjkoepke

I work on the team that maintains this component just as a disclaimer.

I personally think that this component should stick to solving the problem of how to display results where possible, since the problem of fetching data async is not related specifically to this component.

If you fancy adding a simple example, you could here: https://github.com/alphagov/accessible-autocomplete#source

If the example gets too big you could consider linking to a bigger example in the https://github.com/alphagov/accessible-autocomplete/tree/master/examples folder

NickColley avatar Nov 04 '19 23:11 NickColley

I'm trying to work out an example.

When doing remote AJAX, it's possible for there to be an error condition; remote site returns non-200 error; network error; remote site returns unparseable content; etc.

It would be good to display this to the user somehow, rather than just leave them hanging waiting for results that will never come in.

Is there any public API already in accessible-autocomplete that can be used to display an error message or any kind of message? I know there are at least one if not several kinds of "messages" that can be displayed to user, like the "No results found" message, not sure if any of it has API that can be accessed by a source callback somehow. Should there be, for this use case?

jrochkind avatar Nov 05 '19 17:11 jrochkind

Sharing some work we did on a prototype. This isn't async/ajax - but simulates how it might work for the purposes of a prototype.

We're prototyping how 30k results might work / look. To do that @AbigailMcP used Lunr to build a client side search index for the autocomplete to search over. This is similar to how the design system search works.

PR here.

For prod we won't want it client side - but this has worked very well for usability testing. 30k is a little slow - but still usable for user research.

With thanks too to @kr8n3r for pointing us at the design system implementation.

edwardhorsford avatar Apr 01 '21 08:04 edwardhorsford

cc'ing @joesnellpdx since I'm not on this project anymore. Joe, hit me up on Twitter if you need context.

cjkoepke avatar Apr 05 '21 17:04 cjkoepke

There is a potential bug for using async right now, below is the steps to show the issue:

  1. Create accessible-autocomplete instance with an async source function
  2. Type a character and then delete the character really quickly

A live demo of this can be found at https://output.jsbin.com/werupanune You can view the code for the demo at https://jsbin.com/werupanune/edit?html,js,output

Accessible-autocomplete only calls the supplied source function when the user has enter a value into the text-input and not when the user has deleted all values from the text-input. Below is the part of the code which implements that logic: https://github.com/alphagov/accessible-autocomplete/blob/935f0d43aea1c606e6b38985e3fe7049ddbe98be/src/autocomplete.js#L226-L241

Because of logic to only call when there is text entered and not when the text has been deleted, there is a race-condition between the async source function and accessible-autocompletes own code to close the menu when the query is empty. Accessible-autocomplete will close the menu but the async source function, once it gets it's results, will call the callback to populate the menu with the results from the previous input/query the user had entered.

Below is a video showing the bug:

https://user-images.githubusercontent.com/1569131/119124363-7a679900-ba28-11eb-8028-ecf7ce89d7d3.mov

I think a solution to this bug would be to always call the supplied source function

JakeChampion avatar May 21 '21 10:05 JakeChampion