A Place For Answers

Posts tagged “Single Page Application

Modern JavaScript Concepts Series – Part 1: Routing

Hey there,

You’ll have to excuse my absence, I’ve been working on a couple of very cool projects. I’d like to share with you guys some of the lessons I took from them. So in the coming months I plan on posting parts of this series, and for the first post we’ll be talking about routing.

Routing refers to the translation of URLs into views, it exists on most modern platforms like .NET MVC and Node.js. In single page applications(from this point on we’ll refer to them as SPA) we can no longer rely on these platforms to handle our views, and we must look elsewhere to find the answer. Many frameworks have rose to answer the issue and they all handle the problem with some aesthetic differences, in principle there are two ways of handling routing. Now before we actually go on to how we do it let’s first describe what we need to make this happen:

  1. A way to start and stop the routing mechanism.
  2. A construct which holds the route and the action to be performed.
  3. Some way to add and remove routes.
  4. And finally the routing translator.

Let’s translate this interface into some code:

router.js:

;(function () {
	function Router () {
		var self = this;
		self.routes = [];

		self.on = function (path, func) {
			// prevent adding same path routes
			var routeIndex = pathFilter(path);
			if (routeIndex == -1) self.routes.push({ route: path, init: func });
		}

		self.off = function (path) {
			// find the correct route and remove it
			var routeIndex = pathFilter(path);
			if (routeIndex != -1) self.routes.splice(routeIndex, 1);
		}

		function routeMatch (path) {
			var routePath = path.match(/\w+(\/\w+)*/i);
			return (routePath == null) ? "" : routePath[0];
		}

		function route (e) {
			// Todo: respond to router event
		}

		self.start = function () {
			// Todo: start listening to router events
		}

		self.stop = function () {
			// Todo: stop listening to router events
		}

		/* Utils */
		function filterByProp (array, prop, val) {
			for (var i = 0; i < array.length; i++) {
				if (array[i][prop] !== undefined && array[i][prop] == val) { return i; }
			};
			return -1;
		}

		function pathFilter (val) {
			// Shorthand for filterByProp()
			return filterByProp(self.routes, "route", val);
		}
	}
	window.Router = Router;
})()

index.html:

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="utf-8" name="viewport" content="width=device-width, initial-scale=1.0">
	<title>Router</title>
</head>
<body>
	<a href="#test1">test1</a>
	<a href="#test2">test2</a>
</body>
<script type="text/javascript" src="router.js"></script>
<script type="text/javascript">
	document.addEventListener('DOMContentLoaded',function() {
		var router = new Router()
		router.start()
		router.on( "test1", function () {
			alert("1st hello");
		} )
		router.on( "test2", function () {
			alert("2nd hello");
		} )
	}
</script>
</html>

First Method – Nipping it in the bud

Capturing the page transitions at the source, binding the routing handler to the event that instigates the transition, for this example we’ll be using the anchor tag, but the idea is that whenever we click a routing action it’ll be resolved by our routing translator.

function route (e) {
	var targetPath = routeMatch(this.attributes["href"].value);
	var routeIndex = pathFilter(targetPath);
	if (routeIndex != -1) {
		self.routes[routeIndex].init();
	} else {
		console.log("Error: missing route.");
		// Handle missing route
	}
}

self.start = function () {
	var anchors = document.querySelectorAll("a");
	for (var i = 0; i < anchors.length; i++) {
		anchors[i].addEventListener("click", route, false);
	};
}

self.stop = function () {
	var anchors = document.querySelectorAll("a");
	for (var i = 0; i < anchors.length; i++) {
		anchors[i].removeEventListener("click", route, false);
	};
}

Pros:

  1. Compatibility – This concept should work on most browsers and we all want our code to reach out to as many people, here and there you might need to handle browser quirks if you’re attempting to go legacy but the implementation concept is accessible.

Cons:

  1. DOM Management Required – In this method we’re attaching events to anchor elements, which would have been alright if our pages were static, sadly we’re all about dynamic content today switching views and what nots. That is why we have to call router.stop() and router.start() every time we change the DOM, or alternatively create a new function that handles only the DOM changes so we┬áremove the events as to avoid memory leaks.
  2. History – This method does not take into consideration browser history at all, sometimes that’s a good thing, but I wrote it in the cons cause personally I prefer to have the choice.

 

Second Method – Every action has an opposite and equal reaction

This technique is a bit more modern than our last one, in this method we’re gonna capture the url change or, in case we need to handle browser quirks, the hashbang change( the part in the query string after the # character ).

I feel this method is more inline with how JavaScript works, being an asynchronous language that is fueled by events.

function route (e) {
	var targetPath = routeMatch(location.hash);
	var routeIndex = pathFilter(targetPath);
	if (routeIndex != -1) {
		self.routes[routeIndex].init();
	} else {
		console.log("Error: missing route.");
		// Handle missing route
	}
}

self.start = function () {
	addEventListener("popstate", route, false);
	if( ieUserAgent() || !("onpopstate" in window) ) addEventListener("hashchange", route, false);
}

self.stop = function () {
	removeEventListener("popstate", route, false);
	if( ieUserAgent() || !("onpopstate" in window) ) removeEventListener("hashchange", route, false);
}

</pre>
<pre>/* Utils */
function ieUserAgent () {
 var result = false;
 if ( navigator.userAgent.indexOf("MSIE") > 0 || navigator.userAgent.match(/Trident.*rv\:11\./) != null ) { result = true; }
 return result;
}

Pros:

  1. History – In this method we’re listening directly to the browser’s history and we have it’s state, which is a huge deal for single page application for all sort of reason for instance, we can differentiate between being in the same view(example: being in the 1st view, switching to a 2nd view and then coming back to the 1st view) through it’s state.
  2. Automatic Transition Handling – No DOM management or worry about memory leaks.

Cons:

  1. Compatibility – This method’s greatest drawback, the support for the “popstate” event is growing and all evergreen browsers nowadays support it, we still need to handle IE’s quirks but the support for the event is has around 75%, with opera-mini not supporting at all and safari only partially supporting the event. The “hashchange” event is also not perfect and standing at ~90% with opera-mini still slow on the draw.

 

Now that you’ve seen how both frameworks operate I’ll leave it up to you to decide how you wanna go about, I will say this, most frameworks go for a mix of both methods incorporating all the advantages they can.

 

Next time I plan on talking about data-binding a pretty cool concept, I suggest you stay tuned.


Single Page Application – The jQuery Mobile Way

Single page applications are becoming more and more prevalent in these days. So I thought I’d review how it can be achieved using some of today’s libraries and frameworks, starting with my personal first – jQuery Mobile.

Before actually diving into the matter let’s review over the idea that is single page applications. The idea is based on the notion that to get a better user experience you need to remove hindrances, such as waiting times. think about it – what happens when a user travels inside a site? He reads a bit of data in page one, decides to move to page two, reads a little bit there and maybe moves to another page. During all those transitions the browser made about 50-60 requests per page, now some of those requests are files that are shared between all those pages. Single page applications exploit that fact by grouping all the shared files and saving them into browser’s cache or by saving them into the browser’s storage system.

Now that we got over that boring stuff, Let’s dig into the cool stuff. It starts the same as any html page you add the major css and Javascript files:

<!DOCTYPE html>
<html>
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
  <link rel="stylesheet" href="css/jquery.mobile-1.3.2.min.css" />
  <script src="js/jquery-1.9.1.min.js"></script>
  <script src="js/jquery.mobile-1.3.2.min.js"></script>
</head>
<body>
</body>
</html>

So here’s where it gets tricky with jQuery Mobile. It all starts with the container:

<div id="page-one" data-role="page" data-title="Page 1">
content goes here
</div>

In that container you can start styling your page and pour content into it, and that’s it whenever you go to that page again you will only see the content of that page. And so, when your done with your main page, you just create another container just like the first with a different id and a title of your choosing. The next step is to create another page and then continue on and on till all your pages are set up. Your HTML page at this point would look something like this:

<!DOCTYPE html>
<html>
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
  <link rel="stylesheet" href="css/jquery.mobile-1.3.2.min.css" />
  <link rel="stylesheet" href="css/bootstrap.min.css" media="screen">
  <script src="js/jquery-1.9.1.min.js"></script>
  <script src="js/jquery.mobile-1.3.2.min.js"></script>
</head>
<body>
  <div id="page-one" data-role="page" data-title="Page 1">...</div>
  <div id="page-two" data-role="page" data-title="Page 2">...</div>
</body>
</html>

Thing is, when you view this page you only see the first page so you must be wondering now:
“But Netanel if it’s all one page how do you see and move between the pages?”
Well, that’s simple you just create a hyperlink and redirect to the hashtag with the id of the target page, something like this:

<a href="#page-two">Click here for page #2</a>

The url would be something like this: http://index.html#page-two.
So that’s Single Page Application using the jQuery Mobile, quite simple you have to admit and keeps the mantra of:
img

But wait, what if you want to trigger events on page load, such as querying the server for updated information? well here’s where some jQuery magic comes into place, you can achieve page load events in this fashion with:

$('#page-one').on('pageshow', function(event) {
	alert("page #1");
});

The structure of this SPA is basically this: the shared CSS files, JS files and with them global variables if you want to use them, so you can even pass data between pages, even though that’s never advised, and you know why! Here’s a small diagram to illustrate this:
spadiagram

In the bottom line here, what did we get? We managed to reuse our code, gave the users a better user experience and overall solid structure for our project.

I’ll be updating this post when i finish uploading a nice little example displaying how it’s all tied up with a pretty little bootstrap bow tie on top soon.
Like always keep tuning in for updates.