Create an address autocomplete field tutorial

Geoapify Geocoder API has a dedicated end-point for address autocomplete calls. In this tutorial, we would like to show you how to create an Address Autocomplete field step by step with Vanilla JavaScript. In each step, we will provide parts of the code that added. The full example code you can find on JSFiddle. You need to register and get Geoapify API key to reuse the code in your projects.

Step 1. Add an input to the provided container

Let's develop a function addressAutocomplete() that adds an address autocomplete field into the provided container element (DIV). Note, the container element must have position: relative; (or position: absolute;) to behave correctly. The input created will take the full width of the provided container.

HTML
<div class="autocomplete-container" id="autocomplete-container"></div>
CSS
.autocomplete-container {
  /*the container must be positioned relative:*/
  position: relative;
}

.autocomplete-container input {
  width: calc(100% - 43px);
  outline: none;
  
  border: 1px solid rgba(0, 0, 0, 0.2);
  padding: 10px;
  padding-right: 31px;
  font-size: 16px;
}
JavaScript
/* 
    The addressAutocomplete takes a container element (div) as a parameter
*/
function addressAutocomplete(containerElement) {
  // create input element
  var inputElement = document.createElement("input");
  inputElement.setAttribute("type", "text");
  inputElement.setAttribute("placeholder", "Enter an address here");
  containerElement.appendChild(inputElement);
}

addressAutocomplete(document.getElementById("autocomplete-container"));

Step 2. Add user input event and send a geocoding request on user input

As we will receive the geocoding results asynchronously, we need to take care of non-relevant requests. Let's use Promises for that. We store a reject function in a variable currentPromiseReject and reject the promise before a new request.

JavaScript
function addressAutocomplete(containerElement) {
  ...

  /* Active request promise reject function. To be able to cancel the promise when a new request comes */
  var currentPromiseReject;

  /* Execute a function when someone writes in the text field: */
  inputElement.addEventListener("input", function(e) {
    var currentValue = this.value;

    // Cancel previous request promise
    if (currentPromiseReject) {
      currentPromiseReject({
        canceled: true
      });
    }

    if (!currentValue) {
      return false;
    }

    /* Create a new promise and send geocoding request */
    var promise = new Promise((resolve, reject) => {
      currentPromiseReject = reject;

      var apiKey = "47f523a46b944b47862e39509a7833a9";
      var url = `https://api.geoapify.com/v1/geocode/autocomplete?text=${encodeURIComponent(currentValue)}&limit=5&apiKey=${apiKey}`;

      fetch(url)
        .then(response => {
          // check if the call was successful
          if (response.ok) {
            response.json().then(data => resolve(data));
          } else {
            response.json().then(data => reject(data));
          }
        });
    });

    promise.then((data) => {
        // we will process data here
    }, (err) => {
      if (!err.canceled) {
        console.log(err);
      }
    });
  });
}

Step 3. Show geocoding results in a dropdown list

Geoapify Geocoding API returns results in GeoJSON format. The each GeoJSON Feature contains a location information in properties. Learn more about the Geocoding API properties on the API documentation page.

We going to append the dropdown list element to the provided container. We show the list when we get new results. We close the list when we get a new user input.

css
.autocomplete-items {
  position: absolute;
  border: 1px solid rgba(0, 0, 0, 0.1);
  box-shadow: 0px 2px 10px 2px rgba(0, 0, 0, 0.1);
  border-top: none;
  background-color: #fff;

  z-index: 99;
  top: calc(100% + 2px);
  left: 0;
  right: 0;
}

.autocomplete-items div {
  padding: 10px;
  cursor: pointer;
}

.autocomplete-items div:hover {
  /*when hovering an item:*/
  background-color: rgba(0, 0, 0, 0.1);
}
JavaScript
function addressAutocomplete(containerElement) {
  ...

  /* Current autocomplete items data (GeoJSON.Feature) */
  var currentItems;

  /* Execute a function when someone writes in the text field: */
  inputElement.addEventListener("input", function(e) {

    /* Close any already open dropdown list */
    closeDropDownList();

    ...

    /* Create a new promise and send geocoding request */
    var promise = new Promise((resolve, reject) => {...});

    promise.then((data) => {
      currentItems = data.features;

      /*create a DIV element that will contain the items (values):*/
      var autocompleteItemsElement = document.createElement("div");
      autocompleteItemsElement.setAttribute("class", "autocomplete-items");
      containerElement.appendChild(autocompleteItemsElement);

      /* For each item in the results */
      data.features.forEach((feature, index) => {
        /* Create a DIV element for each element: */
        var itemElement = document.createElement("DIV");
        /* Set formatted address as item value */
        itemElement.innerHTML = feature.properties.formatted;
        autocompleteItemsElement.appendChild(itemElement);
      });
    }, (err) => {...}
    });
  });

  function closeDropDownList() {
    var autocompleteItemsElement = containerElement.querySelector(".autocomplete-items");
    if (autocompleteItemsElement) {
      containerElement.removeChild(autocompleteItemsElement);
    }
  }
}

Step 4. Select an option and notify about selection

We will make options clickable and send notifications when an option was selected. We will provide callback function to the addressAutocomplete() to make callbacks.

JavaScript
/* 
    The addressAutocomplete takes as parameters:
  - a container element (div)
  - callback to notify about address selection
*/
function addressAutocomplete(containerElement, callback) {
  ...

  inputElement.addEventListener("input", function(e) {
    ...

    /* Create a new promise and send geocoding request */
    var promise = new Promise((resolve, reject) => {...});

    promise.then((data) => {
      currentItems = data.features;
      ...
      /* For each item in the results */
      data.features.forEach((feature, index) => {
        /* Create a DIV element for each element: */
        var itemElement = document.createElement("DIV");
        /* Set formatted address as item value */
        itemElement.innerHTML = feature.properties.formatted;

        /* Set the value for the autocomplete text field and notify: */
        itemElement.addEventListener("click", function(e) {
          inputElement.value = currentItems[index].properties.formatted;
          callback(currentItems[index]);
          /* Close the list of autocompleted values: */
          closeDropDownList();
        });

        autocompleteItemsElement.appendChild(itemElement);
      });
    }, (err) => {...});
  });
  ...
}

addressAutocomplete(document.getElementById("autocomplete-container"), (data) => {
  console.log("Selected option: ");
  console.log(data);
});

Step 5. Add clear button

The new Geocoding Autocomplete is fully functional now, but now user-friendly enough. Let's make a few improvements to make it more convenient. We add a clear button to the input element, that allows removing a text.

css
.clear-button {
  color: rgba(0, 0, 0, 0.4);
  cursor: pointer;
  
  position: absolute;
  right: 5px;
  top: 0;

  height: 100%;
  display: none;
  align-items: center;
}

.clear-button.visible {
  display: flex;
}

.clear-button:hover {
  color: rgba(0, 0, 0, 0.6);
}
JavaScript
function addressAutocomplete(containerElement, callback) {
  ...
  // add input field clear button
  var clearButton = document.createElement("div");
  clearButton.classList.add("clear-button");
  addIcon(clearButton);
  clearButton.addEventListener("click", (e) => {
    e.stopPropagation();
    inputElement.value = '';
    callback(null);
    clearButton.classList.remove("visible");
    closeDropDownList();
  });
  inputElement.parentNode.appendChild(clearButton);

  /* Execute a function when someone writes in the text field: */
  inputElement.addEventListener("input", function(e) {
    ...

    if (!currentValue) {
      clearButton.classList.remove("visible");
      return false;
    }

    // Show clearButton when there is a text
    clearButton.classList.add("visible");
    ...
  });

  function addIcon(buttonElement) {
    var svgElement = document.createElementNS("http://www.w3.org/2000/svg", 'svg');
    svgElement.setAttribute('viewBox', "0 0 24 24");
    svgElement.setAttribute('height', "24");

    var iconElement = document.createElementNS("http://www.w3.org/2000/svg", 'path');
    iconElement.setAttribute("d", "M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z");
    iconElement.setAttribute('fill', 'currentColor');
    svgElement.appendChild(iconElement);
    buttonElement.appendChild(svgElement);
  }
}

Step 6. Add keyboard navigation

We add a listener for arrow UP, arrow DOWN and ENTER key:

  • We accept a geocoding result and send a notification when a user presses the arrows. To highlight the active element we store the appropriate index in focusedItemIndex.
  • We close the dropdown list when user presses enter.
  • We open the dropdown list again when the dropdown list is not there and the user presses arrow DOWN.
CSS
.autocomplete-items .autocomplete-active {
  /*when navigating through the items using the arrow keys:*/
  background-color: rgba(0, 0, 0, 0.1);
}
JavaScript
function addressAutocomplete(containerElement, callback) {
  ...
  /* Execute a function when someone writes in the text field: */
  inputElement.addEventListener("input", function(e) {...});

  /* Add support for keyboard navigation */
  inputElement.addEventListener("keydown", function(e) {
    var autocompleteItemsElement = containerElement.querySelector(".autocomplete-items");
    if (autocompleteItemsElement) {
      var itemElements = autocompleteItemsElement.getElementsByTagName("div");
      if (e.keyCode == 40) {
        e.preventDefault();
        /*If the arrow DOWN key is pressed, increase the focusedItemIndex variable:*/
        focusedItemIndex = focusedItemIndex !== itemElements.length - 1 ? focusedItemIndex + 1 : 0;
        /*and and make the current item more visible:*/
        setActive(itemElements, focusedItemIndex);
      } else if (e.keyCode == 38) {
        e.preventDefault();

        /*If the arrow UP key is pressed, decrease the focusedItemIndex variable:*/
        focusedItemIndex = focusedItemIndex !== 0 ? focusedItemIndex - 1 : focusedItemIndex = (itemElements.length - 1);
        /*and and make the current item more visible:*/
        setActive(itemElements, focusedItemIndex);
      } else if (e.keyCode == 13) {
        /* If the ENTER key is pressed and value as selected, close the list*/
        e.preventDefault();
        if (focusedItemIndex > -1) {
          closeDropDownList();
        }
      }
    } else {
      if (e.keyCode == 40) {
        /* Open dropdown list again */
        var event = document.createEvent('Event');
        event.initEvent('input', true, true);
        inputElement.dispatchEvent(event);
      }
    }
  });

  function setActive(items, index) {
    if (!items || !items.length) return false;

    for (var i = 0; i < items.length; i++) {
      items[i].classList.remove("autocomplete-active");
    }

    /* Add class "autocomplete-active" to the active element*/
    items[index].classList.add("autocomplete-active");

    // Change input value and notify
    inputElement.value = currentItems[index].properties.formatted;
    callback(currentItems[index]);
  }

  function closeDropDownList() {
    ...
    focusedItemIndex = -1;
  }
}

Step 7. Make the dropdown list responsive

At the moment the dropdown is opened when a user types in the input field and closed only when an option was selected. Let’s close the dropdown list when a user clicked outside the input control and show the dropdown list again when the control is clicked again.

JavaScript
function addressAutocomplete(containerElement, callback) {
  ...

  /* Close the autocomplete dropdown when the document is clicked. 
      Skip, when a user clicks on the input field */
  document.addEventListener("click", function(e) {
    if (e.target !== inputElement) {
      closeDropDownList();
    } else if (!containerElement.querySelector(".autocomplete-items")) {
      // open dropdown list again
      var event = document.createEvent('Event');
      event.initEvent('input', true, true);
      inputElement.dispatchEvent(event);
    }
  });
}

Step 8. Search countries, cities, streets

Geoapify Geocoding API allows specifying a type for locations - countries, cities, states, streets and other. Let’s add a parameter for the addressAutocomplete() that limits the search to the specific location type.

HTML
<div class="autocomplete-container" id="autocomplete-container"></div>
<div class="autocomplete-container" id="autocomplete-container-country"></div>
<div class="autocomplete-container" id="autocomplete-container-city"></div>
JavaScript
/* 
    The addressAutocomplete takes as parameters:
  - a container element (div)
  - callback to notify about address selection
  - geocoder options:
       - placeholder - placeholder text for an input element
     - type - location type
*/
function addressAutocomplete(containerElement, callback, options) {
  // create input element
  var inputElement = document.createElement("input");
  inputElement.setAttribute("type", "text");
  inputElement.setAttribute("placeholder", options.placeholder);
  containerElement.appendChild(inputElement);

  ...

  /* Execute a function when someone writes in the text field: */
  inputElement.addEventListener("input", function(e) {
    ...
    /* Create a new promise and send geocoding request */
    var promise = new Promise((resolve, reject) => {
      currentPromiseReject = reject;

      var apiKey = "47f523a46b944b47862e39509a7833a9";
      var url = `https://api.geoapify.com/v1/geocode/autocomplete?text=${encodeURIComponent(currentValue)}&limit=5&apiKey=${apiKey}`;
      
      if (options.type) {
          url += `&type=${options.type}`;
      }

      fetch(url)
        .then(response => {...});
    });

    promise.then((data) => {...}, (err) => {...});
  });

  ...
}

addressAutocomplete(document.getElementById("autocomplete-container"), (data) => {
  console.log("Selected option: ");
  console.log(data);
}, {
    placeholder: "Enter an address here"
});

addressAutocomplete(document.getElementById("autocomplete-container-country"), (data) => {
  console.log("Selected country: ");
  console.log(data);
}, {
    placeholder: "Enter a country name here",
  type: "country"
});

addressAutocomplete(document.getElementById("autocomplete-container-city"), (data) => {
  console.log("Selected city: ");
  console.log(data);
}, {
    placeholder: "Enter a city name here"
});

More Geocoding API options

Geoapify Geocoding API provides options that allow you to make the search more concrete and get accurate results:

  • Search by exact address type. For example, search only countries, cities or streets.
  • Limit the search to a list of counties.
  • Search around a given location.
  • Change the language of results. Learn more about Geoapify Geocoding API on the API documentation page.