Exploring Riot.js – Route 66 (Takeaway)

Hey! Nice to see you for the last blog post of the series « Exploring Riot.js » (I promise, it’s the last one this time).
Last time, we saw how Riot.js routing API works with a simple Hello World example. While coding this example, I felt that it was a bit short, probably not enough to give an idea of how to start a « real » project with the Riot.js routing feature. So, I’ve carried on a bit more my investigations…

What's wrong with this code?

First of all, the routing code was declared in our index.html. That’s not terrible. Why not move it into a dedicated file. Let’s say: routes.js.

Second point: the code itself for handling the routing. I don’t like the endless succession of « if then else ». What if we had more routes than three? It’s ok for a short illustration of routing, but what about a « real » project? That’s the main point of this takeaway: let’s imagine what we would do if we were starting a « real » project.

Third point: the routes were basic. Only one part after the hash, and what about ids? Often, you have routes like http://my.amazing.app#users/1234/edit where 1234 represents an id of a user. In all the cases, somethings are subject to be variable.

So, how to handle the routes « properly »? Let’s try to achieve this with new amazing app, and as an image is worth than 1000 words! Here is the screenshot of the app:

Route66 app screenshot

  • the « Home » page is served by the route #home
  • the « About » page is served by the route #about
  • the « Users » page is served by the route #users
    • the « Profile » page of a user is served by the route #users/xxx/profile where xxx is the id of the user
    • the « Hobbies » page of a user is served by the route #users/xxx/hobbies where xxx is the id of the user

That’s it. Ok, let’s start with our project hierarchy:

your-app/
├── app/
│    ├── index.html
│    ├── css/
│         ├── users/
│         │    ├── user.css
│    │    └── main.css
│    └── js/
│         ├── about/
│         │    ├── about.js
│         │    └── about-page.tag
│         ├── home/
│         │    ├── home.js
│         │    └── home-page.tag
│         ├── users/
│         │    ├── user-card.tag
│         │    ├── user-hobbies.tag
│         │    ├── user-profile.tag
│         │    ├── user-service.js
│         │    ├── users.js
│         │    └── users-page.tag
│         ├── not-found-page.tag
│         └── routes.js
├── dist/
└── package.json

As you can see, we have gathered files under a package which represents the feature, hence, we have home, about and users. The other files are generic files, I’ve left them in the root of the js folder, but you are free to gather them under a util package.

The package.json looks like this one:

{
  "name": "route66",
  "version": "1.0.0",
  "description": "riot.js advanced routing sample with npm and es6",
  "main": "index.js",
  "scripts": {
    "compile": "riot --type es6 app dist",
    "watch": "riot -w --type es6 app dist",
    "browsersync": "browser-sync start --server --files 'dist/*.js, app/*.html' --startPath app",
    "serve": "parallelshell 'npm run watch' 'npm run browsersync'"
  },
  "dependencies": {
    "babel-core": "^6.18.2",
    "babel-preset-es2015-riot": "^1.1.0",
    "material-design-lite": "^1.2.1",
    "riot": "^3.0.1",
    "riot-route": "^3.0.1"
  },
  "devDependencies": {
    "babel-plugin-external-helpers-2": "^6.3.13",
    "browser-sync": "^2.18.2",
    "parallelshell": "^2.0.0"
  }
}

Yep, a new library: Material Design Lite. It’s just for the layout and it replaces Bootstrap (I’ve just wanted to try this Google’s library for fun…). The code of the index.html is almost empty from javascript code:

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>Hello World</title>
  <link rel="stylesheet" href="https://storage.googleapis.com/code.getmdl.io/1.0.4/material.red-orange.min.css" />
  <link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
  <link rel="stylesheet" type="text/css" href="./css/main.css" />
  <link rel="stylesheet" type="text/css" href="./css/users/user.css" />
</head>
  <body>
    <!-- Simple header with scrollable tabs. -->
    <div class="demo-layout-transparent mdl-layout mdl-js-layout">
      <header class="mdl-layout__header">
        <div class="mdl-layout__header-row">
          <!-- Title -->
          <span class="mdl-layout-title">Exploring Riot.js - Route 66</span>
          <!-- Add spacer, to align navigation to the right -->
          <div class="mdl-layout-spacer"></div>
          <!-- Navigation -->
          <nav class="mdl-navigation">
            <a class="mdl-navigation__link" href="#home">Home</a>
            <a class="mdl-navigation__link" href="#users">Users</a>
            <a class="mdl-navigation__link" href="#about">About</a>
          </nav>
        </div>
      </header>
      <div class="mdl-layout__drawer">
        <span class="mdl-layout-title">Exploring Riot.js - Route 66</span>
        <nav class="mdl-navigation">
          <a class="mdl-navigation__link" href="#home">Home</a>
          <a class="mdl-navigation__link" href="#users">Users</a>
          <a class="mdl-navigation__link" href="#about">About</a>
        </nav>
      </div>
      <main class="mdl-layout__content">
        <div id="content"></div>
      </main>
    </div>

    <!-- include riot.js -->
    <script src="../node_modules/riot/riot.min.js"></script>
    <!-- include riot's routes.js -->
    <script src="../node_modules/riot-route/dist/route.js"></script>

    <script src="../node_modules/material-design-lite/material.min.js"></script>

    <script src="../dist/js/not-found-page.js"></script>
    <script src="../app/js/routes.js"></script>
    <script src="../app/js/about/about.js"></script>
    <script src="../dist/js/about/about-page.js"></script>
    <script src="../app/js/home/home.js"></script>
    <script src="../dist/js/home/home-page.js"></script>
    <script src="../dist/js/users/user-card.js"></script>
    <script src="../dist/js/users/user-hobbies.js"></script>
    <script src="../dist/js/users/user-profile.js"></script>
    <script src="../app/js/users/user-service.js"></script>
    <script src="../app/js/users/users.js"></script>
    <script src="../dist/js/users/users-page.js"></script>

    <!-- mount normally -->
    <script>
      riot.mount('*');
      route.start(true);
    </script>
  </body>
</html>

The interesting code resides in the routes.js:

var currentTag = null;
var routes = {};

function mount(tag, options) {
  currentTag && currentTag.unmount(true);
  currentTag = riot.mount('#content', tag, options)[0];
}

function handler(collection, id, action) {
  var fn = routes[collection || 'home'];
  fn ? fn(id, action) : mount('not-found-page');
}

route(handler);

Have you noticed the empty routes object? This object will be filled will all routes of our application by the different features. For instance, the home.js adds the route declaration for the Home page as follow:

routes.home = function(id, action) {
  mount('home-page');
}

Hint: when you mount a tag attached to a div with an id as we have done it (<div id=”content”></div>), don’t use “id” as a key of the Riot.js opts object (e.g. riot.mount(‘my-tag’, {id: anId})). id is already taken by the object: it’s the id of the div (e.g. “content”). Your code will be messed up then. I’ve met this case, so if it can save you a bit time…

Objective met! The code organization is feature oriented!

Let’s have a look to a more complicated route: the Users‘ one.

var userService = new UserService();

routes.users = function(id, item) {
  if (id) {
    if (item == 'profile') {
      var userProfile = userService.fetchUserNameAndProfile(id);
      mount('user-profile', { user: userProfile });

    } else if (item == 'hobbies') {
      var userHobbies = userService.fetchUserNameAndHobbies(id);
      mount('user-hobbies', { user: userHobbies });

    }
  } else {
      var users = userService.fetchUsers();
      mount('users-page', {users: users});

  }
}

What have we done? If the id does not exist, it means our route is the users one (http://localhost:3000/#users). If the id exists, then it’s a route for a given user (e.g. http://localhost:3000/#users/xxx/profile) and we test the 3rd part of the route to mount the proper tag.

Shall we call the UserService to fetch the data and pass it to the tag or shall we pass the UserService and let the tag call it to fetch the data? That’s a question I asked myself when coding this sample. Actually, I’ve done it both ways and I don’t think there is an absolute answer. Nonetheless, I tend to prefer to pass the data to the tag and not the service: the tag is thus decoupled from the service. Furthermore, it limits the scope of the tag: it has access to only what it needs. Accessing the service gives it much more possibilities. I think the tag is more « reusable » too with static data: no need to mock a pseudo service, we just pass an object we have forged manually.

This way also reminds me of  the AngularJS UI-Router library which enables you to call services (often async services) or other stuff in a resolve section before the initialization of the controller. I’ve found this way to be pretty neat.
Whichever way you choose, you can see the above code also enables you to add some logic before mounting the tag (e.g. clearing a user session, adding ACLs checks, and so on). So, this prevents the tag from dealing with business logic related to the app and, thus, potentially makes it more reusable.

Hint: As you can see our code is written in ECMAScript 2015 (ES6). If you’re using a recent version of Node.js, it natively supports it (see here and here for details). Otherwise, you have to tell Babel to transpile this code too.

As we already use babel-core for transpiling code in our Riot components, we can use the require hook to compile code in javascript files.

First of all we need to create a main.js file to call babel-register and give it our service’s filename:

require('babel-core/register');
require('./app/js/routes.js');
require('./app/js/about/about.js');
require('./app/js/home/home.js');
require('./app/js/users/user-service.js');
require('./app/js/users/users.js');

Then we are just going to add a new task in our serve task to compile it:

{
   ...
   "scripts": {
    ...
    "serve": "parallelshell 'node main.js' 'npm run watch' 'npm run browsersync'"
  },
  ...
}

Just a takeaway inside the takeaway… Here is the same code with an asynchronous UserService which has a delay of 3 seconds to simulate the asynchronous behavior:

var userService = new UserAsyncService();

routes.users = function(id, item) {
  mount('loading');
  
  if (id) {
    if (item == 'profile') {
      userService.fetchUserNameAndProfile(id)
        .then(userProfile => {
            mount('user-profile', { user: userProfile });
         });

    } else if (item == 'hobbies') {
      userService.fetchUserNameAndHobbies(id)
        .then(userHobbies => {
            mount('user-hobbies', { user: userHobbies });
        });

    }
  } else {
      userService.fetchUsers()
        .then(users => {
          mount('users-page', {users: users});
        });
  }
}

Here, we mount a loading tag (a spinner wheel) then we call the asynchronous service to fetch the data and once the data has been fetched, we mount the proper tag.

The code only deals with routes of 3 parts, you will probably need to refactor a bit to handle longer routes but you get the idea!

The code is on our GitHub (route66 branch). The code with the synchronous service is under the tag sync and the code with the asynchronous service is under the tag async.

Ok, that’s almost all. I hope you’ve enjoyed this series of « Riot.js Exploration »!

May be a conclusion before leaving?

Conclusion

I’ve really enjoyed coding with Riot.js while exploring this library. It’s a micro-library but it’s a library that offers all the essential features to develop nice apps: custom tags, eventing and routing. All the things you usually need for an app.
The learning curve is also small, since the library is tiny.

What else to say? It’s a React-like library as React, Riot.js plays with a virtual DOM, but as far as React, I’m not a big fan of declaring inside javascript all the html layout. Well, I am not a React expert either. I’ve just read some articles and made a small contribution to DevConferences.org during a nice BreizhCamp hands on lab. I like the fact you declare your UI inside html tags in Riot.js.  and I also like the fact the component holds both the html and the javascript. It makes easier to see the relationship between the two.
As React, Riot.js can be used in a Flux architecture (see RiotControl or Flux-Riot).

Compared to AngularJS, I think it’s very straightforward to start a project with Riot.js while with AngularJS, you need to declare all the plumbing materials (injection, controllers, etc.). Had I known Riot.js earlier, I would have probably used it to code our APISpark Quizz demo and have probably saved time. I’m not an expert in AngularJS 2 either,  but I just had a look at some posts and I must admit I am a bit perplexed about all the knowledge you need before starting coding. Templating?!? You need to know when to use () and [] and where. What the f…??? With Riot.js, the templating syntax is simpler: use {} (as it was with AngularJS 1).

It’s the same thing with EmberJS v1.x.y: Riot.js looks simpler (by the way, it has also some Mixin stuff as EmberJS).

The last feature I haven’t tried, but what can be interesting is the ability to run Riot.js in the server-side. This enables you to develop an isomorphic architecture (a bit of an obscure name in my opinion) also known as Universal Javascript (a bit of a pedantic name, in my opinion as well).

So, to sum up: Give Riot.js a try, and tell us if you are fond of it!

Give Riot.js a try, you will love it!

Share it :
0010

Give it a try!

Try streaming any JSON REST API within 30 sec
curl -v "https://proxy.streamdata.io/http://mysite.com/myJsonRestService?param1=[]&param2=[]"

2 thoughts on “Exploring Riot.js – Route 66 (Takeaway)

  • Do all of the compiled tag files transfer (or not) to the client when the user visits the root path (index.html)? If so would this be a performance issue?

    • Hello,
      Thanks for reading.
      Yes, in the example, the compiled tags are all transferred. Note that here, the compilation is done before. We don’t use the compiler to compile the tag when the page is loaded. Riot team says the in-browser-compiling has a tiny overhead.
      Coming back to your question, in production, you will probably use some tools that concatenates and minifies the js. The Riot.js documentation also mentions the possibility to use Riot.js with module loaders (AMD, CommonJS, etc.). From what I see, there is nothing special in Riot.js preventing from applying the common « best » web practices with js files. I haven’t done it in the example to keep it simple and keep the reader focus on the « basics » of Riot.js.

Leave a Reply

Your email address will not be published. Required fields are marked *