async results
Is there a recommended pattern when using fetch to get the results ?
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.
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) ?
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.
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)
fetch(URL+query).then(r => r.json()).then(data => populateResults(data)) works. Thanks
@jojo05 that's cool! Would you like to submit a pull request to the examples demo-ing this pattern? ✨
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
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.
yes, let me start with the first. I need to figure out the debouncing
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.
Hi, I am ready to send the Pull Request. I don't want to fork the repo so I think I need push rights.
What's the status here, @jojo05 ? I'd love to get some input on your apporach for debouncing the keystrokes.
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
i am finding this because I'm also interested in debounce strategies!
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 )
})
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).
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 @jrochkind Yeah, my example would definitely require a Webpack config of sorts, but Nick's example would be great for a docs example.
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?)
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.
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
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?
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.
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.
cc'ing @joesnellpdx since I'm not on this project anymore. Joe, hit me up on Twitter if you need context.
There is a potential bug for using async right now, below is the steps to show the issue:
- Create accessible-autocomplete instance with an async source function
- 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