Just over 4 years ago, I attended a tech conference for the first time. I went to a session on implementing client patterns in XAML. At the time, XAML was looking very promising as a cross-platform solution using Silverlight. The presenter started off the session by making it clear that he was not a fan of the web. His quote was something along the line of the following...

I would never wish a web application on my clients. Why? Because of the "back" button.

At the time, I thought this an incredibly ignorant statement to make. Four years later I realized that speaker wasn't ignorant at all. In fact, I believe he was ahead of his time.

What the speaker was alluding to in his statement is that the back button is a primary and complex part of your application whether you like it or not. If you fail to aknowledge that fact, you will most likely break the web. And it appears that is exactly what has happened.

Breaking The Web

In the past few years, we have discovered that we don't need to make multiple requests to the server for markup. JavaScript is more than capable of loading, showing and hiding fragments of DOM. AJAX is now our data pipeline and requests occur in the background. This model of application is commonly (if not inaccurately) referred to as a Single Page Application (SPA).

The SPA is a widly important concept since it has rocketed web applications into the same space as their native installed counterparts in terms of look and feel. Having one application with unlimited reach is an opportunity too good for any developer to pass up. Unfortunately, while we have abandonded traditional web functionality in favor of handling page rendering ourselves, we have circumvented the browser history process altogether rendering the back button useless, if not completely unpredictable. Our SPA's have broken the web.

Case In Point

To demonstrate how frustrating the back button can be in a SPA, I'm going to pick on Twitter. Any tweet with an attached picture shows the picture inline with the tweet. Clicking on that picture expands it to a larger size and darkens the entire screen. At this point, it looks like you are on a completely different screen, but you aren't. It feels natural (at least to me) to hit the back button to return to my timeline, but that's not at all what happens. What happens is that you navigate yourself completely out of Twitter. This is probably not what you were expecting.

What do we do about this? We can't expect users to stop using the back button because they haven't and they won't. On mobile devices, the back button is even more important since it's persistent and easier to find than your apps navigation. It's high time we gave the back button the respect it deserves.

Respecting The Back Button

There is no shortage of frameworks available for building SPA's (Twitter uses it's own proprietary Flight framework in case you were wondering). Regardless of which framework you are using, you will be manually handling URL's for your application. This is commonly referred to as "routing".

SPA Routing

Like most SPA frameworks, Kendo UI implements a router to allow you to specify what content should be shown on the screen whenever a specific URL is visited.

Simple Router Configuration (function() { var router = new kendo.Router(); router.route('/', function() { console.log('Home Page'); }); router.route('/orders', function() { console.log('Orders Page'); }); router.route('/products', function() { console.log('Products Page'); }); }());

Kendo UI Views

The next building block is the Kendo UI View. Views are rendered from templates which are not parsed by the browser when the page loads. Until they are initialized, they do not exist as part of the application. Kendo UI Views also take an optional second parameter which defines a ViewModel. Any widgets in the view that have a data-role or data-bind configuration will be appropriately bound by the framework. The router simply needs to render these views and show them on the page somewhere.

Index.html <div id="root" class="container"> <ul class="nav nav-pills"> <li> <a href="#">Home</a> </li> <li> <a href="#/orders">Orders</a> </li> <li> <a href="#/products">Products</a> </li> </ul> <div id="content"></div> </div> <script type="text/x-kendo-template" id="home-template"> <h2>Home Page</h2> </script> <script type="text/x-kendo-template" id="orders-template"> <h2>Orders Page</h2> </script> <script type="text/x-kendo-template" id="products-template"> <h2>Products Page</h2> </script>

index.js (function() { // Cache reference to application container div var content = $('#content'); // Define Views var views = { home: new kendo.View('#home-template'), orders: new kendo.View('#orders-template'), products: new kendo.View('#products-template') }; // Define Router var router = new kendo.Router(); // Configure routes router.route('/', function() { views.home.render(content); }); router.route('/orders', function() { views.orders.render(content); }); router.route('/products', function() { views.products.render(content); }); router.start(); }());

View Demo

You will notice that as you visit the different pages of the app, the contents of the views get appended to the page. This isn't quite what we want. This is happening because Kendo UI has no way of managing our views currently. They all stand on their own, and Kendo UI doesn't yet know how we want to display them. Kendo UI provides another construct called the "Layout" to help you manage your views.

Kendo UI Layouts

The Kendo UI Layout component provides a container in which to render markup as well as other views. It also handles the loading and unloading of these views for you. A SPA can have one to many layouts.

Index.js (function() { // Define Layout var layout = new kendo.Layout('#layout-template'); // Define Views var views = { home: new kendo.View('#home-template'), orders: new kendo.View('#orders-template'), products: new kendo.View('#products-template') }; // Define Router var router = new kendo.Router({ init: function() { // render the layout first layout.render('#root'); } }); // Configure routes // layout.showIn(where, what) router.route('/', function() { layout.showIn('#content', views.home); }); router.route('/orders', function() { layout.showIn('#content', views.orders); }); router.route('/products', function() { layout.showIn('#content', views.products); }); router.start(); }());

View Demo

Now we have a proper SPA with views being loaded and unloaded by Kendo UI. If you navigate between the tabs and use the back button, everything works just like it would in a normal app. So far we haven't broken the web. High five.

The Gray Area

The trouble with SPA's is that they can create a very gray area for the back button. We can quickly find ourselves in territory where we aren't really sure what the user will expect the back button to do. This is exactly what is happening with the Twitter example above.

We don't have to add much functionality to the demo SPA to find ourselves smack in that gray area. A grid with paging is a perfect use case.

A grid of orders is added to the orders tab. This grid has paging controls that provide the user with the ability to move through pages of data. What we have done now is implement a navigation scheme within our app that the app itself doesn't know about. If the user is on page 4 of data, they will probably expect the back button to take them to page 3, but it won't. It will take them completely off the page. And just like that, we have broken the web.

View Demo

What Should The Back Button Do?

In this case, we need to ask ourselves what the user is going to expect to happen. We can be pretty sure that the user is going to expect the back button to take them backwards through their paging history. To do that, we need to bring the application into the loop on what is happening in the grid.

The Kendo UI Grid exposes the Pager component which raises a change event whenever the page is changed. In that event, we need to alert the application that the current page of data has changed. We do that by calling the navigate method on the router. We're going to be navigating to the same view we're already on, but passing in the page parameter of the page we just navigated to in the grid.

index.js // some code omitted for berevity var dataSource = new kendo.data.DataSource({ type: 'odata', transport: { read: 'http://demos.kendoui.com/service/Northwind.svc/Orders' }, pageSize: 20, serverPaging: true }); var grid = $('#grid').kendoGrid({ dataSource: dataSource, columns: [ { field:'OrderID', filterable: false }, "Freight", { field: "ShipName", title: "Ship Name", width: 260 }, { field: "ShipCity", title: "Ship City", width: 150 } ], pageable: true }).data('kendoGrid'); // bind the change event on the pager grid.pager.bind('change', function(e) { // navigate to the current view passing the current page as a parameter router.navigate('/orders/' + e.index); });

The 'orders' route now needs to do more than just blindly show the 'orders' view. It needs to look at the parameter passed in the URL, compare it to the current page of data in the data source, and update the page if necessary. The net effect here is that when the user presses the back button, they move backwards through their paging history.

index.js // orders route only for berevity router.route('/orders(/:page)', function(page) { // set the page to 1 if no parameter is passed var page = page || 1; // if the current page of data is not the same as the // one passed in the URL, update the datasource page to match if (dataSource.page() != page) { dataSource.page(page); } });

Note: The () around the :page parameter indicate that it is optional. Kendo UI Routes follow the Ruby On Rails routing formats.

Now the user can navigate through the grid with either the pager controls, or the back button. Even better is that we now have deep linking inside the application. If the user wants to share page 3 of data with another user, they simply have to send the link. This is the power of the web!

View Demo

Please Respect The Back Button

As developers, we have to respect the existing pillars of the web as we move forward and create new ones. The back button was here long before AJAX and it isn't going anywhere. Give the back button a seat at the table and incorporate it into your application architecture as you build that new SPA. Unfortunately, your users probably won't care that your application isn't posting back, but they will think it's broken if the back button doesn't do what they expect it to.