I wrote a router plugin for soma.js to create internal routes in a web app.

The plugin is based on the amazing library davis.js (thanks a lot to the author Oliver from New Bamboo for the help). It uses the HTML5 history API and have a hash fallback for browsers that don’t support the HTML5 API (see the davis hash routing plugin).

Click here to see the demo, the soma.js site has also been updated with the plugin and the latest version (1.0.2).

Prepare the html

Include the libraries in your page:

<script src="/js/davis.js"></script>
<script src="/js/davis.hashRouting.js"></script>
<script src="/js/soma.js"></script>
<script src="/js/soma-router.js"></script>

Create the plugin

Creating the plugin takes only one line:

var router = this.createPlugin(soma.router.Router);

This can be done in a soma.js framework element such as the application itself or a wire:

var SomaApplication = soma.Application.extend({
	init: function() {
		this.createPlugin(soma.router.Router);
	}
});
var app = new SomaApplication();

Create the routes

Given some html links:

<li><a href="/">Home</a></li>
<li><a href="/about">About</a></li>
<li><a href="/services">Services</a></li>
<li><a href="/articles">Articles</a></li>
<li><a href="/articles/article1">Articles</a></li>
<li><a href="/articles/article2">Articles</a></li>
<li><a href="/articles/article3">Articles</a></li>
<li><a href="/contact">Contact</a></li>

Here is how the routes can be created:

var routes = {
	'get': {
		'/' : RouterEventTypes.ROOT,
		'/:nav' : RouterEventTypes.NAV,
		'/:nav/:subnav' : RouterEventTypes.SUBNAV
	}
};

The variables RouterEventTypes are simple strings and will be used as event types.

var RouterEventTypes = {
	ROOT: "root",
	NAV: "nav",
	SUBNAV: "subnav"
};

When the router will find a route, from a url change or when a link is clicked in the browser, a specific event will be dispatched through the framework. A url such as “http://site.com/about” will be matched by the rule “/:nav” and an event of type “RouterEventTypes.NAV” will be dispatched through the framework.

Note that I could have set a route in a static way: “/about” rather than “/:nav”.

Register the routes to the router

The routes object created previously can be registered to the router this way:

var router = this.createPlugin(soma.router.Router, routes);

Listen to route changes

Now that everything is set up, it is only a matter of adding event listeners in the framework elements, here is an example with a wire:

var WireRouter = soma.Wire.extend({
	init: function() {
		this.addEventListener(RouterEventTypes.ROOT, this.rootHandler);
		this.addEventListener(RouterEventTypes.NAV, this.navHandler);
		this.addEventListener(RouterEventTypes.SUBNAV, this.subnavHandler);
	},
	rootHandler: function(event) {
		// do something
	},
	navHandler: function(event) {
		// do something
	},
	subnavHandler: function(event) {
		// do something
	}
});

The event received contains the following information:

  • event.params.rule -> /:nav
  • event.params.request -> Davis.Request

And the Davis.Request will contains all the information you might need. Such as the link or url:

navHandler: function(event) {
	var request = event.params.request;
	// the following variable urlParameter will contain "about"
	// if the link was for example http://site.com/about
	var urlParameter = request.params[RouterEventTypes.NAV];
}

Controlling and interrupting

A global route change event can also be listened to:

this.addEventListener(soma.router.RouterEvent.CHANGED, this.changeHandler);

This is an internal framework command and can be interrupted this way:

changeHandler:function(event) {
	if (whatever_reason) {
		// stop the route to occur
		event.preventDefault();
	}
}

Hash routing fallback

In case the browser doesn’t support the HTML5 History API, you can add a hash fallback by adding the Davis plugin hashRounting just before you create the router plugin:

Davis.extend(Davis.hashRouting({ prefix: "!/" }));
this.createPlugin(soma.router.Router);

Davis handler

When a Davis.js instance is created, an handler is passed to it and will be a Davis.App.

For example:

var app = Davis(function () {
	// do something
});

The creation of the Davis.App is automatically done by the plugin but you can still pass the handler to it and do more Davis.js specific operations:

var router = this.createPlugin(soma.router.Router, routes, function() {
	app.bind('routeNotFound', function () {
		// do something
	});
});

Handling non-existing route

When you have a url and create HTML5 History routes (called states), these URLS might not exist in reality.
If a user go a non-existing URL, it can be either handled by the server to the serve the correct content, or you can use the following htaccess file on an Apache Server:

RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_URI} !index
RewriteRule (.*) index.html [L]

These rules will redirect any URL that is not a file or a folder to the index.html of you site, Davis.js will then handle the routes for you to show the right content on the client side.

Note that you need to specify to Davis.js that you want the first page load to be handled as a route:

var router = this.createPlugin(soma.router.Router, routes, function() {
	this.settings.generateRequestOnPageLoad = true;
});

To get the router plugin, you can either:
– clone the repository (native or mootools version)
– grab it directly (native or mootools)
check out the soma.js site

For more information about Davis.js, you can check:
Davis.js site
Davis.js API documentation
Davis.js code

Share and Enjoy!