Skip to content
onde

Add city autocomplete to a form

Show matching cities while a person types, and store the ID of the city that the person selects. The example uses a public key and plain JavaScript.

A free text field for the city gives you NYC, New York, and new york city for one city, and typos for many others. A city field with autocomplete gives you one ID for each city.

Create a public key

  1. On the dashboard, create a public key.
  2. Add the origin of your site, such as https://www.example.com.
  3. Add the origin of your local development server, such as http://localhost:4321.

Keys and origins gives the rules for origins.

Request cities while the person types

Send the text of the field to Autocomplete. Autocomplete returns cities with a name that starts with the text. If no city matches a text of 4 or more characters, autocomplete also accepts a name with one typo.

Each successful response costs one unit, so do not send a request for each key press:

  • Do not send a text of fewer than 2 characters. The API returns no cities for this text, and the response costs one unit.
  • Wait for a short pause in the typing before you send the request.
  • Cancel the previous request when the text changes, so that an old response does not replace a new one.
JavaScript
const AUTOCOMPLETE_URL = 'https://api.onde.earth/v1/cities/autocomplete'
const PUBLIC_KEY = 'onde_pk_...'
const MINIMUM_LENGTH = 2
const INPUT_DELAY_MS = 150

const input = form.elements.city
const cityId = form.elements.cityId
let timer
let controller

async function suggestCities(text, signal) {
  const url = new URL(AUTOCOMPLETE_URL)
  url.searchParams.set('q', text)
  url.searchParams.set('key', PUBLIC_KEY)
  const response = await fetch(url, { signal })
  const body = await response.json()
  if (!response.ok) {
    throw new Error(body.detail)
  }
  return body.data
}

input.addEventListener('input', () => {
  cityId.value = ''
  clearTimeout(timer)
  controller?.abort()
  const text = input.value.trim()
  if (text.length < MINIMUM_LENGTH) {
    showCities([])
    return
  }
  timer = setTimeout(async () => {
    controller = new AbortController()
    try {
      showCities(await suggestCities(text, controller.signal))
    } catch (error) {
      if (error.name !== 'AbortError') {
        showCities([])
      }
    }
  }, INPUT_DELAY_MS)
})

form is your form, with a text field city and a hidden field cityId. showCities is a function of your interface that shows the list. If a request fails, the list stays empty, and the person can type the city as text.

Each change of the text clears cityId. Without this step, a person who selects Paris and then types Berlin sends the ID of Paris.

Show each city

Many cities have the same name. Show the region and the country with each name, so that the person can find the correct city.

JavaScript
function cityLabel(city) {
  return [city.name, city.region?.name, city.country.name]
    .filter(Boolean)
    .join(', ')
}

region is null for a city with no region. A result can also be a district, such as Manhattan. A district has type district, and parentCityId is the ID of its city.

Show the list with the combobox pattern of the WAI-ARIA Authoring Practices, so that a person can select a city with the keyboard. A combobox component of your framework can show the list.

Store the selected city

When the person selects a city, put its ID in the hidden field.

JavaScript
function selectCity(city) {
  input.value = cityLabel(city)
  cityId.value = city.id
  showCities([])
}
HTML
<input type="hidden" name="cityId">

On the server, send the ID to Get a city with a secret key. If the status is not 200, the ID is not valid, so ask the person to select the city again. The response also gives all fields of the city. Store cities in your database tells which fields to keep.

Accept text with no selection

A person can submit the form with no selection, for example when the API refused a request. Then cityId is empty, and city holds the text.

On the server, send the text to Search. Accept the first result only if its match is exact or alias, and no other result has exact or alias. Otherwise, store the text and ask the person to select the city later. Match city names from a file gives the same rule.

Set the language and the countries

These parameters of autocomplete change the results:

  • language sets the language of the names. Use the language of the page.
  • country limits the results to the countries in the list, such as country=PT,ES. Use it if your service is only in some countries.
  • worldview sets the country of a city in a disputed area. Worldviews and disputed areas tells how to select a worldview.