A front-end tale: Google Places Library, jQuery promises and Coffeescript

When it comes to define my working position I often need to state (and clarify) the differences between design, frontend and backend development.
Given my academic background and my interests I can easily be considered more a backend developer than a frontend. I do not have a great “esthetic sense” and despite my knowledge of HTML5 and CSS (with all its SCSS, SASS, LESS and so on) I do not have the expertise of a “real frontend developer”.
Don’t get me wrong, I studied Javascript basics and I like to keep myself updated with its community news but still, it is not (at least for now ;P) my area of expertise.

image

Nevertheless I like to solve problems independently on their nature and so if there is a “frontend problem” to tackle I’m always ready to dive in.

This is exactly what happened last week.

I was working on a backend story while two of my colleagues (a backend and a frontend dev) were handling another one related to the display of the nearest Points Of Interest (i.e. POI) of a given place on a (Google) Map.
I followed the evolution of their work because I never had the opportunity to try the Google Places Library and I was thrilled to learn more about it. In particular the requirements requested to use the Nearby Search API.
I was also particularly lucky… Once I closed my story I was able to refactor and complete the POI story. The backend developer that was following it was indeed moved to another project to handle an urgent task.
Anyway, the main features required by the story were already implemented but the underlying solution wasn’t quite optimal.

The principal problem was the tangling of multiple AJAX calls with a big common callback whose aim was to store the results of the calls until a given condition was met and then trigger some calculations and UI adjustments.

When I heard about this kind of problem the first thing that jumped to my mind was: “Promises!”

Exactly like Javascript, I read about them and followed their evolution but, until last week, I never practically used them.
Given the limited scope of our problem and the fact that we already had jQuery included inside the project I decided to take a look at jQuery promises. I remembered reading something about their not being compliant and even “broken” but again, our need was kind of basic and so I gave them a try.

The first thing I end up reading was $.when() and right after that I dove into DeferredObject.
After a bit I identified one of the main problems regarding our use case: we didn’t knew in advance how many AJAX calls we had to make. Their number was actually dynamic!
We actually had to pass to the $.when function an arbitrary long array of functions, or better, promises.

Another more trivial problem was you do “transform” a function into a promise.
For this latter the solution was simply to wrap each AJAX call inside a DeferredObject:

...
nearbySearch: (googleTypeName) ->
  deferred = new $.Deferred()
  @placesService.nearbySearch {
    location: @placeCoords
    radius: @nearbySearchRadius
    type: [ googleTypeName ]
  }, @storePoiPlaces(googleTypeName, deferred)
  deferred.promise()
...

The AJAX call is actually @placesService.nearbySearch while the “promise wrapping” consists of deferred = new $.Deferred() and deferred.promise().

@placesService is an attribute of a MapDetail object instance that stores an instance of google.maps.places.PlacesService. I didn’t mentioned it but all the original code was implemented in Coffeescript and encapsulated for simplicity inside a Coffeescript class that was named MapDetail.

@placeCoords, @nearbySearchRadius are also instance attributes of an instance of MapDetail while @storePoiPlaces(googleTypeName, deferred) is an instance method that represents a much more lighter and readable version of the initial common callback used by all the AJAX calls:

...
storePoiPlaces: (googleTypeName, deferred) ->
  (places, status) =>
    if status == google.maps.places.PlacesServiceStatus.OK
      @poiPlaces = @poiPlaces.concat places
    else
      # FIXME: remove or replace with something more appropriate to track Google API error statuses
      console.log googleTypeName, status
    deferred.resolve(places, status)
...

To those unfamiliar with Coffeescript I highly recommend you to go check about the difference of -> and => 😉

The main concept behind @storePoiPlaces however is that it returns a function that has this (@ in Coffeescript) bound to the instance of the MapDetail object. In this way we can access all the instance “functionalities”. Beside the => there is another peculiarity related to the deferred argument of @storePoiPlaces. It is indeed required to resolve the promises that use the function returned by @storePoiPlaces as callback.

Beware that the call resolve on the deferred requires exactly places and the status as arguments.
Now, here it is the complete part of MapDetail class or at least the part related to the fetch of the nearest places given a specific point on a map. I deliberately left out the part concerning the calculations and UI adjustments as I consider them simple data crunching and transformations functionalities.

class MapDetail
  constructor: (@mapSelector) ->
    @$mapContainer      = $(@mapSelector)
    @poiTypes           = @$mapContainer.data 'map-poi-types'
    @numberOfPoisToShow = @$mapContainer.data('number-of-pois-to-show') - 1
    @googleTypesNames   = Object.keys @poiTypes
    @placeCoords        = new google.maps.LatLng @$mapContainer.data('lat'), @$mapContainer.data('lng')
    @map                = new google.maps.Map @$mapContainer[0], center: @placeCoords, zoom: 17, clickableIcons: false
    @poisBounds         = new google.maps.LatLngBounds()
    @placesService      = new google.maps.places.PlacesService @map
    @nearbySearchRadius = 1000
    @poiPlaces          = []
    @init()

  init: ->
    @addPlaceMarker()
    @fetchPois() if @numberOfPoisToShow >= 0

  addPlaceMarker: ->
    new google.maps.Marker
      position: @placeCoords,
      map: @map,
      icon: "<%= asset_path('selected_pin.svg') %>"
    @poisBounds.extend @placeCoords

  fetchPois: ->
    $.when(@nearbySearches()...).then => @addPoisMarkers()

  nearbySearches: ->
    @googleTypesNames.map (googleTypeName) => @nearbySearch(googleTypeName)

  nearbySearch: (googleTypeName) ->
    deferred = new $.Deferred()
    @placesService.nearbySearch {
      location: @placeCoords
      radius: @nearbySearchRadius
      type: [ googleTypeName ]
    }, @storePoiPlaces(googleTypeName, deferred)
    deferred.promise()

  storePoiPlaces: (googleTypeName, deferred) ->
    (places, status) =>
      if status == google.maps.places.PlacesServiceStatus.OK
        @poiPlaces = @poiPlaces.concat places
      else
        # FIXME: remove or replace with something more appropriate to track Google API error statuses
        console.log googleTypeName, status
      deferred.resolve(places, status)
  ...

I know, there is actually a lot going on here but the principal take aways that I want to stress out in this brief post are:

  • how to wrap a function (in particular an instance method of a Coffeescript object) in a promise
  • how to correctly handle the this (@ in Coffeescript) while wrapping the instance method in a promise and inside the AJAX callback
  • how to pass an arbitrary number of AJAX call (promises) and wait till their finish to perform some other stuff.
    Regarding this last point the main part is definitely the body of the method fetchPois where I use the syntactic sugar of Coffeescript’s splat operator ... to pass to $.when() function the wanted array of promises.

I hope that this brief excursus may be of some help to the devs not so prone towards frontend stuff like myself 😉

To the next time!
Cheers!

Leave a Reply

wpDiscuz