1024px-Flatirons_Winter_Sunrise_edit_2

Over the past few years, “single-page applications” have steadily grown in popularity within the web development industry. As developers have grown accustomed to placing the bulk of the responsibility for rendering these “thick client” apps within the browser, new libraries that provide repeatable patterns for solving frequently encountered issues when using a client-heavy approach have flourished. Chief among these new libraries – JavaScript routers.

What is a JavaScript Router?

First – some groundwork. I’ll spare you the in-depth introduction to REST – if you’ve made it this far, the odds are good that you are at least somewhat familiar with the concept. In a nutshell: on the web, all resources should have clearly defined addresses (i.e. “URLs”) at which they can be reached. For a simple real-world example, let’s look at Wikipedia. If I want to learn more about underwater basket weaving, I can do so by navigating to the following URL:

http://en.wikipedia.org/wiki/Underwater_basket_weaving

Pretty simple stuff. Unfortunately, due to their very nature, single-page applications complicate things just a bit. One of the primary goals of the single-page approach is to provide the user with a more fluid, “desktop-like” experience. To that end, single-page apps attempt to reduce the total number of pages that a user must load down to one, hence their name. Whereas a traditional web application requires full page reloads (along with the accompanying URL change) as the user navigates from page to page, single-age apps shuttle all of this information over AJAX in the background. This presents us with a dilemma… If our user’s URL isn’t going to change as they navigate around our app, how are we supposed to keep track of their location? Enter the JavaScript Router.

Client-Side Representation of State

JavaScript routers provide developers with a method for tracking user state and loading required resources, as needed, without requiring a URL change or page reload. This is accomplished in one of two ways:

  • Via Hashbang – As the user navigates, the library changes the “hashbang” (#!/…) in the URL to denote their current location. While compatible with legacy browsers, many critics complain that the resulting URL is visually unappealing.
  • Via the History API – Introduced in HTML5, the History API provides developers with a method for programmatically updating the browser’s URL without the accompanying page reload that has typically been required.

A quick Google search for “JavaScript router” will present you with a number of useful libraries (e.g. Sammy, Crossroads, etc…). However, my personal experience with such libraries has left me wanting more structural guidance when developing large, complex single-page applications with dozens (or hundreds) of routes. To fill that niche, I have released EdisonJS.

EdisonJS

Most JavaScript routing libraries provide little to no guidance in terms of how one might best go about organizing complex single-page applications with many routes. EdisonJS seeks to simplify the organizational structure of complex single-page applications by encouraging developers to organize their applications as a series of parent (“section”) child (“route”) relationships. The result is a simple, clean, and powerful organizational structure.

Using EdisonJS

Creating a New Instance of EdisonJS

In this example, we create a new instance of EdisonJS and pass a single option – container. Each route that we define will have a template associated with it, and this option determines where those templates are inserted into our document.

Creating a Section

A typical web application is comprised of many different “routes.” EdisonJS encourages the developer to group related routes under a parent “section” as shown below:

When a user navigates to a route belonging to the “users” section for the first time, the section’s callback function will be fired. As the user navigates between routes within the section, this callback function does not continue to fire. As a result, this callback function is useful for performing setup routines that related routes would typically have to perform on their own.

Creating a Route

With our first section and route defined, we can now navigate to the following URL:

In your console, you should now see the following messages:

Passing Parameters

Routes can accept a single id parameter as shown below:

For greater flexibility, you can also access query parameters directly, as shown below:

Extending Sections

Sections can extend their functionality as shown below. As a result, sections can remain organized as they inevitably grow more complex:

Extending Routes

In a similar manner, routes can also extend their functionality.

Accessing Parent Sections from Routes

A route can access its parent section as shown below:

Global Section and Route Extensions

Functionality shared across all sections and routes throughout the application can be extended globally as shown below:

Cleanup Routines (Optional)

If a section or route is given a cleanup method, it will be called when the user navigates to a different section or route. See below:

Global Cleanup Extensions

In the following example, we pass a callback function to the extendCleanup method. By doing so, we instruct EdisonJS to run the callback each time the user navigates away from any section or route – in addition to any cleanup methods that are defined on a specific section or route.

One More Thing

Once all of your sections and routes have been defined, you should call the following method:

Installation

Bower

Configuration

Add the following options to your RequireJS configuration (adjust location as appropriate):