Reverse Routing in Polymer Apps


Thu 29 September 2016 By André Justo

As you may know, we recently released our first preview of the new Web UI built with Polymer. When we started building it, we used the Polymer Starter Kit as a starting point for our application. Currently, this kit is using the recent routing elements (app-route and app-location) to handle the routing on the application, but back when we started, it used to delegate that responsibility to page.js instead, which in fact is still the solution we use in our application.

Building big and content-rich platforms such as the Nuxeo Platform implies following a set of good practices to provide good usability when using a web application, and today we will be focusing on the generation of URLs.

An URL plays an important role when navigating in an application, because it allows people to quickly navigate to a specific page, share it with a coworker or even save a bookmark for reference. Providing links in web applications is usually very easy - we just need to add a bunch of <a> tags and we are ready to go.

<a href=”/home”>Home</a>

However, we must not forget that we are dealing with Polymer and web components in general and that they are designed to provide encapsulation, composition, and separation of concerns. Moreover, each element might be used in totally different apps or contexts with their own routing approach or even with no need for actual links at all.

Another potential problem in big applications is having too many hard coded links spread across different places, which can be painful in case of maintenance or refactoring.

Keeping this in mind, we need to give the elements the ability to know how to generate an URL for a given route. This process is also known as reverse routing.

The Solution

Before diving into the code, it’s important to note that this solution does not depend on any specific client routing library. In our samples, we are going to use page.js just because that’s the library currently used by us.

Let’s start by defining some sample routes for our application:

 page('/home', function() {
   // home page route
 });

 page('/document/:id, function() {
   // route for a specific document
 });

 page('/admin/user-group-management/:type/:id, function() {
   // route to manage a user or a group
 });

As you can see, creating routes with page.js is fairly easy: you just need to specify the paths that will be handled by the router, and a callback function to do whatever you want if needed - nothing new here. Now that we have our routes, we just need to add <a> tags in our elements with the proper href attribute to create links to our pages. But while the home route may be used once or twice in an application, URLs to documents or users/groups will appear several times in different elements (listings, searches and so on) - and that’s when our solution comes handy.

We introduced a Polymer behavior called Nuxeo.RoutingBehavior which provides a function-type property named urlFor that should be used to generate an URL for a given route.

Each element with this behavior, is now able to generate proper URLs:

<a href$="[[urlFor('document', document.uid)]]">My document</a>

Note that instead of hardcoding the URL directly, we give names to our routes. This way, we add flexibility to our elements allowing us to have different URLs if the element is used in two different apps - for instance, in application A the URL for the document page could be /my-document/:id while in application B the URL could be /document-page/:id.

Let’s take a look at what’s happening behind the scenes: the urlFor function takes as arguments the name of the route and other additional parameters that will be used to produce the URL.

Nuxeo.RoutingBehavior = {
  properties: {
    urlFor: {
      type: Function,
      notify: true,
      value: function() {
        return this.generateUrl;
      }
    }
  },

  generateUrl: function() {
    if (this.router) {
      var route = arguments[0];
      if (route.startsWith('/')) {
        return this.baseUrl + route;
      }
      if (!this.router[route]) {
        console.error('Could not generate a url for route ' + route);
        return;
      }
      var params = Array.prototype.slice.call(arguments, 1);
      return this.router[route].apply(this, params);
    }
  }
}

If you look closely, you can see that there’s a router object being used several times. This object holds all our named reverse route handlers and having it as a property allows you to easily set a custom router for your elements. In the context of an application, we'd like all the route enabled elements to share the same set of routes so we can just use events to trigger an update when our routes are (re)defined, like we did with our i18n helper, or even add it to all Polymer elements with:

Polymer.Base._addFeature({
  router: {
    home: function() {
      // returns url for homepage
      return page.url('/home');
    },
    document: function(id) {
      // returns url for a document page
  return page.url('/document/' + id);
    }
  }
});

For each route defined in the application, we need to implement the function (stored in the router object) that is responsible for generating the URL for that route - there’s no magic here. This shouldn’t be hard though, and for sure will be useful in the long term if by any chance you need to slightly change some of the URLs of your application.

And that’s it! We let page.js handle the routing in the application, and with the help of our behavior we can add links in our elements in a decoupled way.

Check out our elements catalog to learn more about the elements and tell us what you think!


Tagged: Polymer, How to, Nuxeo-Platform