Populating an autocomplete textbox
Problem
I want to populate an autocomplete textbox with values from a collection. The completions should adjust dynamically based on user input.
Solution
Use a web framework for the client-side autocomplete rendering and event processing. Use a collection with a (sorted) skiplist index and a range query on it to efficiently fetch the completion values dynamically. Connect the two using a simple Foxx route.
Install an example app
This app contains a jquery-powered web page with an autocomplete textbox. It uses jquery autocomplete, but every other web framework will also do.
The app can be installed as follows:
- in the ArangoDB web interface, switch into the Applications tab
- there, click Add Application
- switch on the Github tab
- for Repository, enter
jsteemann/autocomplete
- for Version, enter
master
- click Install
Now enter a mountpoint for the application. This is the URL path under which the
application will become available. For the example app, the mountpoint does not matter.
The web page in the example app assumes it is served by ArangoDB, too. So it uses a
relative URL autocomplete
. This is easiest to set up, but in reality you might want
to have your web page served by a different server. In this case, your web page will
have to call the app mountpoint you just entered.
To see the example app in action, click on Open. The autocomplete textbox should be populated with server data when at least two letters are entered.
Backend code, setup script
The app also contains a backend route /autocomplete
which is called by the web page to
fetch completions based on user input. The HTML code for the web page is
here.
Contained in the app is a setup script
that will create a collection named completions
and load some initial data into it. The
example app provides autocompletion for US city names, and the setup script populates the
collection with about 10K city names.
The setup script also creates a skiplist index on the lookup attribute,
so this attribute can be used for efficient filtering and sorting later.
The lookup
attribute contains the city names already lower-cased, and the original
(pretty) names are stored in attribute pretty
. This attribute will be returned to
users.
Backend code, Foxx route controller
The app contains a controller.
The backend action /autocomplete
that is called by the web page is also contained herein:
controller.get("/autocomplete", function (req, res) {
// search phrase entered by user
var searchString = req.params("q").trim() || "";
// lower bound for search range
var begin = searchString.replace(/[^a-zA-Z]/g, " ").toLowerCase();
if (begin.length === 0) {
// search phrase is empty - no need to perfom a search at all
res.json([]);
return;
}
// upper bound for search range
var end = begin.substr(0, begin.length - 1) + String.fromCharCode(begin.charCodeAt(begin.length - 1) + 1);
// bind parameters for query
var queryParams = {
"@collection" : "completions",
"begin" : begin,
"end" : end
};
// the search query
var query = "FOR doc IN @@collection FILTER doc.lookup >= @begin && doc.lookup < @end SORT doc.lookup RETURN { label: doc.pretty, value: doc.pretty, id: doc._key }";
res.json(db._query(query, queryParams).toArray());
}
The backend code first fetches the search string from the URL parameter q
. This is what the
web page will send us.
Based on the search string, a lookup range is calculated. First of all, the search string is lower-cased and all non-letter characters are removed from it. The resulting string is the lower bound for the lookup. For the upper bound, we can use the lower bound with its last letter character code increased by one.
For example, if the user entered Los A
into the textbox, the web page will send us the string
Los A
in URL parameter q
. Lower-casing and removing non-letter characters from the string,
we'll get losa
. This is the lower bound. The upper bound is losa
, with its last letter adjusted
to b
(i.e. losb
).
Finally, the lower and upper bounds are inserted into the following query using bind parameters
@begin
and @end
:
FOR doc IN @@collection
FILTER doc.lookup >= @begin && doc.lookup < @end
SORT doc.lookup
RETURN {
label: doc.pretty,
value: doc.pretty,
id: doc._key
}
The city names in the lookup range will be returned sorted. For each city, three values are
returned (the id
contains the document key, the other two values are for display purposes).
Other frameworks may require a different return format, but that can easily be done by
adjusting the AQL query.
Author: Jan Steemann
Tags: #aql #autocomplete #jquery