Part of a client-side router’s job is to allow users to use the address bar and the browser’s navigation buttons as they normally would in a traditional web application.
At a minimum, many client-side routers offer the following features that make this possible:
■ Match patterns in the URL with paths defined by the route
■ Allow for the execution of code in your application when a match is found Listing 4.4 Default route (pseudocode)
Otherwise indicates your default. You redirect back to the faculty route when there’s no match to any other path.
■ Allow a view to be specified that will be displayed when the route is triggered
■ Allow for parameters to be passed via the route’s path
■ Allow users to use standard navigation methods of the browser to navigate the SPA These features are all that are needed to provide a minimal level of navigation in an SPA. Keep in mind, though, that there’s no guaranteed standard that all client-side routers must follow. These are just the most common options you’ll encounter. The documentation for the MV* framework (or independent router library) will list its full range of features.
Having summarized a basic list of features most routers offer, let’s peek under the covers to see how routers provide navigation in a single-page setting.
Routers use one of two methods: either via the URL’s fragment identifier or the HTML5 History API. Both methods enable the router to provide server-less navigation but in slightly different ways. Because the HTML5 History API is newer and not sup- ported by older browsers, you’ll start your foray into the world of client-side routing with the more traditional fragment identifier method.
4.3.1 The fragment identifier method The traditional method for routers to provide navigation in an SPA is via a fragment identifier. As noted in figure 4.5, the fragment identifier is any arbitrary string of text at the end of the URL and is prefixed with a hash symbol (#). This optional part of the URL references a section of the cur- rent document, not a new document.
Browsers treat this part differently from the rest of the URL. When a new fragment identifier is added to the URL, the browser doesn’t attempt to interact with the server.
The addition does, however, become a new entry in the browser’s history.
This is important because all entries in the browser’s history, even those generated from the fragment identifier, can be navigated to via normal means, such as the address bar and the navigation buttons. To see this in action, go to any website, such as www.manning.com. Next, try executing the following in your browser’s console:
window.location.hash = "hello";
As you’ll see, doing this results in hello being added as the URL’s fragment identifier.
The URL should look like this after the line executes:
http://www.manning.com/#hello
This action also adds a new entry in the browser’s history. Now you can navigate back and forth between the fragment identifier and the original URL.
http://www.somesite.com/categ/#hashinfo
Protocol Category
Hostname Fragment
identifier Figure 4.5 The fragment identifier
95 How do client-side routers work?
EXPLOITING THE BROWSER’S LOCATION OBJECT
The location object contains an API that allows you to access the browser’s URL informa- tion. In an SPA, routers take advantage of the location object to programmatically gain access to the current URL, including the fragment identifier. The router does this to listen for changes in the fragment identifier portion of the URL, via the window’s onhashchange event (if available in that browser version—otherwise, it polls for hash changes).
When a change occurs, the pattern in the new hash string is compared to all the paths in each route from the router’s configuration. If a match exists, the router exe- cutes any process specified and then displays the view from the matching route.
For example, imagine you have a link to your fictitious department’s main contact page in your website header. The link’s code points to a fragment identifier URL:
<a href="#routes/contact">Contact Us</a>
When you click this link, the browser’s fragment identifier changes from its initial value to #/routes/contact.
Because the router actively listens for changes in the fragment identifier, this new hash is detected. Upon detection, the router searches all routes in its configuration for a path matching /routes/contact. When it finds a match, the route in the follow- ing listing is carried out.
ON MATCH OF "/routes/contact" : FUNCTION NAME : "displayContactNumber",
VIEW TO DISPLAY : "App/partials/contact.html"
You should now have a pretty good understanding of basic client-side routing. As I mentioned at the beginning of this section, routers can use two methods to control the application’s state. You’ve looked at the fragment identifier method. In the next section, you’ll look at the newer HTML5 History API method.
4.3.2 The HTML5 History API method
You’ve learned that by using the fragment identifier method to change the URL’s hash information, the router can add new navigable entries in the browser’s history. Each change adds a new entry in the history stack. After that, users can navigate back and forth between hashes without triggering a page refresh. But when using this method, developers are forced to create paths that revolve around the hash symbol (#).
New methods in the HTML5 History API change this. Routers can take advantage of new functionality available in HTML5 to interact with the browser’s history without rely- ing on the fragment identifier. Also, because these methods aren’t available in older browsers, most routers gracefully fall back on the fragment identifier automatically.
Listing 4.5 Main contact route (pseudocode)
The path
The functionality used in the new route
The view you’ll change to
PUSHSTATE AND REPLACESTATE
The two new methods in the History object’s API that routers can take advantage of are as follows:
■ pushState()—Allows you to add new history entries
■ replaceState()—Allows you to replace existing history entries with new ones These new additions allow direct access to the browser’s history without relying on the fragment identifier. You’ll explore them briefly to understand what happens when routers use the HTML5 History API method.
Using history.pushState() or history.replaceState(), the router can directly modify the browser’s history stack. Both methods also allow the router to work with
“pretty,” natural-looking URL segments instead of hashes. Both methods take three parameters:
■ State object—An optional JavaScript object associated with the history entry
■ Title—Represents a new title for the history entry (though not implemented by most browsers as of this writing)
■ URL—The URL that should be displayed in the browser’s address bar
To see how this method works, give pushState() a try. Go to any website, such as www.manning.com, and type following in your browser’s console:
history.pushState({myObject: "hi"},"A Title", "newURL.html");
The command results in the URL changing to this:
http://www.manning.com/newURL.html
It also adds a new entry in the browser’s history. Now you can navigate back and forth between the new URL and the original URL. You’ll also notice that the URL added via pushState() doesn’t trigger a browser refresh and doesn’t contain the hash symbol.
To view the state object that was added, you can type history.state into the con- sole. In response, you’ll see the contents of myObject returned.
THE POPSTATE EVENT
Finally, routers are given a way to monitor the history stack for changes: the window.popstate event. Browsers fire this event whenever the user navigates between history entries.
You can also experiment with this in your console. Use the pushState() method to add some history entries. Then execute the following code in your console:
window.addEventListener("popstate", function(event) { console.log("popstate event fired");
});
Next, navigate back and forth between the URLs added with pushState(). You should see the following log entry added to the console:
popstate event fired
97 How do client-side routers work?
Now that you understand the newer HTML5 History API method of routing, let’s see how you change your code to use it.
4.3.3 Changes for the HTML5 History API method
Most routers offer the option to use the HTML5 History API method for client-side routing. Indicating to the router which method you prefer is usually as easy as setting a single configuration option. Often, however, other changes need to be made in addition to the mode switch. I talk about those in this section.
Let’s start with the option to change methods. In many routers, you change a Bool- ean value from false to true.
HTML5 MODE
To convert our example from the fragment identifier method to this one, you need to change the appropriate setting in your router’s configuration. This is where you flip the switch to use the HTML5 History API method. For example, in AngularJS, you’d use this:
html5Mode(true);
In Backbone.js, you’d use this:
Backbone.history.start({pushState: true});
Again, these are framework-specific examples. Consult your router’s documentation for the exact syntax.
BASE HREF
Now that you’ve told the router that you want to use the HTML5 History API method, you need to set the BASE HREF in your index page’s header:
<head>
<base href="/SPA/">
</head>
For the HTML5 History API to work correctly, your BASE HREF must match the deployed application’s root path in its base URL. Otherwise, you’ll get an “Error 404 not found” response when your application tries to retrieve the views in its routes.
TIP You need a base URL only if you don’t want to include the full path in your links/code.
In this example, /SPA/ will be the root path in your base URL. So you need to use that as the BASE HREF. A lot of different servers are out there, and applications get deployed in many ways. As long the BASE HREF is set properly, your views will be displayed.
SERVER-SIDE CHANGES
Finally, to finish off the HTML5 History API configuration, you’ll need to configure your server so that it always returns content for the root. For example, if you have a catchall server-side route configured, it’ll always return the correct resource to the client.
One caveat is that if a user uses a bookmark or page refresh, the browser will make a request for that same content. One possible solution is to set up a redirect on the server to internally redirect to that same URL.
REMOVING THE HASH
If the router supports it, you can now remove the hash characters from the links in your views. For example, in the link to the main office contact information, the anchor tag can be written as follows:
<a href="routes/contact">Contact Us</a>
When you click this link, you’ll see what looks like a normal URL in the browser’s address bar. No hash character!
Now that you know the basics of client-side routing, you can roll up your sleeves and do some coding.