Skip to Content

Intro (why & what)

For a delivery service we needed an easy solution to figure out the most optimized route to deliver. There were some services online (paid) but they had too many options and were not easy to use.

So we decided to create an application that could generate the most optimal delivery route by using publicly accessible APIs. To save time during development we have cut some corners, but we will give a heads up when applicable.

We will go through some points of the application that are worth mentioning. We have added the repository so you can download the application and run it yourself. 

 

URLs

3rd party API’s/Libs used for this application

 

Application url

Repo url

 

Running the project

Fork or download the project from the bitbucket url.

Navigate with terminal/cmd to the folder and run the command ‘npm install’

Commands

  • To run the project type ‘gulp’ inside the folder.
  • To build the project to a dist folder type ‘gulp build’.

After running the command ‘gulp’

Project setup

To quickly start the project we used Yeoman

Yeoman is a project generator to quickly setup a project with best-practice tools already set up. You can create your own generator or use one that is created by someone from the community.

For this project we used an existing generator

We chose the one from Soontao because he created a generator that generates an application based on the following (great) tools:

 

Babel

Writing ES6 in source code makes your life so much easier 🙂

Gulp

Automating work who doesn’t want this (during development: refresh page after code changes, for distribution: check/fix code with eslint -> generating component-preload -> minifying/uglifying files -> write everything to dist folder that you can upload (this part you can also automate)

Browsersync

Perfect for testing front-end applications. you can test the application on mobile/desktop at the same time. It even has a internal server to run the application locally.

 

Initiating the AppData Model

We chose to add the model in manifest.json and add some initial data during project setup.

"AppData" : {
 "type": "sap.ui.model.json.JSONModel"
}
/**
   * initialize AppData model with empty values
   *
   */
  setupAppData: function(){
    // set initial data for AppData model
    let oModel = this.getModel('AppData');
    oModel.setProperty('/routes', []);
    oModel.setProperty('/iStartingId', 1);
    oModel.setProperty('/iEndingId', 1);
    oModel.setProperty('/bHasRoute', false);
    oModel.setProperty('/bHasStreets', false);
  }

BUT in a complex project we wouldn’t have done it like this…

Just for the simple reason manifest.json gets quickly cluttered. In more complex projects you will have much more models (view models, data models and so on).

So probably we would have added a viewmodel folder and created the model with an extension on a JSONmodel and initiate it at the right place.

 

Use of a BaseController

Because we want to save on duplicating code we use the power of OOP by writing most-used methods of controllers in a ‘Base Controller.

This also helps in reducing complexity or rewriting parts of code. We like to put all our “get something”- code in a shared controller. That way we don’t have to import it in all controllers and just always use “this.getSomething()”.

All controllers extend upon this Base Controller so they can use all the functionality that is already available.

 

 

Binding GoogleMaps to the view

To keep google maps or google autocomplete binded to the view we have to do the binding during the ‘onAfterRendering’ event of a view. Because we found out if you just bind it once and navigate away or update a model (that causes a redraw for the input) it loses its binding.

This is because the Tab View Control removes its content when switching to another tab. Every time you switch back to the Map tab, we have to bind the Google Maps and Google Autocomplete to the newly created elements.

 

  /**
   * On after rendering method bind google autocomplete
   *
   * @param {*} oEvent
   * @memberof Overview
   */
  onAfterRendering(oEvent) {
    this.bindGoogleSearch();
  }

 

Sending big chunks of data to another View/Controller

We are using the eventbus to send big parts of code to another view. It is really easy to use.

In essence it works like an event listener. You create a channel and send data to that channel. Another “view” is listening to this channel. The moment some new data exists it will run the method that you defined as the handler.

 

 

  /**
   * Calculate the route based on the places in the list and publish to listener
   *
   * @memberof Overview
   */
  calculateRoute() {

    this.onViewRoute();

    // set that there is a route
    this.oAppModel.setProperty('/bHasRoute', true);
    // reset models
    this.resetModels();
    // set special point
    this.setSpecialPoints();

    this.GrapHop.calculateRoute().then(json => {
      this.getEventBus().publish("HomeChannel", "onNewJson", { json: json });
    });
  }

Sending data with eventbus

 

  /**
   * Initialization
   *
   * @memberof Detail
   */
  onInit() {
    this.getEventBus().subscribe("HomeChannel", "onNewJson", this.onNewJson.bind(this));
  }

Create subscriber for the eventbus on detail view onInit method

  /**
   * Event handler when a new json response is pushed through eventbus
   *
   * @param {*} sChannel
   * @param {*} sEvent
   * @param {*} oData
   * @returns
   * @memberof Detail
   */
  onNewJson(sChannel, sEvent, oData){
    if (oData.json["message"] === "Bad Request") {
      this.showBadRequestMessageBox();
      return;
    }
    this.json = oData.json;
    let fTime = this.json.paths[0].time / 3600000; //divide by 3600000 for hour
    this.oAppModel.setProperty("/fTime", fTime.toFixed(2));
    let iDistance = this.json.paths[0].distance / 1000; // divide by 1000 for km
    this.oAppModel.setProperty("/iDistance", iDistance.toFixed(2));

    this.setGoogleMap();

    this.bSorted = false;
  }

The method that runs

 

Fragments with Controllers

The use of fragments in UI5 is nice but we found it difficult to maintain the code easily if the fragments get complex. Because officially you have to write all the code in the parent controller.

Luckily we found a project that addressed this problem. By using this methods when we are calling the fragments we can keep using the already familiar MVC concept also for the fragments.

We have extended the method a bit by adding a small part to the method so that you can close a popup if you click outside the box and to use it also for popovers.

 

Fragments have their own controllers

 

 

  /**
   * Event handler for the button to open the favourites dialog
   * Opens the favourites dialog
   *
   * @memberof Overview
   */
  onOpenFavouritesDialog() {
    this.openFragment("com.booking.routemii.view.fragments.ManageFavouritesDialog", null, true, true, false, false);
  }

Calling the fragment

Use of Singleton

Our two custom classes (GrapHop & Helper) are singletons. We did that because we want to use one and the same object during the whole application. Because we are reading and writing to the same object during different views of the application.

 

  /**
   * Get the Helper object
   *
   * @returns {Helper} Instance
   * @memberof BaseController
   */
  getHelper(){
    return Helper.getInstance();
  }

Returning the instance (in BaseController)

 

Last remarks

As you maybe already noticed we didn’t use any viewmodels in this project. We could have done that to bind all properties of a control in the view to a model.

That is much cleaner solution than using variables or calling a control to check if something is enabled/hidden or just general variables that are used in a view.

And yes it’s never a good idea to put any key in the front-end code. (like we did for graphopper/google maps api). You can hide the key with the use of a server (XSA/Nodejs) and create a ‘bridge’.

 

This is the first time for me writing a blog so if you have any question leave a comment or contact me / Enne van Ziel 

 

Next steps

If you want you can extend the application to add parameters for the Graphopper API. For example if you want you can add options to let the driver stop every 4 hours or 400 km or you can add more vehicles to share the load and even how much one car can do.

The possibilities are endless 🙂

 

Small update (19/06/18)

The API limit (1500 calls a day) was hit a couple of times causing the application not to work. I have added a error message if that is the case. The best thing you can do is download/fork the application and create an own API key at Graphopper

To report this post you need to login first.

3 Comments

You must be Logged on to comment or reply to a post.

Leave a Reply