This is a very simple routing class that listens to location hash changes and clicks on links to registered routes.
You have to explicitly define the routes that you wish to use, so we don't clash (too much) with deep-linking to named anchors on your page. And also because it enables you to handle different routes with different functions.
The biggest benefit of using this router is that you can navigate your single page app and still have a proper history. So forward and back buttons work, you can refresh and deep-link to a specific page, those kinds of things.
Create and install a router object:
import Router from './router.js';
const router = new Router().install();
Then, add routes. Either individually, maybe programmatically:
router.addRoute('welcome', navigationFunc);
Or multiple routes at once, configuration style:
router.addRoutes({
router: navigationFunc,
activate: navigationFunc
});
If, like in these examples, all routes lead to the same function, you can declare them in one go:
router.addRoutes([ 'click',
'dragdrop' ], navigationFunc);
You then have to implement your own navigation or rendering functions. Maybe
each page has a different function. In our case, we use this quick and dirty
navigation solution: hide the active .page
and show the one we requested.
function navigationFunc(route) {
document.querySelector('.page.active').classList.remove('active');
document.getElementById(route).classList.add('active');
window.scrollTo(0,0);
};
If you like, you can pass any parameters that you would use with addRoutes
to the constructor too:
new Router(
[
'welcome',
'router',
'energize',
'click',
'dragdrop'
],
route => {
document.querySelector('.page.active').classList.remove('active');
document.querySelector(`#${route}`).classList.add('active');
window.scrollTo(0,0);
}
).install();
This is equivalent to:
new Router()
.install()
.addRoutes(
[
'welcome',
'router',
'energize',
'click',
'dragdrop'
],
route => {
document.querySelector('.page.active').classList.remove('active');
document.querySelector(`#${route}`).classList.add('active');
window.scrollTo(0,0);
}
);
Now all that's left to do is add some links with routing-power:
<nav>
<ul>
<li><a href="#welcome">Welcome!</a></li>
<li><a href="#router">Router</a></li>
<li><a href="#energize">Energize</a></li>
<li><a href="#click">Click</a></li>
<li><a href="#dragdrop">Drag & Drop</a></li>
</ul>
</nav>
Advanced use
Regular expressions
If your routing needs are more complicated than string → function you can use regular expressions instead of strings. The navigation function gets called with three parameters: the matched route (either the string or the regular expression), the regular expression matches if any, and the original event that triggered the call to the navigation function:
router.addRoute(/admin\/users\/(\d+)/, (route, matches, e) => {
console.log('This function was called because the location hash matched', route);
console.log('We\'re interested in user number', matches[1]);
console.log('This was the original navigation event:', e);
});
Nested routers
For readability or for nested components, you can nest router instances. This can be done in two ways: either by registering a route that leads to a router instance:
new Router()
.install()
.addRoutes({
public: new Router({
login: loginFunc,
logout: logoutFunc
})
});
<a href="#public/login">Click here to log in</a>
...or by calling route
on the nested router:
const privateRouter = new Router({
dashboard: dashboardFunc,
users: usersFunc
});
new Router()
.install()
.addRoute(/private\/(.*)/, (route, matches, evnt) => {
if (!loggedIn) {
window.location.hash = 'public/login';
return;
}
privateRouter.route(matches[1], evnt);
});
<a href="#private/dashboard">Go to your dashboard</a>
Please note that the router that you call the install
function on is the
one that will be the root router. This router binds itself to the right
events.