Exploring Riot.js – En route to…

Welcome back to our Riot.js exploration!

This time, we will explore the routing feature which is the last main feature of Riot.js and will enable you to code amazing apps. This post will also conclude our series « Exploring Riot.js » (don’t be too sad…).

What is a router?

What is a router?Good question. A router is a component that deals with the URLs of your app and the back button of your browser. The Riot.js router can:

  • change the hash # part of your URL app
  • notify when hash # changes happen
  • study the current hash #

As you’ve noticed, we keep talking about hash #. That’s because we are also talking about single page applications. In such applications, the hash is often the master piece of routing: what comes next to the hash often defines a piece of UI to be displayed in the browser page. By the way, what comes next to the hash is called a route.

From my personal experience, when handling routing, you need to pay close attention to how you access the URL. Actually, you’ve got two ways:

  • what I call « programmatically »: you click on a button, a link, etc. of your app and you are redirected to another part of your app. Let’s say that you are on the Welcome page of your app. You click on your profile icon and the app displays the UI with your account and the URL of your app has also changed (let’s say, you’ve passed from the route http://my.amazing.app#welcome to the route http://my.amazing.app#users/1234/profile)
  • direct access: for me, the direct access is when you access the UI portion of your app directly by entering the URL in your browser (e.g. you’ve entered http://my.amazing.app#users/1234/profile in your browser)

Usually, changing the hash portion of the URL in a single page app often means using some logic (e.g. loading a new UI page or / and prefetching some data) before accessing the URL.  Depending on the framework, these two cases are dealt with transparently or a little bit « differently ». For instance, AngularJS router does not make the distinction while as far as I remember, there was some subtlety in dealing with the two ways with EmberJS (at least v1.x.y)…

What about Riot.js? For Riot.js, it’s the second case, the two ways are « managed » slightly differently, but, notice the « slightly »… because, I really think it’s so « slight » that you may even not notice it when developing a Riot.js app.

«Programmatically» access

When you access «programmatically» to another hash part of the app, Riot.js offers you to react to this change by passing a callback:

route(function(part1, part2, part3) {
 // this is the callback 
});

Note the parameters of the callback: I’ve called them “part1”, “part2”, “part3”. Meaningless isn’t it? Well, if we take our URL example http://my.amazing.app#users/1234/profile, “part1” corresponds to “user”, “part2” to “1234” and “part3” to “profile”.

In the Riot.js examples, they took “#customers/987987/edit” as route example and their callback looks like:

route(function(collection, id, action) {
 // add your logic here in the callback
});

Thus, “collection” is for “customers”, “id” for “987987” and “action” for “edit”. That’s a good naming method,  but  I’ve first deliberately named the parameters “part1”, …, “part3” because you may think the callback can only take three parameters. Not at all! Your callback can have many parameters as they are words separated by a ‘/’ after the hash. For instance, if your URLs look like “#users/1234/comments/23” then your callback can be defined as:

route(function(part1, part2, part3, part4) {
 // add your logic here in the callback
});

As Riot doesn’t start its router automatically, it’s up to you to start it by yourself once you’ve defined your routes and their associated callbacks with the start() method:

route(function(part1, part2, part3, part4) {
 // add your logic here in the callback
});

route.start();

Pretty neat and simple, isn’t it?

Direct access

Now, what about direct access? To allow direct access to your ressources, you simply need to add a call to the exec() method once you’ve started your router which gives us the following code:

route(function(part1, part2, part3, part4) {
 // add your logic here in the callback
});

route.start();
route.exec();

But you can even simplify this by calling the start() method with the autoexec parameter set to true, which transform the previous code into this:

route(function(part1, part2, part3, part4) {
 // add your logic here in the callback
});

route.start(true);

Where is the example ?

If you don’t call the exec(); method directly or through the start(true);, you will have no error when calling the route directly, but you will not be able to see its content!

Wait a minute! You haven’t explained how you access «programmatically» to a route! Keep cool, we will see it in the example… :)

Concrete example

Routing Sample - Screenshots

The example is quite simple, we have 3 pages:

  • a «Home» page served by the route http://localhost:3000/#
  • a «Hello» page served by the route http://localhost:3000/#hello
  • a «Bonjour» page served by the route http://localhost:3000/#bonjourSo, let’s start!As for our previous posts about Riot.js, we manage our dependencies and the build phases with npm. If you don’t have npm yet, I advice you to install it with nvm, a really nice tool that can manage several npm versions.

    Here is our project hierarchy:

    your-app/
    ├── app/
    │    ├── index.html
    │    ├── css/
    │    │    └── main.css
    │    └── js/
    │         ├── bonjour-page.tag
    │         ├── hello-page.tag
    │         └── home-page.tag
    ├── dist/
    └── package.json
    

    And the package.json looks like this one:

    {
      "name": "routing",
      "version": "1.0.0",
      "description": "riot.js routing sample with npm and es6",
      "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",
        "bootstrap": "^3.3.7",
        "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"
      }
    }

    As you can see, we have to declare a specific dependency for the router: riot-route. Since version 2.3, the router is an independant module, which means that you’re free to use another router in your Riot application and to use the Riot router in any other application. Nice, isn’t it?

    Now, let’s take a seat because our pages are quite complicated…

    The home-page.tag looks like:

    <home-page>
      <h1>Welcome!</h1>
      <p>This is the Home page</p>
    </home-page>
    

    While the hello-page.tag is declared as follow:

    <hello-page>
      <h1>It's time to say « Hello ! »</h1>
      <p>That's obviously the Hello page...</p>
    </hello-page>
    

    … and the bonjour-page.tag:

    <bonjour-page>
      <h1>Il est temps de vous dire « Bonjour ! »</h1>
      <p>Il semblerait que vous soyez sur la page qui vous dit <strong><em>Bonjour</em></strong>...</p>
    </bonjour-page>
    

    The main.css file is quite complex too:

    body {
        margin: 10px 30px 10px 30px;
    }

    Ok. Enough joking! Let’s have a look at the routing logic. It’s all in the index.html:

    <!doctype html>
    <html lang="en">
    <head>
      <meta charset="utf-8">
      <title>Hello World</title>
    
      <link rel="stylesheet" type="text/css" href="../node_modules/bootstrap/dist/css/bootstrap.min.css" />
      <link rel="stylesheet" type="text/css" href="./css/main.css" />
    </head>
      <body>
        <a href="#">Home</a>
        <a href="#hello" class="btn btn-info">Go to «Hello»</a>
        <button type="button" class="btn btn-success" onclick="goToBonjour()">Allez sur «Bonjour»</button>
    
        <!-- mount point -->
        <div id="content"></div>
    
        <!-- include riot.js -->
        <script src="../node_modules/riot/riot.min.js"></script>
        <!-- include riot route.js -->
        <script src="../node_modules/riot-route/dist/route.js"></script>
        <script src="../dist/js/home-page.js"></script>
        <script src="../dist/js/hello-page.js"></script>
        <script src="../dist/js/bonjour-page.js"></script>
    
        <!-- mount normally -->
        <script>
        function goToBonjour() {
          route('bonjour');
        }
    
        var currentPage = null;
    
        route(function goTo(path) {
          if (currentPage) {
            currentPage.unmount(true);
          }
    
          if (path === 'bonjour' ) {
            currentPage = riot.mount('div#content', 'bonjour-page')[0];
    
          } else if (path === 'hello') {
            currentPage = riot.mount('div#content', 'hello-page')[0];
    
          } else {
              currentPage = riot.mount('div#content', 'home-page')[0];
    
          }
        });
        route.start(true);
        </script>
      </body>
    </html>
    

    First of all, we’ve declared a navigation menu: «Home», «Go to Hello» and «Allez sur Bonjour».

    <a href="#">Home</a>
    <a href="#hello" class="btn btn-info">Go to «Hello»</a>
    <button type="button" class="btn btn-success" onclick="goToBonjour()">Allez sur «Bonjour»</button>
    

    Well, not a very consistent menu… Don’t worry, we’ve done it to show what we’ve called «programmatic access». The first two navigation items are hyperlinks that enable to go to our «Home» and «Hello» pages thanks to the declaration of a href. That’s the first style of « programmatic access ».

    The third navigation item is declared with a button and its listener onclick=goToBonjour(). and the goToBonjour() makes a call to the route.js API:

    function goToBonjour() {
      route('bonjour');
    }

    That’s the second style of «programmatic access»: via an API call to riot.route(myRoute). This time, riot.route(.) doesn’t take a callback, but a string. The string is the path of the URL of the hash. If we take the example of our introduction, going to http://my.amazing.app#users/1234/profile would be done by calling:

    riot.route('users/1234/profile');

    That’s pretty simple, no?

    Let’s go on our code road trip. We have defined a div with an id=”content”.

    <div id="content"></div>

    This div will act as a place holder: depending on the route selected, we will insert the UI of the route in this div. How do we do that? By mounting the tags, naturally! That’s our callback function which has the original name of goTo(.):

    var currentPage = null;
    
    route(function goTo(path) {
      if (currentPage) {
        currentPage.unmount(true);
      }
    
      if (path === 'bonjour' ) {
        currentPage = riot.mount('div#content', 'bonjour-page')[0];
    
      } else if (path === 'hello') {
         currentPage = riot.mount('div#content', 'hello-page')[0];
    
      } else {
         currentPage = riot.mount('div#content', 'home-page')[0];
    
      }
    }
    

    Note that we register the current page in variable currentPage. This is the Riot.js tag element returned by the riot.mount(.) function. Actually, the first one because riot.mount(.) return an array of the mounted tags. In our example, we assume that only one type of page tag is mounted. Hence the fact we happily take the first element without any defensive code (come on, that’s a sample…).

    Note also that before mounting the tag corresponding to our page, we first ensure to unmount the current page if it exists. The true value passed to the unmount(.) function tells Riot.js to remove the tag but not its parent (that is to say, our <div id=”content”><div>).

    The last piece of code start the router with the autoexec parameter:

    route.start(true);

    So, in wrapping things up, the code is in our GitHub (routing branch).

    That’s all… or almost all… I wanted to give you a last take away (and perhaps a conclusion) but scrolling up to the top, it seems to be a fairly looonnng article! So, let’s move our take away to another blog post…

    Exploring Riot.js – Route 66 (Takeaway)

    Missed previous entries on riot.js?

    Exploring Riot.js – Introduction

    Exploring Riot.js – Get your hands dirty

    Exploring Riot.js – Event-driven app (Step1)

    Exploring Riot.js – Event-driven app (Step2)

    Exploring Riot.js – Event-driven app (Step3)

Share it :
0000

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=[]"

Leave a Reply

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