A Place For Answers

Posts tagged “Code

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.

Advertisements

String Base Conversion – for Node.js & JavaScript

This goes out to all you Node.js and JavaScript people out there.
From normal to binary base, we’ll have to turn each string to unicode and then make it binary:

function encode (plainString ) {
	var encodedArray = [];
	for (var i = 0; i &lt; plainString.length; i++) {
		encodedArray.push(plainString.charCodeAt(i).toString(2));
	};
	return encodedArray.join().replace(/,/g,'');
}

Just remember the function does not pad the string with zeros, so for instance “0” will become “110000” and not “00110000”.
Getting back to normal from binary is a bit easier:

function (encodedString) {
	var plainString  = "";
	for (var i = 0; i &lt; encodedString.length; i+=8) {
		var c = String.fromCharCode(parseInt(encodedString.substr(i,8),2));
		plainString = plainString + c;
	};
	return plainString;
}

This bit of code will take every 8 characters,turn it to unicode and then change it to normal characters in normal base.

As always, stay tuned.