The Future of Web Apps - Single Page Applications

Mark Boas

The world wide web is constantly evolving and so is the way we write the applications that run upon it. The web was never really designed as a platform for today’s applications, nevertheless we continue to bend it to our will.

Due to differing paradigms we are forced to design our web apps in a completely different way to native apps. Some of the most obvious constraints are those imposed by using the traditional multiple page model, when employed this model clearly illustrates the difference in performance between native and web apps. Many developers will probably feel that the multi-page approach is the price we pay for having search engine indexable, back-button supporting, bookmarkable and accessible applications.

The price is high.

In this article I propose that we can have our cake and eat it. We can have all the benefits of a multi-page web application in a single page. The objective then, is to outline the advantages of this approach and to describe its implementation. To do this I’ve taken an application that is dear to my heart, the humble audio application, and have tried to resolve one of the main issues - the uninterrupted playback of audio throughout.

But first let’s take a look at the advantages of a single-page approach. What it can do.

1. Make your app as ’snappy’ as a native app.

What we are essentially talking about here is having an application where ‘virtual pages’ are loaded into one single web-page, which means switching between pages need not involve a trip to the server and so the switch occurs almost instantly.

2. Reduce load on your server.

Using the ‘traditional’ approach we load a lot of duplicated content for each page we visit. A single page approach avoids this repetition and so makes your application more efficient to code and run.

3. Give yourself more freedom.

When all ‘pages’ are accessible from one page you give yourself more freedom to manipulate the content of these ‘pages’ client-side. You can easily take content from one page and insert into another. You also have the option of keeping certain aspects of your application ‘fixed’, that is to say that state can be easily preserved as you don’t need to store/retrieve state between page reloads anymore.

We can do all this and still have a site that allows full SEO (Search Engine Optimization) - one of the biggest concerns when developing this type of app.


Making it Work

OK so hopefully I’ve convinced you of the benefits - lets talk a little about how we can achieve them.

I’m assuming that for a web application we are using some kind of server-side language. In this example I’m using PHP. We could also do with some client-side goodness and for that I’m using jQuery.

Note that the approach I use in this example falls back to a traditional multi-page approach when JavaScript is not available. You can call this progressive enhancement or graceful degradation depending upon your perspective. Either way this means that there is no reason that you cannot make your application fully accessible.

Next let’s take a look at the example application. I’ve kept it fairly simple while implementing some functionality which lends itself to the single page approach. We have three ‘virtual pages’ that can be accessed via tabs. Each of these pages displays a tracklisting of an album. You can switch between these albums quickly and easily as the content is already loaded, you can also sample the tracks or add them to a playlist. On the right hand side we have a music player with an integrated playlist. You can keep this player playing throughout as state is easily maintained.


Try the Demo

Single Page Web App Screenshot

Click on a few tabs and try out the back buttons, reload the page at any given time and that ‘tab’ state should be maintained, which means that each of the containing pages are bookmarkable. If you like, you can disable your browser’s JavaScript and revisit the link http://happyworm.com/jPlayerLab/single-page-app/ - you should notice that the site falls back to the traditional multi-page model. This incidentally is what a search engine crawler will see, which explains why the content is ‘indexable’. Obviously our jPlayer plugin for jQuery (the bit that handles the audio) will not work with JS disabled however you could take the non JS version as far as you wanted, perhaps using HTML5 audio to allow the user to play tracks in-page.

OK so now you know what it can do, let’s take a look at how it works. I’ve tried to create this example in the most efficient manner possible but I’m sure there is room for improvement. One of the most important aims for me was the non-repetition of code or content. If you change the content it is changed for both non-JS and JS version alike. I’ve taken this quite far but for the sake of simplicity a small amount of repetition remains.

Application Outline

Application Outline

1. index.php is called and body.php loaded into it.

2. If JS disabled body.php links to first.php, second.php and third.php which in turn contain body.php. So we have 3 separate pages. body.php also loads in first.htm, second.htm and third.htm depending on the tab parameter passed.

3. If JS enabled body.php loads first.htm, second.htm and third.htm into tabs (via the JS at the top of index.php).

So effectively, when JS is enabled we are loading all the tab’s contents into the same page. The tabs link to separate pages which load that same tab content, but of course these links are ignored when JS is enabled (because we return false from their click handlers).

With me? If not you may want to take a look at the source.

But what about back-button functionality, page state and bookmarkability? Fortunately this is all taken care of by Ben Alman’s excellent BBQ plugin We add the page name to a hash in the URL and BBQ pretty much takes care of the rest.

Incidentally this approach will work in all major browsers including IE6 and upwards. It even works on the iPad. I may be missing something but I don’t see why every web application shouldn’t be created in this way. Sure, there is a small amount of added complexity, and perhaps the initial page load takes slightly longer, but that seems like a reasonable price to pay.

Thanks to Ben Alman and thanks to Remy Sharp for providing the inspiration for the tab functionality.

Grab the Source

[Edit : 2010-9-1 Changed the source code to include fixes.]

Monday, August 23rd, 2010 AJAX, Audio, SEO, Uncategorized 21 Comments

Safari Requires QuickTime for HTML5 Media Support

Over the weekend I upgraded to Safari 5.0.1, around about the same time my colleague Mark P informed me of a potential HTML5 audio issue with this latest incarnation of Apple’s browser.

It wasn’t until Sunday afternoon that I finally started to investigate the issue. The recently upgraded version of Safari on my Mac worked fine, in the sense that it passed all the expected tests we’d set up for jPlayer compatibilty. So I decided to investigate further and installed on my little used Windows XP partition. I ran the test and sure enough it failed. Next I installed on a copy of XP running under Parallels Desktop. Same problem. Mark P on the other hand had it working under XP but not under Windows 7. Asking around on Twitter others had no such issues on Windows 7. Most strange.

I started to suspect it was an installation issue, but had no idea of the root cause until someone on the jPlayer group thread we’d started mentioned that they’d had similar issues with Safari 4 and that QuickTime had been the culprit. It turns out Safari relies on QuickTime to play HTML5 based media. Needless to say that this came as a bit of a surprise to us.

So Safari does not then support HTML5 audio or video without QuickTime being installed, just when we were getting used to browsers being ’standalone’. Next to check if Internet Explorer 9 requires Silverlight to support canvas. Imagine the fuss if it did.

(Thanks to @trygve_lie and @getify for their help with this.)

Mark B

Tuesday, August 17th, 2010 Audio, HTML5 1 Comment

Spreading Love and the Load with HTML5

A couple of weeks ago and I came across an interesting HTML5 based experiment. Canvas based image manipulation by Patrick H. Lauke. Thinking this could be quite useful if we could save the resulting image, I cobbled it together with Jacob Seidelin’s Canvas2Image and suddenly I had a working prototype of a jPlayer Skinner.


jPlayer Skinner v0.3

If we were to try and create something similar without using HTML5 we would have needed either to use Flash or manipulate the image on the server. (Let’s just pretend Flash doesn’t exist for the purposes of this post). Even if you put to one side the added complexity of creating and integrating the serverside code, it makes much more sense to do this kind of manipulation clientside - the load is perfectly distributed, in fact all the server has to think about is serving up plain old text.

This is a huge win for HTML5 and one that not many people are talking about. Increasingly we are using client’s CPUs as the workhorse for complex activities that previously could only be achieved on the server. This means more efficent web apps, less traffic between client and server and fewer cycles being consumed on the server.

In short HTML5 helps web apps scale.

Tags: , ,

Wednesday, June 9th, 2010 HTML5 No Comments

HTML5 Video. Who needs Flash?

Nobody it seems, at least not for video. Almost unbelievably in the space of a year, Flash the once ubiquitous format for all web-based video, has become surplus to requirements. Shunned by Microsoft in favour of Silverlight and by other browsers in favour of HTML5. Significantly, now that Opera has joined the ranks of HTML5 video supporting browsers none of the top 5 browsers now require the Flash plugin to play video.

It seems this fact has not been lost on the web development community, with a slew of demos and plugins being released over the last few weeks. One of the first to cause a stir was the excellent SublimeVideo player. Not to be outdone the Ambilight player shows what can be achieved when you integrate HTML5 video with canvas. Video affecting other parts of a web-page, who knew that was even possible?

There’s also been some progress on fallbacks for non supporting browsers. Earlier this week Dave Hall released a ‘patch’ for those browsers that don’t support video natively. This was shortly followed by the release a jQueryless modification by Remy Sharp. This is great news as it means that we can use the <video> tag right now!

But they kept coming. Mario Fischer making an excellent post on how to use HTML5 video with Mootools.

Hot on his heels Michael Dale came out with a HTML5 Video plugin for jQuery. Something close to our hearts here at Happyworm, having made something similar for <audio>.

They say a week is a long time in politics, but this seems doubly true of HTML5 right now. Things are moving apace and they are moving towards a richer web, increasingly less reliant on third-party plugins.

Exciting times indeed.

Mark B

Related:

HTML5Patch Articles
HTML5 The Revolution will not be Televised

Tags:

Friday, March 5th, 2010 Audio, HTML5, Uncategorized 7 Comments

A Simple and Robust jQuery 1.4 CDN Failover in One Line

Mark Boas

Google, Yahoo, Microsoft and others host a variety of JavaScript libraries on their Content Delivery Networks (CDN) - the most popular of these libraries being jQuery. But it’s not until the release of jQuery 1.4 that we were able to create a robust failover solution should the CDN not be available.

First off, lets look into why you might want to use a CDN for your JavaScript libraries:

1. Caching - The chances are the person already has the resource cached from another website that linked to it.

2. Reduced Latency - For the vast majority the CDN is very likely to be much faster and closer than your own server.

3. Parallelism - There is a limit to the number of simultaneously connections you can make to the same server. By offloading resource to the CDN you free up a connection.

4. Cleanliness - Serving your static content from another domain can help ensure that you don’t get unnecessary cookies coming back with the request.

All these aspects are likely to add up to better performance for your website - something we should all be striving for.

For more discussion of this issue I highly recommend this article from Billy Hoffman : Should You Use JavaScript Library CDNs? which includes arguments for and against.

The fly in the ointment is that we are introducing another point of failure.

Major CDNs do occasionally experience outages and when that happens this means that potentially all the sites relying on that CDN go down too. So far this has happened fairly infrequently but it is always good practice to keep your points of failure to a minimum or at least provide a failover. A failover being a backup method you use if your primary method fails.

I started looking at a failover solution for loading jQuery1.3.2 from a CDN some months ago. Unfortunately jQuery 1.3.2 doesn’t recognise that the DOM is ready if it is added to a page AFTER the page has loaded. This made making a simple but robust JavaScript failover solution more difficult as the possibility existed for a race condition to occur with jQuery loading after the page is ready. Alternative methods that relied on blocking the page load and retrying an alternative source if the primary returned a 404 (page not found) error code were complicated by the fact that the Opera browser didn’t fire the "load" event in this situation, so when creating a cross-browser solution it was difficult to move on to the backup resource.

In short, writing a robust failover solution wasn’t easy and would consume significant resource. It was doubtful that the benefit would justify the expense.

The good news is that jQuery1.4 now checks for the dom-ready state and doesn’t just rely on the dom-ready event to detect when the DOM is ready, meaning that we can now use a simpler solution with more confidence.

Note that importantly the dom-ready state is now implemented in Firefox 3.6, bringing it in line with Chrome, Safari, Opera and Internet Explorer. This then gives us great browser coverage.

So now in order to provide an acceptably robust failover solution all you need do is include your link to the CDN hosted version of jQuery as usual :

<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4/jquery.min.js"></script>

and add some JS inline, similar to this:

  <script type="text/javascript">
  if (typeof jQuery === 'undefined') {
     var e = document.createElement('script');
     e.src = '/local/jquery-1.4.min.js';
     e.type='text/javascript';
     document.getElementsByTagName("head")[0].appendChild(e);
  }
  </script>

You are then free to load your own JavaScript as usual:

<script type="text/javascript" src="my.js"></script>

It should be well noted however that the above approach does not "defer" execution of other script loading (such as jQuery plugins) or script-execution (such as inline JavaScript) until after jQuery has fully loaded. Because the fallbacks rely on appending script tags, further script tags after it in the markup would load/execute immediately and not wait for the local jQuery to load, which could cause dependency errors.

One such solution is:

<script type="text/javascript">
  if (typeof jQuery === 'undefined')
     document.write('script type="text/javascript" src="/local/jquery-1.4.min.js"><\/script>');
</script>

Not quite as neat perhaps, but crucially if you use a document.write() you will block other JavaScript included lower in the page from loading and executing.

Alternatively you can use Getify’s excellent JavaScript Loading and Blocking library LABjs to create a more solid solution, something like this :

  <script type="text/javascript" src="LAB.js">
  <script type="text/javascript">
  function loadDependencies(){
  $LAB.script("jquery-ui.js").script("jquery.myplugin.js").wait(...);
  }

  $LAB
  .script("http://ajax.googleapis.com/ajax/libs/jquery/1.4/jquery.min.js").wait(function(){
  if (typeof window.jQuery === "undefined")
     // first load failed, load local fallback, then dependencies
     $LAB.script("/local/jquery-1.4.min.js").wait(loadDependencies);
  else
     // first load was a success, proceed to loading dependencies.
     loadDependencies();
  });
  </script>

Note that since the dom-ready state is only available in Firefox 3.6 + <script> only solutions without something like LABjs are still not guaranteed to work well with versions of Firefox 3.5 and below and even with LABjs only with jQuery 1.4 (and above).

This is because LABjs contains a patch to add the missing "document.readyState" to those older Firefox browsers, so that when jQuery comes in, it sees the property with the correct value and dom-ready works as expected.

If you don’t want to use LABjs you could implement a similar patch like so:

  <script type="text/javascript">
  (function(d,r,c,addEvent,domLoaded,handler){
     if (d[r] == null && d[addEvent]){
        d[r] = "loading";
        d[addEvent](domLoaded,handler = function(){
        d.removeEventListener(domLoaded,handler,false);
        d[r] = c;
     },false);
   }
})(window.document,"readyState","complete","addEventListener","DOMContentLoaded");
</script>

(Adapted from a hack suggested by Andrea Giammarchi.)

So far we have talked about CDN failure but what happens if the CDN is simply taking a long time to respond? The page-load will be delayed for that whole time before proceeding. To avoid this situation some sort of time based solution is required.

Using LABjs, you could construct a (somewhat more elaborate) solution that would also deal with the timeout issue:

  <script type="text/javascript" src="LAB.js"></script>

  <script type="text/javascript">
  function test_jQuery() { jQuery(""); }
  function success_jQuery() { alert("jQuery is loaded!"); 

  var successfully_loaded = false;
  function loadOrFallback(scripts,idx) {
     function testAndFallback() {
        clearTimeout(fallback_timeout);
        if (successfully_loaded) return; // already loaded successfully, so just bail
        try {
           scripts[idx].test();
           successfully_loaded = true; // won't execute if the previous "test" fails
           scripts[idx].success();
        } catch(err) {
           if (idx < scripts.length-1) loadOrFallback(scripts,idx+1);
        }
     }

     if (idx == null) idx = 0;
     $LAB.script(scripts[idx].src).wait(testAndFallback);
     var fallback_timeout = setTimeout(testAndFallback,10*1000); // only wait 10 seconds
  }
  loadOrFallback([
     {src:"http://ajax.googleapis.com/ajax/libs/jquery/1.4/jquery.min.js", test:test_jQuery, success:success_jQuery),
     {src:"/local/jquery-1.4.min.js", test:test_jQuery, success:success_jQuery}
  ]);
  </script>

To sum up - creating a truly robust failover solution is not simple. We need to consider browser incompatabilities, document states and events, dependencies, deferred loading and time-outs! Thankfully tools like LABjs exist to help us ease the pain by giving us complete control over the loading of our JavaScript files. Having said that, you may find that in many cases the <script> only solutions may be good enough for your needs.

Either way my hope is that these methods and techniques provide you with the means to implement a robust and efficient failover mechanism in very few bytes.

A big thanks to Kyle Simpson, John Resig, Phil Dokas, Aaoran Saray and Andrea Giammarchi for the inspiration and information for this post.

Mark B

Related articles / resources:

1.http://LABjs.com

2.http://groups.google.com/group/jquery-dev/browse_thread/thread/87a675bd840ff070/ac60006335f9e23a?hl=en

3.http://aaronsaray.com/blog/2009/11/30/auto-failover-for-cdn-based-javascript/

4.http://snipt.net/pdokas/load-jquery-even-if-the-google-cdn-is-down/

5.http://stackoverflow.com/questions/1447184/microsoft-cdn-for-jquery-or-google-cdn

6.http://webreflection.blogspot.com/2009/11/195-chars-to-help-lazy-loading.html

7.http://zoompf.com/blog/2010/01/should-you-use-javascript-library-cdns/

Tags: , , ,

Thursday, January 28th, 2010 AJAX, configuration, development, jQuery, javascript 6 Comments