Routing

Another important feature in Single Page Application development is routing, this client-side routing allows users to navigate through our application where no full page reload will happend, this way you can use $http service to fetch partial information from the server, you can also use history buttons without full page reload.

The routing configuration is done in the istance configuration object, but the navigation is done through the $router.navigate service.

A simple SPA with Routing

JSONPlaceholder is a Fake Online REST API for Testing and Prototyping and we will use to show its entities in a SPA.

We will use the following resources from JSONPlaceholder.

Resource Description url
posts 100 posts https://jsonplaceholder.typicode.com/posts
comments 500 comments https://jsonplaceholder.typicode.com/comments?postId=1
todos 200 todos https://jsonplaceholder.typicode.com/todos
users 10 users https://jsonplaceholder.typicode.com/users

Since these resources have relations, we can create a simple interface to navigate through these relations, like posts and comments related to posts, or posts related to users.

HTML

<div id="app" class="tabset">
  <!-- Tab 1 -->
  <input type="radio" name="tabset" id="tab1" aria-controls="posts" checked>
  <label for="tab1" tp-on:click="navigate('')">Posts</label>
  <!-- Tab 2 -->
  <input type="radio" name="tabset" id="tab2" aria-controls="todos">
  <label for="tab2" tp-on:click="navigate('todos')">Todos</label>
  <!-- Tab 3 -->
  <input type="radio" name="tabset" id="tab3" aria-controls="users">
  <label for="tab3" tp-on:click="navigate('users')">Users</label>
</div>

<router-view></router-view>

<section id="posts" class="tab-panel">
  <h2>Posts</h2>
  <ul>
    <li tp-for="post in posts">{{ post.title }}</li>
  </ul>
</section>
<section id="todos" class="tab-panel">
  <h2>Todos</h2>
  <ul>
    <li tp-for="todo in todos">{{ todo.title }}</li>
  </ul>
</section>
<section id="users" class="tab-panel">
  <h2>Users</h2>
  <ul>
    <li tp-for="user in users">{{ user.name }}({{ user.username}}) - {{ user.email }}</li>
  </ul>
</section>

JavaScript

// navigation app
new Tulipan({
  el: '#app',
  data: {
  },
  methods: {
    navigate: function (page) {
      this.$router.navigate("/" + page);
    }
  }
})

// posts app
new Tulipan({
  el: '#posts',
  route: "/",
  data: {
    posts: []
  },
  methods: {
    after: function(){
      this.fetchPosts();
    },
    fetchPosts: function () {
      this.$http.get('https://jsonplaceholder.typicode.com/posts')
        .then(function (res){
          this.$set("posts", res.data);
        }, function(err){
          console.log(err);
        }) 
    }
  }
})

// todos app
new Tulipan({
  el: '#todos',
  route: "/todos",
  data: {
    todos: []
  },
  methods: {
    after: function(){
      this.fetchTodos();
    },
    fetchTodos: function () {
      this.$http.get('https://jsonplaceholder.typicode.com/todos')
        .then(function (res){
          this.$set("todos", res.data);
        }, function(err){
          console.log(err);
        }) 
    }
  }
})

// users app
new Tulipan({
  el: '#users',
  route: "/users",
  data: {
    users: []
  },
  methods: {
    after: function(){
      this.fetchUsers();
    },
    fetchUsers: function () {
      this.$http.get('https://jsonplaceholder.typicode.com/users')
        .then(function (res){
          this.$set("users", res.data);
        }, function(err){
          console.log(err);
        }) 
    }
  }
})

Which will render

Posts

  • {{ post.title }}

Todos

  • {{ todo.title }}

Users

  • {{ user.name }}({{ user.username}}) - {{ user.email }}

Adding subroutes

Each resource display a list of elements in which each element has its own detailed data and information. The idea now is to display a post body a its comments.

Let's add a new section to show post details

HTML

<section id="post-detail" class="tab-panel">
  <h2>Post</h2>
  <b>{{ post.title }}</b>
  <p>{{ post.body }}</p>
</section>

Let's create a new Tulipan instance to handle this template

JavaScript

new Tulipan({
  el: '#post-detail',
  route: "/posts/:postId",
  data: {
    post: {}
  },
  methods: {
    after: function(params){
      var postId = params.postId;
      this.fetchPost(postId);
    },
    fetchPost: function (postId) {
      this.$http.get('https://jsonplaceholder.typicode.com/posts/' + postId)
        .then(function (res){
          this.$set("post", res.data);
        }, function(err){
          console.log(err);
        }) 
    }
  }
})

Let's add a link to navigate to each post

HTML

<section id="posts" class="tab-panel">
  <h2>Posts</h2>
  <ul>
    <li tp-for="post in posts">
    {{ post.title }}
    <a href="javascript:void(0)" tp-on:click="viewPost(post.id)">View</a>
    </li>
  </ul>
</section>

and a fix to the posts JavaScript application in order to navigate on click.

JavaScript

new Tulipan({
  el: '#posts',
  route: "/",
  data: {
    posts: []
  },
  methods: {
    after: function(){
      this.fetchPosts();
    },
    fetchPosts: function () {
      this.$http.get('https://jsonplaceholder.typicode.com/posts')
        .then(function (res){
          this.$set("posts", res.data.slice(0, 20));
        }, function(err){
          console.log(err);
        }) 
    },
    viewPost(index){
      this.$router.navigate("/posts/" + index);
    }
  }
})

All these will render the following.

Posts

  • {{ post.title }} View

Todos

  • {{ todo.title }}

Users

  • {{ user.name }}({{ user.username}}) - {{ user.email }}

Post

{{ post.title }}

{{ post.body }}

Handling Parameters

You can handle parameters inside the after special methods buy supplying the first argument.

JavaScript

new Tulipan({
  ...
  route: "/posts/:postId",
  data: {
    post: {}
  },
  methods: {
    after: function(params){
      var postId = params.postId;
    },
    ...
  }
})

the params argument is an object with property names equal to the format provided in the route option.

Handling Query Strings

You can also handle query strings inside the after special method buy supplying a second argument.

JavaScript

new Tulipan({
  ...
  route: "/posts/:postId",
  data: {
    post: {}
  },
  methods: {
    after: function(params, query){
      // If we have /posts/12?sortby=inc as a url then  
      var postId = params.postId;
      // query = sortby=inc
    },
    ...
  }
})