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) undefined
- state string | Object - router state object or name
- pattern string - url pattern to link with the state
- options Object - other state options
Add a new state to the router. You can pass an object as the first argument with "state" and "pattern" as properties
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) undefined
- [defaultUrl] string - default url
- [hashHistoryMode=true] string - use the hash history mode or window.history
Initialize the router.
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) undefined
- state string | Object - router state object or name
- [params] Object - url params
- [query] Object - url query
- [hash] string - url hash
- [options] Object - state options
Go to the necessary route by state.
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) undefined
- url string - URL
- [options] Object - state options
Go to the necessary route by url.
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() {
// corresponding transition info
console.log(this.transition);
// simplified transition info about the closest transition
console.log(this.scope.__transition === this.transition);
}
}
<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. Function results have a higher priority than the current URL arguments. Also you can forcibly remove the necessary argument passing null. It has the highest priority.
router.add('app', '^/:lang', {
params: {
lang: args => args.params.lang || 'en'
},
query: {
v: 1
priority: () => 1,
useless: 'oops'
},
hash: () => 'header1'
});
router.state('add', { lang: 'es' }, { v: 2, priority: 2, useless: null }, hash: null); // /es/?v=2&priority=1
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
Reload the current state.
router.back() undefined
Go back on the history.
router.forward() undefined
Go forward on the history.
router.go(position) undefined
- position integer - history position (window.history.go)
Go to the position.