Hands on

Who wants ice cream!? — A HERE Maps API for JavaScript Tutorial (Part 5: Refactoring)

By Richard Süselbeck | 08 November 2016

Who wants ice cream?! — In this series of blog posts we are going to develop a small web application called "Gelary" using HERE maps and services. Gelary aims to disrupt the ice-cream market by enabling ice-cream producers to deliver their sweet goods directly to their customers, wherever they are.

The final application will resemble a mobile dashboard view for the employees of our little start -up which they can either take on the road or use at Gelary HQ to plan their work. It will consist of a map and a floating control panel behind a menu button. The map is used to visualise a route, starting from the user's current position along a series of customers, and the traffic situation along the route. The control panel will enable our delivery drivers to search for nearby ice-cream shops, display turn-by-turn directions as well as to calculate the best pick-up location for a group of selected customers. The app will also be able to handle order changes: e.g. if a customer cancels or updates his or her position, the route will automatically be recalculated.

To keep things simple our application will use plain Javascript (ES5) and front-end technologies and will target mobile and desktop browsers alike.

What will we learn in this tutorial?

In the previous post we had learned how to display multiple routes as well some detail information for each and enable the user to select the best one. If you haven't read it yet, you might want to take a look at it first before going on.

Since the last part was so long, we're going to take it easy this time and just do some refactoring to get our code ready for the next set of features. Let's get going.

Router

As already indicated in the last post, it's time to clean-up our route.js. Let's get to it before we dive into new features!

The first thing you might have been wondering about is actually the name. As we're now dealing with multiple routes and related functionality the singular Route is simply no longer appropriate. Let's change it from route.js to router.js and rename the contained class to HERERouter. This should cover our new scope nicely.

Let's keep going! Currently our class is a really just one constructor function which in turn contains a bunch of local functions. Let's make better use of JavaScript's prototypical inheritance model and move some functionality into actual instance methods. In this sense we'll introduce three new methods:

getRoutes will contain all code needed to retrieve the routes from the API:

  HERERouter.prototype.getRoutes = function(options, onSuccessCallback) {
  var onSuccess,
  onError;
   
  onSuccess = function (result) {
  if (result.response.route) {
  var routeLineGroup = new H.map.Group();
   
  var routes = result.response.route.map(function(route) {
  var routeLine = this.getRouteLine(route);
  routeLineGroup.addObject(routeLine);
   
  return {
  route: route,
  routeLine: routeLine
  };
  }.bind(this));
   
  onSuccessCallback(routes, routeLineGroup);
   
  } else {
  onError(result.response.details);
  }
  };
   
  onError = function (error) {
  console.error('Oh no! There was some communication error!', error);
  };
   
  this.router.calculateRoute(options, onSuccess.bind(this), onError);
  }
view raw gelary-part5-1.js hosted with ❤ by GitHub

getRouteLine will hold the code necessary to create the visual representation of the route and return a PolyLine instance:

  HERERouter.prototype.getRouteLine = function(route) {
  var routeShape = route.shape;
   
  // Create a strip to use as a point source for the route line
  var strip = new H.geo.Strip();
   
  // Push all the points in the shape into the strip:
  routeShape.forEach(function(point) {
  var parts = point.split(',');
  strip.pushLatLngAlt(parts[0], parts[1]);
  });
   
  // Create a polyline to display the route:
  var routeLine = new H.map.Polyline(strip,
  { style: this.routeLineStyles.normal }
  );
   
  return routeLine;
  };
view raw gelary-part5-2.js hosted with ❤ by GitHub

onRouteSelection will define the piece of code called once a route has been selected, e.g. via the panel:

  HERERouter.prototype.onRouteSelection = function(route) {
  if (this.selectedRoute) {
  this.selectedRoute.routeLine.setStyle(this.routeLineStyles.normal).setZIndex(1);
  }
   
  route.routeLine.setStyle(this.routeLineStyles.selected).setZIndex(10);
  this.selectedRoute = route;
  };
view raw gelary-part5-3.js hosted with ❤ by GitHub

With this change in place our constructor shrinks quite significantly in size:

  function HERERouter (map, platform) {
  this.map = map;
  this.router = platform.getRoutingService();
   
  this.routePanel = null;
  this.selectedRoute = null;
  this.routeLineGroup = null;
   
  this.routeLineStyles = {
  normal: { strokeColor: 'rgba(0, 85, 170, 0.5)', lineWidth: 3 },
  selected: { strokeColor: 'rgba(255, 0, 0, 0.7)', lineWidth: 7 }
  };
   
  var panelElement = document.querySelector('#route-panel');
  var routePanelOptions = {
  onRouteSelection: this.onRouteSelection.bind(this)
  };
  this.routePanel = new HERERoutesPanel(panelElement, routePanelOptions);
  }
view raw gelary-part5-4.js hosted with ❤ by GitHub

As you can see we had to make a few formerly local variables available across the instance for this update to work. Furthermore we have opted to create a routePanelOptions for readability instead of passing it into the HERERoutesPanel constructor inline.

Looks like a lot has changed but we really have just split our code into more digestible chunks which makes the end result right away a lot more maintainable. — Nice!

Map

With our new HERERouter class in place we'll need to update also the HEREMap class which relies on the routing functionality. In the constructor we instantiate the new class:

  this.router = new HERERouter(this.map, this.platform);
view raw gelary-part5-5.js hosted with ❤ by GitHub

With the router variable available across the instance we can also update the drawRoutes method and replace

  this.route = new HERERoute(this.map, this.platform, routeOptions);
view raw gelary-part5-6.js hosted with ❤ by GitHub

with the following:

  this.router.drawRoute(routeOptions);
view raw gelary-part5-7.js hosted with ❤ by GitHub

In the last installment of this series we had also hinted that the marker handling might require a bit of refactoring. It might make sense to extract the marker-related functionality at some point into its own class but for now we can get by by cleaning things up a bit. Let's start by creating a markers instance variable in the constructor to hold information on all of our markers:

  this.markers = {
  myLocation: null,
  origin: null,
  destination: null
  };
view raw gelary-part5-8.js hosted with ❤ by GitHub

With this change in place we can now simplify the updateMyPosition method:

  HEREMap.prototype.updateMyPosition = function(event) {
  this.position = {
  lat: event.coords.latitude,
  lng: event.coords.longitude
  };
   
  // Remove old location marker if it exists
  this.updateMarker('myLocation', this.position)
   
  // Draw the route from current location to HERE HQ if not yet drawn
  this.drawRoute(this.position, HEREHQcoordinates);
   
  this.map.setCenter(this.position);
  };
view raw gelary-part5-9.js hosted with ❤ by GitHub

As you can see we have introduced a new updateMarker method to help us with moving markers after they have been created. It takes the name of the marker (a key used with the aforementioned markers container object) and a coordinate pair as parameters.

  HEREMap.prototype.updateMarker = function(markerName, coordinates) {
  if (this.markers[markerName]) {
  this.removeMarker(this.markers[markerName]);
  }
   
  this.markers[markerName] = this.addMarker(coordinates, markerName);
  }

Finally we will make use of our new router variable in the drawRoute method by replacing

  this.route = new HERERoute(this.map, this.platform, routeOptions);

with

  this.router.drawRoute(routeOptions);

Wrapping up

House keeping isn't exciting work but it sure feels good once it's done! We can now feel a lot more comfortable moving forward and introducing new functionality and are fully set to continue on to the next part: Reacting to outside influences.