Woosmap for What3Words

Convert a What3Words address to a street address and pick a suggested location

  1. Example
  2. Running the Sample Locally

Example

Woosmap for What3Words
        let selectedAddress: HTMLElement | null;
let addressDetailsContainer: HTMLElement;
let addressListContainer: HTMLElement;
let addressList: HTMLElement;
let subBuildingListContainer: HTMLElement;
let subBuildingList: HTMLElement;
let allResultsContainer: HTMLElement;
let resultsContainer: HTMLElement;
let autoSuggestResultsTitle: HTMLElement;
let results: HTMLElement;
let inputElement: HTMLInputElement;
let detailsHTML: HTMLElement;
let clearSearchBtn: HTMLButtonElement;
let map: woosmap.map.Map;
let marker: woosmap.map.Marker;
let debouncedAutosuggestW3W: (
  ...args: any[]
) => Promise<What3WordsSuggestionsResponse>;

const API_KEY = "YOUR_API_KEY"; // Replace YOUR_API_KEY with your actual API key

interface What3WordsSuggestionsResponse {
  suggestions: What3WordsSuggestion[];
}

interface What3WordsSuggestion {
  country: string;
  distanceToFocusKm: number | null;
  language: string;
  nearestPlace: string;
  rank: number;
  words: string;
}

interface What3WordsAddressesResponse {
  results: What3WordsAddress[];
}

interface What3WordsAddress {
  description: string;
  public_id: string;
  status: string | null;
  sub_buildings:
    | {
        description: string;
        public_id: string;
      }[]
    | null;
  types: string[];
}

function convertToAddress(words: string): Promise<What3WordsAddressesResponse> {
  return fetch(
    `https://api.woosmap.com/what3words/convert-to-address?key=${API_KEY}&words=${words}`,
  ).then((response) => response.json());
}


function getLocalitiesDetails(
  publicId: string,
): Promise<woosmap.map.localities.LocalitiesDetailsResponse> {
  return fetch(
    `https://api.woosmap.com/localities/details/?key=${API_KEY}&public_id=${publicId}`,
  ).then((response) => response.json());
}


function autosuggestW3W(input: string): Promise<What3WordsSuggestionsResponse> {
  return fetch(
    `https://api.woosmap.com/what3words/autosuggest?key=${API_KEY}&input=${input}`,
  ).then((response) => response.json());
}


function clearSection(section: HTMLElement): void {
  section.innerHTML = "";
}

function hideSection(section: HTMLElement): void {
  section.style.display = "none";
}

function displaySection(section: HTMLElement, mode = "block"): void {
  section.style.display = mode;
}

function setSelectedAddress(selectedElement: HTMLElement): void {
  if (selectedAddress) {
    selectedAddress.classList.remove("selected");
  }
  selectedAddress = selectedElement;
  selectedAddress.classList.add("selected");
}

function panMap(addressDetail) {
  if (addressDetail.geometry.viewport) {
    const { viewport } = addressDetail.geometry;
    const bounds = {
      east: viewport.northeast.lng,
      south: viewport.southwest.lat,
      north: viewport.northeast.lat,
      west: viewport.southwest.lng,
    };
    map.fitBounds(bounds);
    map.panTo(addressDetail.geometry.location);
  } else {
    let zoom = 17;
    if (addressDetail.types[0] === "address") {
      zoom = 18;
    }
    map.setZoom(zoom);
    map.panTo(addressDetail.geometry.location);
  }
}

function createAddressMarker(addressDetail) {
  if (marker) {
    marker.setMap(null);
  }
  marker = new woosmap.map.Marker({
    position: addressDetail.geometry.location,
    icon: {
      url: "https://images.woosmap.com/marker-alt.png",
      scaledSize: {
        height: 59,
        width: 37,
      },
    },
  });
  marker.setMap(map);
  panMap(addressDetail);
}

function fillAddressDetails(
  addressDetails: woosmap.map.localities.LocalitiesDetailsResult,
) {
  const details: string[] = [];
  if (addressDetails.formatted_address) {
    details.push(
      `<p class='option-detail'><span class='option-detail-label'>Formatted_address :</span><span class='bold'>${addressDetails.formatted_address}</span></p>`,
    );
  }
  if (addressDetails.types && addressDetails.types[0]) {
    details.push(
      `<p class='option-detail'><span class='option-detail-label'>Type : </span><span class='bold'>${addressDetails.types[0].replace("_", " ")}</span></p>`,
    );
  }
  if (addressDetails.geometry) {
    details.push(
      `<div class='option-detail'><div><span class='option-detail-label'>Latitude :</span> <span class='bold'>${addressDetails.geometry.location.lat.toString()}</span></div><div><span class='option-detail-label'>Longitude : </span><span class='bold'>${addressDetails.geometry.location.lng.toString()}</span></div></div>`,
    );
    if (addressDetails.address_components) {
      const compoHtml = addressDetails.address_components
        .map(
          (compo) =>
            `<p class='option-detail'><span class='option-detail-label'>${compo.types[0]}:</span> <span class='bold'>${compo.long_name}</span></p>`,
        )
        .join("");
      details.push(
        `<div class='address-components'><div class='title'>Address components</div><div>${compoHtml}</div>`,
      );
    }
  }
  detailsHTML.innerHTML = details.join("");
}


function getAddressDetails(target, publicId) {
  setSelectedAddress(target);
  getLocalitiesDetails(publicId)
    .then((detailResponse) => {
      const addressDetails = detailResponse.result;
      if (addressDetails) {
        createAddressMarker(addressDetails);
        fillAddressDetails(addressDetails);
        displaySection(addressDetailsContainer);
      }
    })
    .catch((error) => {
      console.error(error);
    });
}

function displaySubBuildings(target: HTMLElement, subBuildings) {
  setSelectedAddress(target);
  hideSection(addressListContainer);
  clearSection(subBuildingList);
  displaySection(subBuildingListContainer);
  displaySection(autoSuggestResultsTitle);
  const addressList = document.createElement("ul");

  subBuildings.forEach((address, index) => {
    const line = document.createElement("li");
    line.className = "address";
    line.addEventListener("click", (event) => {
      getAddressDetails(line, address.public_id);
    });
    line.innerHTML = `<span class='pin'></span><span>${address.description}</span>`;
    addressList.appendChild(line);
  });

  subBuildingList.appendChild(addressList);
}

function backToAddressList() {
  hideSection(autoSuggestResultsTitle);
  hideSection(subBuildingListContainer);
  hideSection(addressDetailsContainer);
  displaySection(addressListContainer);
}

function displayAddressList(addressDetails) {
  backToAddressList();
  const addressListContainer = addressList;
  const newAddressList = document.createElement("ul");
  const fragment = document.createDocumentFragment();

  if (!Array.isArray(addressDetails) || addressDetails.length === 0) {
    const line = document.createElement("li");
    line.className = "not-found";
    line.textContent =
      "what3words address invalid or no street address found within 200m of its location.";
    newAddressList.appendChild(line);
  } else {
    addressDetails.forEach((address, index) => {
      const line = document.createElement("li");
      line.className = "address";
      line.addEventListener("click", (event) => {
        if (address.public_id) {
          getAddressDetails(line, address.public_id);
        } else {
          displaySubBuildings(line, address.sub_buildings);
        }
      });
      line.innerHTML = `<span class='${
        address.public_id ? "pin" : "arrow"
      }'></span><span>${address.description}</span>`;
      fragment.appendChild(line);
    });
  }
  newAddressList.appendChild(fragment);
  addressListContainer.appendChild(newAddressList);
}

function getPossibleAddress(words: string) {
  hideSection(resultsContainer);
  displaySection(addressListContainer);
  hideSection(addressDetailsContainer);
  clearSection(addressList);
  convertToAddress(words)
    .then(({ results }) => {
      if (results) {
        displayAddressList(results);
      }
    })
    .catch((error) => {
      console.error(error);
    });
}

function w3wClickCallback(suggestion: What3WordsSuggestion): void {
  (document.querySelector(".words") as HTMLElement).innerHTML =
    suggestion.words;
  (document.querySelector(".nearest") as HTMLElement).innerHTML =
    `Nearest place : ${suggestion.nearestPlace}`;
  (inputElement as HTMLInputElement).value = `///${suggestion.words}`;
  getPossibleAddress(suggestion.words);
}

function displayW3wSuggestion() {
  const value = inputElement.value;
  if (value) {
    displaySection(clearSearchBtn);
  }
  value.replace('"', '\\"').replace(/^\s+|\s+$/g, "");
  hideSection(autoSuggestResultsTitle);
  if ((value.match(/[.]/g) || []).length !== 2) {
    hideSection(resultsContainer);
    hideSection(addressListContainer);
    hideSection(subBuildingListContainer);
    return;
  }

  displaySection(allResultsContainer);
  hideSection(addressListContainer);
  hideSection(subBuildingListContainer);
  hideSection(addressDetailsContainer);

  debouncedAutosuggestW3W(value)
    .then(({ suggestions }) => {
      clearSection(results);
      const list = document.createElement("ul");
      if (suggestions) {
        suggestions.forEach((suggestion) => {
          const item = document.createElement("li");
          item.className = "suggestion";
          item.id = suggestion.rank.toString();
          item.innerHTML = `<div class="words">${suggestion.words}</div><div class="nearest">Nearest place : ${suggestion.nearestPlace}</div>`;
          item.dataset.word = suggestion.words;
          item.onclick = () => {
            w3wClickCallback(suggestion);
          };
          list.appendChild(item);
        });
        results.appendChild(list);
      } else {
        const failed = document.createElement("div");
        failed.innerHTML = "Failed to load suggestions";
        results.appendChild(failed);
      }

      displaySection(resultsContainer, "flex");
    })
    .catch((error) => {
      console.error(error);
    });
}

function initMap() {
  map = new woosmap.map.Map(document.getElementById("map") as HTMLElement, {
    center: {
      lat: 48.8534,
      lng: 2.3488,
    },
    disableDefaultUI: true,
    gestureHandling: "greedy",
    zoom: 5,
    styles: [
      {
        featureType: "poi",
        stylers: [{ visibility: "off" }],
      },
    ],
  });
  debouncedAutosuggestW3W = debouncePromise(autosuggestW3W, 0);
  initUI();
}

function resetUI() {
  hideSection(resultsContainer);
  hideSection(addressListContainer);
  hideSection(subBuildingListContainer);
  hideSection(addressDetailsContainer);
  hideSection(clearSearchBtn);
  hideSection(allResultsContainer);
  clearSection(addressList);
  clearSection(subBuildingList);
  clearSection(detailsHTML);
  inputElement.value = "";
  if (marker) {
    marker.setMap(null);
  }
  inputElement.focus();
}

function initUI() {
  inputElement = document.getElementById(
    "autocomplete-input",
  ) as HTMLInputElement;
  addressDetailsContainer = document.querySelector(
    ".addressDetails",
  ) as HTMLElement;
  addressListContainer = document.querySelector(
    ".address-list-container",
  ) as HTMLElement;
  addressList = document.getElementById("address-suggestions") as HTMLElement;
  subBuildingListContainer = document.getElementById(
    "sub-building-suggestions",
  ) as HTMLElement;
  subBuildingList = document.getElementById(
    "sub-building-suggestions",
  ) as HTMLElement;
  allResultsContainer = document.getElementById(
    "all-results-container",
  ) as HTMLElement;
  resultsContainer = document.querySelector(
    ".autosuggest-results-container",
  ) as HTMLElement;
  autoSuggestResultsTitle = document.querySelector(
    ".sub-building-list-container .autosuggest-results-title",
  ) as HTMLElement;
  detailsHTML = document.querySelector(
    ".addressDetails .options",
  ) as HTMLElement;
  results = document.querySelector(".autosuggest-results") as HTMLElement;
  clearSearchBtn = document.querySelector(
    ".clear-searchButton",
  ) as HTMLButtonElement;

  autoSuggestResultsTitle.addEventListener("click", backToAddressList);
  inputElement.addEventListener("input", displayW3wSuggestion);
  clearSearchBtn.addEventListener("click", resetUI);
}

type DebouncePromiseFunction<T, Args extends any[]> = (
  ...args: Args
) => Promise<T>;

function debouncePromise<T, Args extends any[]>(
  fn: (...args: Args) => Promise<T>,
  delay: number,
): DebouncePromiseFunction<T, Args> {
  let timeoutId: ReturnType<typeof setTimeout> | null = null;
  let latestResolve: ((value: T | PromiseLike<T>) => void) | null = null;
  let latestReject: ((reason?: any) => void) | null = null;

  return function (...args: Args): Promise<T> {
    return new Promise<T>((resolve, reject) => {
      if (timeoutId !== null) {
        clearTimeout(timeoutId);
      }
      latestResolve = resolve;
      latestReject = reject;
      timeoutId = setTimeout(() => {
        fn(...args)
          .then((result) => {
            if (latestResolve === resolve && latestReject === reject) {
              resolve(result);
            }
          })
          .catch((error) => {
            if (latestResolve === resolve && latestReject === reject) {
              reject(error);
            }
          });
      }, delay);
    });
  };
}

declare global {
  interface Window {
    initMap: () => void;
  }
}
window.initMap = initMap;

    
        let selectedAddress;
let addressDetailsContainer;
let addressListContainer;
let addressList;
let subBuildingListContainer;
let subBuildingList;
let allResultsContainer;
let resultsContainer;
let autoSuggestResultsTitle;
let results;
let inputElement;
let detailsHTML;
let clearSearchBtn;
let map;
let marker;
let debouncedAutosuggestW3W;
const API_KEY = "YOUR_API_KEY"; // Replace YOUR_API_KEY with your actual API key

function convertToAddress(words) {
  return fetch(
    `https://api.woosmap.com/what3words/convert-to-address?key=${API_KEY}&words=${words}`,
  ).then((response) => response.json());
}

function getLocalitiesDetails(publicId) {
  return fetch(
    `https://api.woosmap.com/localities/details/?key=${API_KEY}&public_id=${publicId}`,
  ).then((response) => response.json());
}

function autosuggestW3W(input) {
  return fetch(
    `https://api.woosmap.com/what3words/autosuggest?key=${API_KEY}&input=${input}`,
  ).then((response) => response.json());
}

function clearSection(section) {
  section.innerHTML = "";
}

function hideSection(section) {
  section.style.display = "none";
}

function displaySection(section, mode = "block") {
  section.style.display = mode;
}

function setSelectedAddress(selectedElement) {
  if (selectedAddress) {
    selectedAddress.classList.remove("selected");
  }

  selectedAddress = selectedElement;
  selectedAddress.classList.add("selected");
}

function panMap(addressDetail) {
  if (addressDetail.geometry.viewport) {
    const { viewport } = addressDetail.geometry;
    const bounds = {
      east: viewport.northeast.lng,
      south: viewport.southwest.lat,
      north: viewport.northeast.lat,
      west: viewport.southwest.lng,
    };

    map.fitBounds(bounds);
    map.panTo(addressDetail.geometry.location);
  } else {
    let zoom = 17;

    if (addressDetail.types[0] === "address") {
      zoom = 18;
    }

    map.setZoom(zoom);
    map.panTo(addressDetail.geometry.location);
  }
}

function createAddressMarker(addressDetail) {
  if (marker) {
    marker.setMap(null);
  }

  marker = new woosmap.map.Marker({
    position: addressDetail.geometry.location,
    icon: {
      url: "https://images.woosmap.com/marker-alt.png",
      scaledSize: {
        height: 59,
        width: 37,
      },
    },
  });
  marker.setMap(map);
  panMap(addressDetail);
}

function fillAddressDetails(addressDetails) {
  const details = [];

  if (addressDetails.formatted_address) {
    details.push(
      `<p class='option-detail'><span class='option-detail-label'>Formatted_address :</span><span class='bold'>${addressDetails.formatted_address}</span></p>`,
    );
  }

  if (addressDetails.types && addressDetails.types[0]) {
    details.push(
      `<p class='option-detail'><span class='option-detail-label'>Type : </span><span class='bold'>${addressDetails.types[0].replace("_", " ")}</span></p>`,
    );
  }

  if (addressDetails.geometry) {
    details.push(
      `<div class='option-detail'><div><span class='option-detail-label'>Latitude :</span> <span class='bold'>${addressDetails.geometry.location.lat.toString()}</span></div><div><span class='option-detail-label'>Longitude : </span><span class='bold'>${addressDetails.geometry.location.lng.toString()}</span></div></div>`,
    );
    if (addressDetails.address_components) {
      const compoHtml = addressDetails.address_components
        .map(
          (compo) =>
            `<p class='option-detail'><span class='option-detail-label'>${compo.types[0]}:</span> <span class='bold'>${compo.long_name}</span></p>`,
        )
        .join("");

      details.push(
        `<div class='address-components'><div class='title'>Address components</div><div>${compoHtml}</div>`,
      );
    }
  }

  detailsHTML.innerHTML = details.join("");
}

function getAddressDetails(target, publicId) {
  setSelectedAddress(target);
  getLocalitiesDetails(publicId)
    .then((detailResponse) => {
      const addressDetails = detailResponse.result;

      if (addressDetails) {
        createAddressMarker(addressDetails);
        fillAddressDetails(addressDetails);
        displaySection(addressDetailsContainer);
      }
    })
    .catch((error) => {
      console.error(error);
    });
}

function displaySubBuildings(target, subBuildings) {
  setSelectedAddress(target);
  hideSection(addressListContainer);
  clearSection(subBuildingList);
  displaySection(subBuildingListContainer);
  displaySection(autoSuggestResultsTitle);

  const addressList = document.createElement("ul");

  subBuildings.forEach((address, index) => {
    const line = document.createElement("li");

    line.className = "address";
    line.addEventListener("click", (event) => {
      getAddressDetails(line, address.public_id);
    });
    line.innerHTML = `<span class='pin'></span><span>${address.description}</span>`;
    addressList.appendChild(line);
  });
  subBuildingList.appendChild(addressList);
}

function backToAddressList() {
  hideSection(autoSuggestResultsTitle);
  hideSection(subBuildingListContainer);
  hideSection(addressDetailsContainer);
  displaySection(addressListContainer);
}

function displayAddressList(addressDetails) {
  backToAddressList();

  const addressListContainer = addressList;
  const newAddressList = document.createElement("ul");
  const fragment = document.createDocumentFragment();

  if (!Array.isArray(addressDetails) || addressDetails.length === 0) {
    const line = document.createElement("li");

    line.className = "not-found";
    line.textContent =
      "what3words address invalid or no street address found within 200m of its location.";
    newAddressList.appendChild(line);
  } else {
    addressDetails.forEach((address, index) => {
      const line = document.createElement("li");

      line.className = "address";
      line.addEventListener("click", (event) => {
        if (address.public_id) {
          getAddressDetails(line, address.public_id);
        } else {
          displaySubBuildings(line, address.sub_buildings);
        }
      });
      line.innerHTML = `<span class='${address.public_id ? "pin" : "arrow"}'></span><span>${address.description}</span>`;
      fragment.appendChild(line);
    });
  }

  newAddressList.appendChild(fragment);
  addressListContainer.appendChild(newAddressList);
}

function getPossibleAddress(words) {
  hideSection(resultsContainer);
  displaySection(addressListContainer);
  hideSection(addressDetailsContainer);
  clearSection(addressList);
  convertToAddress(words)
    .then(({ results }) => {
      if (results) {
        displayAddressList(results);
      }
    })
    .catch((error) => {
      console.error(error);
    });
}

function w3wClickCallback(suggestion) {
  document.querySelector(".words").innerHTML = suggestion.words;
  document.querySelector(".nearest").innerHTML =
    `Nearest place : ${suggestion.nearestPlace}`;
  inputElement.value = `///${suggestion.words}`;
  getPossibleAddress(suggestion.words);
}

function displayW3wSuggestion() {
  const value = inputElement.value;

  if (value) {
    displaySection(clearSearchBtn);
  }

  value.replace('"', '\\"').replace(/^\s+|\s+$/g, "");
  hideSection(autoSuggestResultsTitle);
  if ((value.match(/[.]/g) || []).length !== 2) {
    hideSection(resultsContainer);
    hideSection(addressListContainer);
    hideSection(subBuildingListContainer);
    return;
  }

  displaySection(allResultsContainer);
  hideSection(addressListContainer);
  hideSection(subBuildingListContainer);
  hideSection(addressDetailsContainer);
  debouncedAutosuggestW3W(value)
    .then(({ suggestions }) => {
      clearSection(results);

      const list = document.createElement("ul");

      if (suggestions) {
        suggestions.forEach((suggestion) => {
          const item = document.createElement("li");

          item.className = "suggestion";
          item.id = suggestion.rank.toString();
          item.innerHTML = `<div class="words">${suggestion.words}</div><div class="nearest">Nearest place : ${suggestion.nearestPlace}</div>`;
          item.dataset.word = suggestion.words;
          item.onclick = () => {
            w3wClickCallback(suggestion);
          };

          list.appendChild(item);
        });
        results.appendChild(list);
      } else {
        const failed = document.createElement("div");

        failed.innerHTML = "Failed to load suggestions";
        results.appendChild(failed);
      }

      displaySection(resultsContainer, "flex");
    })
    .catch((error) => {
      console.error(error);
    });
}

function initMap() {
  map = new woosmap.map.Map(document.getElementById("map"), {
    center: {
      lat: 48.8534,
      lng: 2.3488,
    },
    disableDefaultUI: true,
    gestureHandling: "greedy",
    zoom: 5,
    styles: [
      {
        featureType: "poi",
        stylers: [{ visibility: "off" }],
      },
    ],
  });
  debouncedAutosuggestW3W = debouncePromise(autosuggestW3W, 0);
  initUI();
}

function resetUI() {
  hideSection(resultsContainer);
  hideSection(addressListContainer);
  hideSection(subBuildingListContainer);
  hideSection(addressDetailsContainer);
  hideSection(clearSearchBtn);
  hideSection(allResultsContainer);
  clearSection(addressList);
  clearSection(subBuildingList);
  clearSection(detailsHTML);
  inputElement.value = "";
  if (marker) {
    marker.setMap(null);
  }

  inputElement.focus();
}

function initUI() {
  inputElement = document.getElementById("autocomplete-input");
  addressDetailsContainer = document.querySelector(".addressDetails");
  addressListContainer = document.querySelector(".address-list-container");
  addressList = document.getElementById("address-suggestions");
  subBuildingListContainer = document.getElementById(
    "sub-building-suggestions",
  );
  subBuildingList = document.getElementById("sub-building-suggestions");
  allResultsContainer = document.getElementById("all-results-container");
  resultsContainer = document.querySelector(".autosuggest-results-container");
  autoSuggestResultsTitle = document.querySelector(
    ".sub-building-list-container .autosuggest-results-title",
  );
  detailsHTML = document.querySelector(".addressDetails .options");
  results = document.querySelector(".autosuggest-results");
  clearSearchBtn = document.querySelector(".clear-searchButton");
  autoSuggestResultsTitle.addEventListener("click", backToAddressList);
  inputElement.addEventListener("input", displayW3wSuggestion);
  clearSearchBtn.addEventListener("click", resetUI);
}

function debouncePromise(fn, delay) {
  let timeoutId = null;
  let latestResolve = null;
  let latestReject = null;

  return function (...args) {
    return new Promise((resolve, reject) => {
      if (timeoutId !== null) {
        clearTimeout(timeoutId);
      }

      latestResolve = resolve;
      latestReject = reject;
      timeoutId = setTimeout(() => {
        fn(...args)
          .then((result) => {
            if (latestResolve === resolve && latestReject === reject) {
              resolve(result);
            }
          })
          .catch((error) => {
            if (latestResolve === resolve && latestReject === reject) {
              reject(error);
            }
          });
      }, delay);
    });
  };
}

window.initMap = initMap;

    
        /*
 * Always set the map height explicitly to define the size of the div element
 * that contains the map.
 */
#map {
  height: 100%;
}

/*
 * Optional: Makes the sample page fill the window.
 */
html,
body {
  height: 100%;
  margin: 0;
  padding: 0;
  font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol, Noto Color Emoji;
}

#autocomplete-container {
  display: flex;
  position: absolute;
  top: 10px;
  left: 10px;
  z-index: 1;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2), 0 -1px 0px rgba(0, 0, 0, 0.02);
  background: #fff;
  border-radius: 12px;
  padding: 0 12px;
  max-width: 320px;
  width: 100%;
  height: 42px;
  border: none;
  box-sizing: border-box;
  align-items: center;
  cursor: text;
  font-size: 15px;
}

#autocomplete-container .search-icon, #autocomplete-container .clear-icon {
  color: inherit;
  flex-shrink: 0;
  height: 16px;
  width: 16px;
}

#autocomplete-container .clear-icon {
  transform: scale(1.3);
}

#autocomplete-input {
  box-sizing: border-box;
  padding: 0;
  height: 40px;
  line-height: 24px;
  vertical-align: top;
  transition-property: color;
  transition-duration: 0.3s;
  width: 100%;
  text-overflow: ellipsis;
  background: transparent;
  border-radius: 0;
  border: 0;
  margin: 0 8px;
  outline: 0;
  overflow: visible;
  appearance: textfield;
  font-size: 100%;
}

.clear-searchButton {
  display: none;
  height: 18px;
  width: 22px;
  background: none;
  border: none;
  vertical-align: middle;
  pointer-events: all;
  cursor: pointer;
}

#suggestions-list {
  border-radius: 12px;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2), 0 -1px 0px rgba(0, 0, 0, 0.02);
  box-sizing: border-box;
  position: absolute;
  max-width: 320px;
  width: 100%;
  top: 100%;
  left: 0;
  z-index: 1;
  list-style: none;
  max-height: 80vh;
  margin: 5px 0 0;
  padding: 0;
  display: none;
  overflow-y: auto;
  background-color: #fff;
}

#suggestions-list li {
  padding: 12px;
  cursor: pointer;
  transition: background-color 0.3s ease;
}

#suggestions-list li:hover {
  background-color: #f2f2f2;
}

#app {
  height: 100%;
  font-size: 13px;
}

#autocomplete-container {
  font-size: 13px;
}

* {
  box-sizing: border-box;
}

p {
  margin: 5px 0;
}

li {
  list-style: none outside;
}

ul {
  margin: 0;
  padding: 0;
}

ul > :not(:last-child) {
  border-bottom: 1px solid rgba(0, 0, 0, 0.1);
}

.bold {
  font-weight: 700;
}

#all-results-container {
  position: absolute;
  top: 100%;
  width: 100%;
  left: 0;
  z-index: 1;
  list-style: none;
  margin: 8px 0 0;
  display: none;
  background-color: #fff;
  border-radius: 6px;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2), 0 -1px 0px rgba(0, 0, 0, 0.02);
}

.autosuggest-results-container {
  display: none;
  flex-direction: column;
  background-color: #fff;
  border-radius: 6px;
}

.autosuggest-results {
  width: 100%;
  min-width: 0;
}

.autosuggest-results-title, .address-list .title {
  padding: 16px;
  background-color: rgba(0, 0, 0, 0.02);
  color: rgba(0, 0, 0, 0.5);
  font-size: 10px;
  text-transform: uppercase;
  border-bottom: 1px solid rgba(0, 0, 0, 0.06);
  letter-spacing: 0.5px;
  border-bottom: 1px solid rgba(0, 0, 0, 0.06);
  font-weight: 500;
}

.address-list {
  max-height: 305px;
  overflow-y: auto;
}

.addressDetails {
  display: none;
  position: absolute;
  right: 10px;
  bottom: 25px;
  border-radius: 6px;
  max-width: 240px;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2), 0 -1px 0px rgba(0, 0, 0, 0.02);
  z-index: 1;
  overflow: hidden;
}
.addressDetails .info {
  padding: 12px 16px;
  border-bottom: 1px solid rgba(0, 0, 0, 0.06);
  background-color: #fff;
}
.addressDetails .options {
  overflow-y: auto;
  max-height: 240px;
  font-size: 12px;
  padding-top: 12px;
  background-color: #fff;
}
.addressDetails .options .option-detail {
  display: flex;
  flex-wrap: wrap;
  padding: 0 12px 8px 12px;
  margin: 0;
}
.addressDetails .options .option-detail-label {
  color: rgba(0, 0, 0, 0.5);
  margin-right: 4px;
}

.address-components {
  padding: 0 0 18px 0;
  background-color: rgba(0, 0, 0, 0.03);
}

.address-components .title {
  color: rgba(0, 0, 0, 0.5);
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 1px;
  padding: 16px 12px 10px 12px;
}

.options > label {
  display: block;
  padding: 5px;
}

.options select, .options button {
  padding: 3px;
  margin: 5px;
  width: 200px;
}

.selectBox {
  position: relative;
}

.w3w-results {
  display: flex;
  background-color: #fff;
  min-width: 300px;
  overflow-y: auto;
  max-height: 400px;
}

.w3w-results-container {
  display: none;
}

.address-list-container {
  display: none;
}

.suggestion {
  padding: 10px 16px;
  width: 100%;
  min-width: 0;
  transition: 0.3s all ease-in-out;
}

.suggestion:hover {
  background-color: rgba(0, 0, 0, 0.02);
  cursor: pointer;
}

.info .words, .suggestion .words {
  font-weight: 700;
  margin-bottom: 1px;
  transition: 0.3s all ease-in-out;
  color: #252525;
}

.info .words::before, .suggestion .words::before {
  content: "///";
  margin-right: 5px;
  color: #e01f25;
  letter-spacing: -0.06rem;
}

.suggestion:hover .words {
  color: #000;
}

.info .nearest, .suggestion .nearest {
  font-size: 12px;
  color: rgba(0, 0, 0, 0.5);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  transition: 0.3s all ease-in-out;
}

.suggestion:hover .nearest {
  color: #000;
}

.sub-building-list-container .autosuggest-results-title {
  cursor: pointer;
  display: none;
}

.not-found {
  padding: 12px 16px;
  color: #930e07;
}

.address {
  cursor: pointer;
  position: relative;
  display: flex;
  align-items: center;
  padding: 10px 16px 10px;
  min-height: 50px;
}
.address:hover {
  background-color: rgba(0, 0, 0, 0.02);
  color: #3949ab;
}
.address:hover .pin {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='23' viewBox='0 0 16 23'%3E%3Cpath fill='%233949ab' d='M7.904,22.449 C8.296,22.449 8.646,22.219 8.646,21.802 C8.646,16.597 15.808,13.584 15.808,7.904 C15.808,3.53874134 12.2692587,1.19949929e-24 7.904,1.19949929e-24 C3.53874134,1.19949929e-24 0,3.53874134 0,7.904 C0,13.583 7.162,16.597 7.162,21.802 C7.162,22.227 7.511,22.449 7.904,22.449 L7.904,22.449 Z M4.625,8.043 C4.625,6.23150602 6.09350602,4.763 7.905,4.763 C9.71649398,4.763 11.185,6.23150602 11.185,8.043 C11.185,9.85449398 9.71649398,11.323 7.905,11.323 C6.09350602,11.323 4.625,9.85449398 4.625,8.043 Z'/%3E%3C/svg%3E");
  background-size: contain;
  background-repeat: no-repeat;
  background-position: center;
}
.address:hover .arrow {
  background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="20" height="16" viewBox="0 0 20 16"><path fill="%233949ab" d="M0 15C0 14.4477153.44771525 14 1 14L19 14C19.5522847 14 20 14.4477153 20 15 20 15.5522847 19.5522847 16 19 16L1 16C.44771525 16 0 15.5522847 0 15M0 8C0 7.44771525.44771525 7 1 7L19 7C19.5522847 7 20 7.44771525 20 8 20 8.55228475 19.5522847 9 19 9L1 9C.44771525 9 0 8.55228475 0 8M0 1C0 .44771525.44771525 0 1 0L19 0C19.5522847 0 20 .44771525 20 1 20 1.55228475 19.5522847 2 19 2L1 2C.44771525 2 0 1.55228475 0 1"/></svg>');
  background-size: contain;
  background-repeat: no-repeat;
  background-position: center;
}
.address.selected {
  color: #da0082;
  font-weight: 700;
  background-color: rgba(85, 134, 255, 0.06);
}
.address.selected .pin {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='23' viewBox='0 0 16 23'%3E%3Cpath fill='%23da0082' d='M7.904,22.449 C8.296,22.449 8.646,22.219 8.646,21.802 C8.646,16.597 15.808,13.584 15.808,7.904 C15.808,3.53874134 12.2692587,1.19949929e-24 7.904,1.19949929e-24 C3.53874134,1.19949929e-24 0,3.53874134 0,7.904 C0,13.583 7.162,16.597 7.162,21.802 C7.162,22.227 7.511,22.449 7.904,22.449 L7.904,22.449 Z M4.625,8.043 C4.625,6.23150602 6.09350602,4.763 7.905,4.763 C9.71649398,4.763 11.185,6.23150602 11.185,8.043 C11.185,9.85449398 9.71649398,11.323 7.905,11.323 C6.09350602,11.323 4.625,9.85449398 4.625,8.043 Z'/%3E%3C/svg%3E");
  background-size: contain;
  background-repeat: no-repeat;
  background-position: center;
}
.address.selected .arrow {
  background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="20" height="16" viewBox="0 0 20 16"><path fill="%23da0082" d="M0 15C0 14.4477153.44771525 14 1 14L19 14C19.5522847 14 20 14.4477153 20 15 20 15.5522847 19.5522847 16 19 16L1 16C.44771525 16 0 15.5522847 0 15M0 8C0 7.44771525.44771525 7 1 7L19 7C19.5522847 7 20 7.44771525 20 8 20 8.55228475 19.5522847 9 19 9L1 9C.44771525 9 0 8.55228475 0 8M0 1C0 .44771525.44771525 0 1 0L19 0C19.5522847 0 20 .44771525 20 1 20 1.55228475 19.5522847 2 19 2L1 2C.44771525 2 0 1.55228475 0 1"/></svg>');
  background-size: contain;
  background-repeat: no-repeat;
  background-position: center;
}

.pin, .arrow {
  flex: 0 0 13px;
  height: 18px;
  margin-right: 12px;
}

.pin {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='23' viewBox='0 0 16 23'%3E%3Cpath fill='%231f88e5' d='M7.904,22.449 C8.296,22.449 8.646,22.219 8.646,21.802 C8.646,16.597 15.808,13.584 15.808,7.904 C15.808,3.53874134 12.2692587,1.19949929e-24 7.904,1.19949929e-24 C3.53874134,1.19949929e-24 0,3.53874134 0,7.904 C0,13.583 7.162,16.597 7.162,21.802 C7.162,22.227 7.511,22.449 7.904,22.449 L7.904,22.449 Z M4.625,8.043 C4.625,6.23150602 6.09350602,4.763 7.905,4.763 C9.71649398,4.763 11.185,6.23150602 11.185,8.043 C11.185,9.85449398 9.71649398,11.323 7.905,11.323 C6.09350602,11.323 4.625,9.85449398 4.625,8.043 Z'/%3E%3C/svg%3E");
  background-size: contain;
  background-repeat: no-repeat;
  background-position: center;
}

.arrow {
  background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="20" height="16" viewBox="0 0 20 16"><path fill="%231f88e5" d="M0 15C0 14.4477153.44771525 14 1 14L19 14C19.5522847 14 20 14.4477153 20 15 20 15.5522847 19.5522847 16 19 16L1 16C.44771525 16 0 15.5522847 0 15M0 8C0 7.44771525.44771525 7 1 7L19 7C19.5522847 7 20 7.44771525 20 8 20 8.55228475 19.5522847 9 19 9L1 9C.44771525 9 0 8.55228475 0 8M0 1C0 .44771525.44771525 0 1 0L19 0C19.5522847 0 20 .44771525 20 1 20 1.55228475 19.5522847 2 19 2L1 2C.44771525 2 0 1.55228475 0 1"/></svg>');
  background-size: contain;
  background-repeat: no-repeat;
  background-position: center;
}


    
        <html>
  <head>
    <title>Woosmap for What3Words</title>
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta charset="utf-8" />

    <link rel="stylesheet" type="text/css" href="./style.css" />
    <script type="module" src="./index.js"></script>
  </head>
  <body>
    <div id="app">
      <div class="addressDetails">
        <div class="info">
          <div class="words"></div>
          <div class="nearest"></div>
        </div>
        <div class="options"></div>
      </div>
      <div id="autocomplete-container">
        <svg class="search-icon" viewBox="0 0 16 16">
          <path
            d="M3.617 7.083a4.338 4.338 0 1 1 8.676 0 4.338 4.338 0 0 1-8.676 0m4.338-5.838a5.838 5.838 0 1 0 2.162 11.262l2.278 2.279a1 1 0 0 0 1.415-1.414l-1.95-1.95A5.838 5.838 0 0 0 7.955 1.245"
            fill-rule="evenodd"
            clip-rule="evenodd"
          ></path>
        </svg>

        <input
          type="text"
          id="autocomplete-input"
          placeholder="your what3words (e.g. filled.count.soap)"
          autocomplete="off"
        />
        <button aria-label="Clear" class="clear-searchButton" type="button">
          <svg class="clear-icon" viewBox="0 0 24 24">
            <path
              d="M7.074 5.754a.933.933 0 1 0-1.32 1.317L10.693 12l-4.937 4.929a.931.931 0 1 0 1.319 1.317l4.938-4.93 4.937 4.93a.933.933 0 0 0 1.581-.662.93.93 0 0 0-.262-.655L13.331 12l4.937-4.929a.93.93 0 0 0-.663-1.578.93.93 0 0 0-.656.261l-4.938 4.93z"
            ></path>
          </svg>
        </button>
        <div id="all-results-container">
          <div class="autosuggest-results-container">
            <div class="autosuggest-results-title">what3words results</div>
            <div class="autosuggest-results"></div>

            <div class="w3w-results-container">
              <div class="w3w-results"></div>
            </div>
          </div>
          <div class="address-list-container">
            <div class="autosuggest-results-title">
              Woosmap addresses results
            </div>
            <div id="address-suggestions" class="address-list"></div>
          </div>
          <div class="sub-building-list-container">
            <div class="autosuggest-results-title">
              ← Back to woosmap results
            </div>
            <div id="sub-building-suggestions" class="address-list"></div>
          </div>
        </div>
        <ul id="suggestions-list"></ul>
      </div>
      <div id="map"></div>
    </div>

    <script
      src="https://sdk.woosmap.com/map/map.js?key=woos-48c80350-88aa-333e-835a-07f4b658a9a4&callback=initMap"
      defer
    ></script>
  </body>
</html>

    

Running the Sample Locally

Before you can run this sample on your local machine, you need to have Git and Node.js installed. If they’re not already installed, follow these instructions to get them set up.

Once you have Git and Node.js installed, you can run the sample by following these steps:

  1. Clone the repository and navigate to the directory of the sample.

  2. Install the necessary dependencies.

  3. Start running the sample.

Here are the commands you can use in your terminal to do this:

Shell
        git clone -b sample/w3w-autocomplete https://github.com/woosmap/js-samples.git
cd js-samples
npm i
npm start

    

You can experiment with other samples by switching to any branch that starts with the pattern sample/SAMPLE_NAME.

Shell
        git checkout sample/SAMPLE_NAME
npm i
npm start

    
Was this article helpful?
Have more questions? Submit a request