Routing

The router allows you to manage the state of URLs in your application.

import router from 'akili/src/services/router';
console.log(router === Akili.services.router); // true

router.add(state, pattern, options)

  • state string | Object - router state object or name
  • pattern string - url pattern to link with the state
  • options Object - other state options

Below we have created abstract state. It is abstract because doesn't have a template. The handling callback takes the transition - object with the full information about the current routing state. It is an instance of the router.Transition class. The last transition is always in router.transition property as well.

router.add('app', '/app', {
  handler: (transition) => {
    return 'App data';
  }
});

It is better to use ^ in the route pattern if it must be at the beginning of URL.

router.add('app', '^/app', {
  handler: (transition) => {
    return 'App data';
  }
});

Because, for example, /some/thing/app is also corresponds to the first pattern but not for the second.

router.init(defaultUrl, hashHistoryMode)

  • [defaultUrl] string - default url
  • [hashHistoryMode=true] string - use hash history mode or window.history
document.addEventListener('DOMContentLoaded', () => {
  router.init('/app', false);
  Akili.init().catch((err) => console.error(err));
});

The router must be initialized before the framework.

Transition

It is an object with all information about the routing.

transition.path includes all states found last time. If the parent path exists it will be in the transition.path.parent property and so on, recursively.

router.add('app.home', '/home', {
  handler: (transition) => {
    return 'App home data';
  }
});
router.add('app.home.hello', '/hello', {
  handler: (transition) => {
    return 'App home hello data';
  }
});

We have three states. Dots in the name separate the parent states.

  • app => /app
  • app.home => /app/home
  • app.home.hello => /app/home/hello

When we return something from the handler function, we add it to the corresponding path of the transition to data property. For example, if we move to app.home.hello state we will have:

  • transition.path.state.name === 'app.home.hello'
    transition.path.data === 'App home hello data'
  • transition.path.parent.state.name === 'app.home'
    transition.path.parent.data === 'App home data'
  • transition.path.parent.parent.state.name === 'app'
    transition.path.parent.parent.data === 'App'

In transition.routes you can find all the paths as an array. In transition.states is information about each state as an object with keys as state names.

router.state(state, params, query, hash, options)

  • state string | Object - router state object or name
  • [params] Object - url params
  • [query] Object - url query
  • [hash] string - url hash
  • [options] Object - state options
router.state('app.home.hello');
State params

To add a dynamic parameter to the URL use params.

router.add('app.outside', '/outside/:place/:time', {
  handler: (transition) => {}
});

router.state('app.outside', { place: 'library', time: 'afternoon' });
// => /app/outside/library/afternoon
State query

To add a query to the URL use query.

router.state('app.outside', { place: 'bar' }, { gender: 'male', eyes: 'black' });
// => /app/outside/bar?gender=male&eyes=black
State hash

If you need to add hash to the URL use hash argument.

router.state('app.outside', { place: 'station' }, {}, 'platform1');
// => /app/outside/station#platform1

router.location(url, options)

  • url string - URL
  • [options] Object - state options

When you change the route but its parents coincide with the previous ones they will not be updated again. Use the option { reload: true } to force it.

router.state('app.outside', { place: 'station' }, {}, '', { reload: true });
router.location('/app/outside/station', { reload: true });

If you want to save the scroll position after the state change pass { saveScrollPosition: true } option.

State change handling

Every time you change the state an event occurs in the window.

// before the change
window.addEventListener('state-change', (e) => {
  console.log(e.detail === router.transaction); // true
});

// after the change
window.addEventListener('state-changed', (e) => {
  console.log(e.detail === router.transaction); // true
});

Templates

In most cases when you work with routes you need to change not only the URL but also the html content. To do this use two options when you are creating the state.

router.add('app.car', '/car', { 
  title: 'My car', // document.title 
  template: '<my-car-component>${ this.__transition.data }</my-car-component>',
  handler: () => 'Tesla model S'
});

router.add('app.bus', '/bus', {
  title: (transition) => transition.data, // document.title 
  templateUrl: '/templates/bus.html'
  handler: transition => 'The bus title'
});

class MyCarComponent extends Component {
  compiled() {
    // simplified transition info about the current transition as a plain object in the scope
    console.log(this.scope.__transition); 

    // there is all information about the current route
    console.log(router.transition.path);
    console.log(router.transition.states['app.car']);
    router.transition.path === router.transition.states['app.car']; // true
  }
}
<div>
  <route></route>
</div>

All the patterns of the current state will be embedded in the route components according to their level of the nesting. Every route component scope.__transition contains all the necessary information about the corresponding transition. It is why inside the my-car-component component we'll see Tesla model S.

To change routes in your templates use a new attributes for a tag.

Component instead of a template

class BikeComponent extends Component {
  static template = '${ this.name }';

  static define() {
    Akili.component('bike', this);

    router.add('app.bike', '/bike', {
      component: this
    });
  }

  created() {
    this.scope.name = 'My bike';
  }
}

BikeComponent.define();

It is the same as:

router.add('app.bike', '/bike', {
  template: '<bike></bike>'
});

class BikeComponent extends Component {
  static template = '${ this.name }';

  static define() {
    Akili.component('bike', this);
  }

  created() {
    this.scope.name = 'My bike';
  }
}

BikeComponent.define();

Default state values

When you add the state you can set default values for params, query and hash. The default values has higher priority than the passed ones. But you can forcibly remove the necessary argument passing null.

router.add('app', '^/:lang', {
  params: {
    lang: args => args.params.lang || 'en'
  },
  query: {
    v: 2
    useless: 'oops'
  },
  hash: 'header1'
});

router.state('add', { lang: 'es' }, { v: 3, useless: null }, hash: null); // /es/?v=2

Redirecting

To redirect to another state inside the handler use transition.redirect().

router.add('app.flat', '/flat/:room', {
  handler: (transition) => {
    if(!transition.path.params.type) {
      return transition.redirect('app.flat', { room: 'kitchen' });
    }
  }
});

To cancel the transition use transition.cancel().

router.add('app.shapes', '/shapes', {
  handler: (transition) => {
    if(transition.path.query.shoes !== undefined) {
      transition.cancel();
    }
  }
});

Isolation

To change the URL bypassing the framework router use isolate method.

router.isolate(() => {
  window.history.pushState(null, '', '/change/myself')
});

router.reload(params, query, hash, options) Promise

  • [params] Object - url params
  • [query] Object - url query
  • [hash] string - url hash
  • [options] Object - state options

router.back()

router.forward()

router.go(position)

  • position integer - history position (window.history.go)