🛠️ Google Maps Clone: Refactor

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: Refactor

We’re done with the component. Let’s take a step back and clean up the code.

Getting search boxes

We used this line to get search boxes many times in the code:

const searchBoxes = [...searchPanel.querySelectorAll('.search-box')]

We can make this shorter (and also easier to read and understand) if we put it into a function.

For this function, we can use document instead of searchPanel. If we do this, we don’t need to pass in a variable (which makes it easier to use). This is only possible if there’s only one search panel in Google Maps clone.

const getSearchBoxes = _ => {
  return [...document.querySelectorAll('.search-box')]
}

Initialise Autocomplete

First, you notice we used these three lines to initialise Google’s Autocomplete in two places:

// For the first two search boxes
searchBoxes.forEach(searchBox => {
  const input = searchBox.querySelector('input')
  const autocomplete = new google.maps.places.Autocomplete(input)
  autocomplete.bindTo('bounds', map)
})
// For search boxes created with JavaScript
addSearchboxButton.addEventListener('click', event => {
  // ...
  const input = clone.querySelector('input')
  const autocomplete = new google.maps.places.Autocomplete(input)
  autocomplete.bindTo('bounds', map)
  // ...
})

We can place these into a function called initGoogleAutocomplete.

const initGoogleAutocomplete = searchBox => {
  const input = searchBox.querySelector('input')
  const autocomplete = new google.maps.places.Autocomplete(input)
  autocomplete.bindTo('bounds', map)
}

We have to put initGoogleAutocomplete inside initGoogleMaps since it requires the map element.

function initGoogleMaps () {
  // ...
  const map = new google.maps.Map(mapDiv, {
    center: { lat: 1.3521, lng: 103.8198 },
    zoom: 13
  })

  const initGoogleAutocomplete = searchBox => {
    const input = searchBox.querySelector('input')
    const autocomplete = new google.maps.places.Autocomplete(input)
    autocomplete.bindTo('bounds', map)
  }
}

Using initGoogleAutocomplete:

// For the first two search boxes
searchBoxes.forEach(initGoogleAutocomplete)
// For search boxes created with JavaScript
addSearchboxButton.addEventListener('click', event => {
  // ...
  initGoogleAutocomplete(clone)
  // ...
})

We had to use querySelector to get the search box value:

searchPanel.addEventListener('submit', event => {
  // ...
  const request = {
    origin: searchBoxes[0].querySelector('input').value.trim(),
    destination: searchBoxes[searchBoxes.length - 1].querySelector('input').value.trim(),
    travelMode: 'DRIVING'
  }
  // ...
})

We can make this easier by putting it into a function.

const getSearchBoxValue = searchBox => {
  return searchBox.querySelector('input').value.trim()
}

Using it:

searchPanel.addEventListener('submit', event => {
  const request = {
    origin: getSearchBoxValue(searchBoxes[0]),
    destination: getSearchBoxValue(searchBoxes.length - 1),
    travelMode: 'DRIVING'
  }

  if (searchBoxes.length > 2) {
    const waypoints = searchBoxes.slice(1, searchBoxes.length - 1)
      .map(waypoint => {
        return {
          location: getSearchBoxValue(waypoint),
          stopover: true
        }
      })
    // ...
  }
  // ...
})

Drawing directions

There’s a chunk of code in the part we used to draw directions (especially with the error messages).

searchPanel.addEventListener('submit', event => {
  getDirections(request)
    .then(result => {
      directionsRenderer.setDirections(result)
    })
    .catch(result => {
      const errors = {
        INVALID_REQUEST: 'Invalid request',
        MAX_WAYPOINTS_EXCEEDED: 'Maximum of 8 waypoints allowed',
        NOT_FOUND: 'At least one location cannot be geocoded',
        OVER_QUERY_LIMIT: 'You sent too many requests in a short time. Slow down!',
        UNKNOWN_ERROR: 'An error happened on the server. Please try again later',
        ZERO_RESULTS: 'Cannot find route between origin and destination'
      }
      const message = errors[result.status]
      const errorDiv = searchPanel.querySelector('.search-panel__error')
      errorDiv.textContent = message
    })
})

We can throw this into a function called drawDirections. Make sure you put the function inside initGoogleMaps .

function initGoogleMaps () {
  // ...
  const drawDirections = request => {
    getDirections(request)
      .then(result => {
        directionsRenderer.setDirections(result)
      })
      .catch(result => {
        const errors = {
          INVALID_REQUEST: 'Invalid request',
          MAX_WAYPOINTS_EXCEEDED: 'Maximum of 8 waypoints allowed',
          NOT_FOUND: 'At least one location cannot be geocoded',
          OVER_QUERY_LIMIT: 'You sent too many requests in a short time. Slow down!',
          UNKNOWN_ERROR: 'An error happened on the server. Please try again later',
          ZERO_RESULTS: 'Cannot find route between origin and destination'
        }
        const message = errors[result.status]
        const errorDiv = searchPanel.querySelector('.search-panel__error')
        errorDiv.textContent = message
      })
  }
}

Using it:

searchPanel.addEventListener('submit', event => {
  // ...
  drawDirections(request)
})

We can pull out errorDiv from drawDirections to initGoogleMaps. If we do this, we don’t have to search for errorDiv again in the submit event listener.

function initGoogleMaps () {
  // ...
  const errorDiv = searchPanel.querySelector('.search-panel__error')

  const drawDirections = request => {
    getDirections(request)
      .then(/*...*/)
      .catch(result => {
        // ...
        errorDiv.textContent = message
      })
  }

  searchPanel.addEventListener('submit', event => {
    // ...
    errorDiv.textContent = ''
    // ...
  })
}

That’s it!