🛠️ Google Maps Clone: Driving directions

Hey ,

I'm thrilled to help you learn JavaScript. Unfortunately, you've landed on a page where you cannot access with your current purchase.

Please upgrade (use this link) access this content.

I'm super eager to help you learn more!

🛠️ Google Maps Clone: Driving directions

You learned how to draw directions on a map. The next thing to learn is how to let users search for directions from one place to another.

For Google Maps, you need to use the places library. (Or you won’t be able to search directions from one address to another).

Adding the places library

To use the places library, you need to add the libraries=places query parameter to your Google Maps API call.

const fetchGoogleMapsApi = _ => {
  // ...
  script.src = `https://maps.googleapis.com/maps/api/js?key=${apiKey}&libraries=places&callback=initGoogleMaps`
  // ...
}

Building the search panel

We’ll build a search panel that allows users to enter two addresses. I’ve created the UI for you. You can find it in the starter file for this lesson.

The HTML for the search panel looks like this:

<form action="#" class="search-panel" autocomplete="off">
  <!-- ... -->
  <div class="search-panel__body">
    <div class="search-box">
      <span class="search-box__stopover-icon"></span>
      <input type="search" ... />
    </div>

    <!-- Another search box -->
    <!-- The Get directions button -->
  </div>
</form>
  Form controls displayed on map

Google’s Autocomplete Widget

Google’s places library contains an Autocomplete Widget. (We called this the Typeahead). It gives us some default HTML, CSS, and JavaScript so you don’t have to write much to get started.

(But styling the widget can be a pain in the ass. I won’t go into the styling in this course. You can find the required styles in the starter file).

To use Google’s Autocomplete Widget, you need to pass an input element into google.maps.places.Autocomplete.

In this case, we have two input elements.

function initGoogleMap () {
  // ...
  const searchPanel = document.querySelector('.search-panel')
  const searchBoxes = [...searchPanel.querySelectorAll('.search-box')]

  searchBoxes.forEach(searchBox => {
    const input = searchBox.querySelector('input')
    new google.maps.places.Autocomplete(input)
  })
}

This creates an Autocomplete (or Typeahead) for the search box. We’ll call it Autocomplete for this lesson since Google calls it Autocomplete.

You can see the Autocomplete if you type something into any of the input fields.

You can also click on the predictions in the Autocomplete widget and it’ll fill in the predictions for you.

(If you’re up for a challenge, you can create an autocomplete component yourself through Google’s Autocomplete Service).

Searching closer to the map

I’m based in Singapore. I created my map to be in Singapore. But my search results were way off the mark!

We want the search results to be bounded (or at least near to) the map you created. To do this, we use the autocomplete’s bindTo method.

You’ll have to search the Docs for this feature. You can find it in this guide for the Autocomplete widget.

function initGoogleMap () {
  // ...
  searchBoxes.forEach(searchBox => {
    const input = searchBox.querySelector('input')
    const autocomplete = new google.maps.places.Autocomplete(input)
    autocomplete.bindTo('bounds', map)
  })
}
Search results are near the boundaries of the map

Drawing the route

When the user presses the “Get directions” button, we want to draw a route. We can do that by listening to the submit event.

function initGoogleMap () {
  // ...
  searchPanel.addEventListener('submit', event => {
    // ...
  })
}

First, we want to prevent the form from submitting (and refreshing the page).

function initGoogleMap () {
  // ...
  searchPanel.addEventListener('submit', event => {
    event.preventDefault()
  })
}

Next, we need to get the values from both input fields. We’ll call the starting point “origin”, and the ending point “destination”.

searchPanel.addEventListener('submit', event => {
  // ...
  const originInput = searchBoxes[0].querySelector('input')
  const destinationInput = searchBoxes[1].querySelector('input')

  const origin = originInput.value.trim()
  const destination = destinationInput.value.trim()
})

Once we know the origin and destination, we can draw the directions.

searchPanel.addEventListener('submit', event => {
  // ...
  const request = {
    origin,
    destination,
    travelMode: 'DRIVING'
  }

  getDirections(request)
    .then(result => {
      new google.maps.DirectionsRenderer({
        map,
        directions: result
      })
    })
    .catch(console.error)
})
  Route drawn from two auto-completed search results

Rendering the directions panel

The Google Maps API lets you create a directions panel that shows you turn-by-turn instructions. You need to tell Google where you add this panel. For this, we’ll add a <div> with a class called directions-panel.

<body>
  <form class="search-panel"> ... </form>
  <div class="directions-panel"></div>
  <!-- ... -->
</body>

Next, you tell Google about this <div> in the directions renderer.

function initGoogleMaps () {
  // ...
  getDirections(request)
    .then(result => {
      new google.maps.DirectionsRenderer({
        // ...
        panel: document.querySelector('.directions-panel')
      })
    })
    .catch(console.error)
}
The directions panel.

Redrawing directions

If the user performs another search right after their first search, you’ll notice Google draws two routes.

  1. The previous route
  2. The current route

This happens because we used a “new” instance of the Directions Renderer. Because we have two renderers, we have two plotted routes on the map.

To fix this, we need to use the same renderer. We can do so by declaring the renderer first.

function initGoogleMaps () {
  // ...
  const directionsRenderer = new google.maps.DirectionsRenderer({
    map,
    panel: document.querySelector('.directions-panel')
  })
}

When we get the directions, we can draw on the same renderer with the setDirections method.

searchPanel.addEventListener('submit', event => {
  // ...
  getDirections(request)
    .then(result => {
      directionsRenderer.setDirections(result)
    })
    .catch(console.error)
})

That’s it!