Kamishibai vs. Server-generated Javascript Responses

We’re writing some new capabilities for our Kamishibai.js Javascript library that powers our Ponzu conference system.

I haven’t documented Kamishibai.js, and at this point, it’s not even an independent library. Still, I just wanted to note a few things that crossed my mind recently.

Enough with the JavaScript Already!

A well written slide stack by Nicholas C. Zakas.

After consulting with several companies on performance related issues, it became clear that one of the biggest performance issues facing websites today is the sheer amount of JavaScript needed to power the page. The demand for more interactive and responsive applications has driven JavaScript usage through the roof. It’s quite common for large sites to end up with over 1 MB of JavaScript code on their page even after minification. But do today’s web applications really need that much JavaScript?

The answer in my opinion is no. Not nearly. Kamishibai.js is less than 50KB minified without HTML templates. It is smaller than the jquery.min.js file.

Server-generated JavaScript Responses

Written by David Heinemeir Hansson on the Signal v. Noise blog.

The essence of Server-generated JavaScript Responses (SJR) is as follows;

  1. Form is submitted via a XMLHttpRequest-powered form.
  2. Server creates or updates a model object.
  3. Server generates a JavaScript response that includes the updated HTML template for the model.
  4. Client evaluates the JavaScript returned by the server, which then updates the DOM.

I totally agree to this approach. In Kamishibai.js, we extend it in the following ways;

Instead of returning a Javascript response in 3, we usually send a simple HTML fragment. The Kamishibai.js library looks at our HTML fragment and searches it to see if any of the top level element ids are already present in the DOM. If so, then Kamishbai.js replaces the content of the DOM with the content in the HTML fragment. This allows us to do common DOM-replacements without any Javascript. If you want to add animations, you can do this declaratively through HTML data-attributes in the HTML fragment.

Another extension is the use of JSON. We totally agree that returning HTML is better than JSON if performance or readability of your code is your main issue. However in Kamishibai, we cache responses in localStorage which is very limited in capacity. Since JSON can be made many times more compact than HTML, we use JSON for the responses that we need to store a lot in localStorage.

In Kamishibai.js, we take a progressive approach to Javascript HTML templates. We start by returning HTML fragments. When we think we want to send a view with JSON, we write a Javascript HTML template and a JSON response for that view. Kamishibai.js can automatically determine if the response is an HTML fragment or JSON which should be used with a template. If it is JSON, then it summons the appropriate template and converted the JSON to an HTML fragment. That HTML fragment is further processed to be inserted into the DOM.

Summary

Kamishibai.js uses Javascript to generate pages, but the code is small and simple. We just expand on some concepts by those who eschew complex Javascript libraries, and provide the Javascript to make these approaches easier to follow.

I hope to write more on Kamishibai.js in the future.

“You might not need jQuery”

I stumbled on the You Might Not Need jQuery website, and I think that it’s a fantastic idea.

What it does is that it compares code written using jQuery, and code written in plain Javascript. If you develop mainly in jQuery, this helps you to write the same code in plain Javascript. On the other hand, if you are like me and don’t like jQuery for any reason, then it’s a good resource to learn from other people’s code.

It also links to some good libraries that can be used independently of jQuery.

Personally, I find using jQuery quite annoying for the following reasons.

  1. It can noticeably slow down page loading, especially on mobile. It even slows down PCs with Core i5 processors by 100ms.
  2. The jQuery website boasts that it’s only 32kB minified and gzipped, and they call it lightweight. On the contrary, the Ponzu system that I’m developing, which uses Javascript for AJAX, hashtag-based navigation, localStorage-based page caching, JSON-driven HTML templates and more, is less than 20kB total (minified and gzipped). It’s hard to justify 32kB when the vast majority of code is not going to be used.
  3. There are often too many functions doing similar things (and I’m saying this coming from Ruby, which also has a lot of redundant functions). Event handling is especially an area that put me off.

In Ponzu, we use jQuery only if the client is Internet Explorer. We have a small number of shims that use jQuery as a compatibility layer.

Analysis of How External Scripts Can Block Page Loading

I recently found two excellent blog articles (1, 2) by Steve Souders on how a single externally loaded Javascript, CSS and fonts can block page loading, resulting in completely or partially blank pages. Steve calls these Frontend SPOF (Frontend Single Point of Failure) and shows some screenshots of what the pages actually look like. I have copied one below.

NewImage

This screenshot was taken when Steve was in Beijing and was caused by the Chinese firewall blocking the domain cdn.eyewonder.com. I also had a similar experience when I was in Shanghai and the firewall blocked a Twitter badge that I had (I was using an old synchronous Twitter badge. Twitter now uses synchronous badges only, which do not cause fronted SPOF).

However, this experience is not limited to China. Technically, this can happen whenever loading of a resource takes a lot of time. This often happens when you have a bad Internet connection, for example when you have a weak 3G connection or you have an unreliable WiFi setup. I experienced this a lot in conferences and exhibitions where not enough money was spent to beef up the WiFi.

It is unfortunate that many web developers are still unaware of this issue.

Javascript Callback Hell and how I cope

I’m not a heavy Javascript coder, but I have already experienced “Callback Hell” more that I would like.

“Callback Hell” is well describe in this website, and they provide some ways to remedy. I however have some issues with this approach.

I’ve copied their example for “Keep your code shallow”, which I understand is the most generic solution (meaning the solution that doesn’t depend on external libraries).

function formSubmit(submitEvent) {
  var name = document.querySelector('input').value
  request({
    uri: "http://example.com/upload",
    body: name,
    method: "POST"
  }, postResponse)
}
function postResponse(err, response, body) {
  var statusMessage = document.querySelector('.status')
  if (err) return statusMessage.value = err
  statusMessage.value = body
}
document.querySelector('form').onsubmit = formSubmit

The issue that I have with this code is that there is no central function that gives me an outline of the program flow. For example, if you wanted to know how error messages are displayed, you would have to first go into formSubmit. There you notice that the callback for the request is postResponse so you read the postResponse function. There you finally find how the error messages are handled.

Having the program flow scattered all over the place makes my head hurt. I need to know how the functions in my code fit together. Especially if you are writing some controller code, you are most likely going to summon functions from a lot of different objects/classes and having to wade through those to understand program flow is something that I want to avoid. I want an overview of the program flow in one single location. I’ve tried “Keeping my code shallow”, but in my experience, it actually made the situation worse.

Coming from a Ruby background where the mantra is to keep methods short and clean, Javascript functions look awfully long and complicated. You feel an urge to separate these into discrete functions and this works if your code is synchronous. When you are dealing with asynchronous code however, you often find yourself writing the same kind of code described above, and you find that it’s more difficult to understand the refactored code than the initial monolithic function. This drove me crazy.

If you could write synchronous code, you could write something like this, which is how I would want to write it;

function formSubmit(submitEvent) {
  var name = document.querySelector('input').value
  response = request({
    uri: "http://example.com/upload",
    body: name,
    method: "POST"
  })
  postResponse(response)
}

The benefit of the synchronous code is that you now have a central location which dictates the flow of the code. You know upfront that your choices for hunting down the error handling code is either the request function or the postResponse function, and judging by the names, postResponse looks more likely. Compare this with the synchronous code where you only learn of the existence of the postResponse function after you read the call to request inside of formSubmit.

This becomes a huge problem when the code is more complex and you need to process the request through multiple steps (resulting in multiple nested callbacks).

My approach is to code like the following;

function formSubmit(submitEvent, callback) {
  var name = document.querySelector('input').value
  request({
    uri: "http://example.com/upload",
    body: name,
    method: "POST"
  }, callback)
}
function postResponse(err, response, body) {
  var statusMessage = document.querySelector('.status')
  if (err) return statusMessage.value = err
  statusMessage.value = body
}
document.querySelector('form').onsubmit =
  function(event){formSubmit(event, 
    function(){postResponse(err,response,body)}
  )}

This style makes it clear that formSubmit subsequently calls postResponse.

If you need to do some more complex processing, you can write the following;

function formSubmit(submitEvent, callback) {
  var name = document.querySelector('input').value
  request({
    uri: "http://example.com/upload",
    body: name,
    method: "POST"
  }, callback())
}
function postResponse(err, response, body, callback) {
  var statusMessage = document.querySelector('.status')
  if (err) return statusMessage.value = err
  statusMessage.value = body
  callback()
}
function doSomethingExtra(callback){
  // Do something here
  callback();
}
function doEvenMore(callback){
  // Do something here
  callback();
}
document.querySelector('form').onsubmit =
  function(event){formSubmit(event, 
    function(err,response,body){postResponse(err,response,body,
      function(){doSomethingExtra(
        function(){doEvenMore()}
      )}
    )}
  )}

It is still super easy to understand the program flow. If you did something like this with the normal approach, you would have to first look into formSubmit, postResponse, doSomethingExtra and finally doEvenMore.

If you use Coffeescript, then the code would look like the following, which makes the intent of the code super easy to understand;

document.querySelector('form').onsubmit = (event) ->
  formSubmit event, (err, response, body) ->
    postResponse err, response, body, ->
      doSomethingExtra ->
        doEvenMore()

The interesting thing about the Coffeescript code is that with the functions statements out of the way, it starts to look like synchronous code with the exception that each consecutive method call is indented one step more than the previous. What I really like is that the program flow is as easy to understand as synchronous code.

Not being a heavy Javascript coder, I haven’t really put my head into error handling and such so I’m sure there is a lot that I’ve overlooked. I’m just posting here the way that I’m overcoming “Callback Hell”, and I really like it compared to the alternative remedies that I find on the web.

Really, really simple way to use the back button on AJAX websites


Update
In the example code, I used an input type=”text” to store the state value. In order to hide this control, you should set the style to “display:none” instead of using an input type=”hidden” form control. Browser implementations are more likely to ensure that input type=”text” values are restored, and Safari 5 for example, decided that type=”hidden” values should not necessarily be preserved (Webkit Bugzilla). Using input type=”text” with style=”display:none” will work more reliably across browsers.

I was searching the web for techniques enabling the “back” button on my AJAX web site. Unfortunately, the only ones that I could find were really “hacky”. There are a couple libraries in active development like Really Simple History (RSH)YUI History by Yahoo! However, contrary to the name “Really Simple”, RSH appears to be a huge polling hack behind the scenes, running a Javascript every 0.5 seconds even if the user is doing absolutely nothing (This polling should become unnecessary with the HTML5 “window.onchangehash” event, that is currently supported in the most recent IE and FF versions, and Webkit nightlies). As far as I can tell, YUI History is doing more or less the same thing. Since you should be designing your site to be non-reliant on the forward and back buttons anyway, it is hard to justify intrusive polling in return for this hopefully rare functionality. The idea made me very uncomfortable.

On the other hand, if the “back” and “forward” buttons do not work as expected, you are surely going to make more than a few users quite infuriated. The typical AJAX application behavior when you push the “back” button is to completely erase everything that the user did, and send them to the fresh page as it was when they first visited it. Essentially, you are sending the users back to the entrance door. Sounds like you won’t be seeing them again any time soon.

Luckily, I managed to come up with a solution that works with browsers as erratic as IE6, does not require loading a library, and should only require a small amount of additional code. Although it only solves a small part of the browser history and “back” button problem, I think it solve the most irritating issues quite sufficiently, such that the user won’t feel one bit the urge to slam shut his laptop and pour himself a strong cup of coffee.

What is the “Back” button problem?

I probably don’t have to teach this to people who have done a fair amount of AJAX programming, but I will discuss it to clarify what my solution does and doesn’t solve.

AJAX is a technique that enables the browser to update only a part of a page, and is very useful for improving the perceived responsiveness of your web site. However, AJAX updates generally do not rewrite the location in the browser location bar. Since it is this location value that the browsers use to maintain their histories and to enable the back and forward buttons, AJAX renders these buttons useless. These buttons will typically send you to the first state that you saw when you first visited the web page, before any of the AJAX updates took place. In effect, all the work that you did since you visited the page will be lost (unless of course, the AJAX updates themselves rewrote the information on the server).

For example, imagine that you are visiting a web catalog site that lists electronic products, and uses AJAX to make browsing the product categories nice and quick. Imagine that the the user decided to do a text search after reaching a category deep in the hierarchy (“mac” in the illustration below). Also let’s assume that this product search fires a regular HTTP request (non-AJAX). Now after viewing the results, what would the user do if he decides to take another look at the “mac” category? Instinctively, he would push the “back” button on his browser, sending him unwittingly to the root category of the product catalog. You can almost see the cold sweat on his face as he realizes that he might have lost any way to get back to the “mac” category, save for going through the whole product hierarchy again. Pushing the “forward” button wouldn’t help either, since he would be taken straight to the search results page, right where his woes began.

Ajax javascript back button.jpg

We can easily see why the AJAX “back” button issue can cause such anger and frustration. Despite all your AJAX efforts to make navigation and data entry nice and quick, the “back” button issue will strip you of all the goodwill that was earned. Most likely, the users will even leave with a worse feeling than before they first visited.

The normal solution

Although there are many posts on the web and several libraries to solve the “back” button problem, all of the solutions that I could find on Google were basically variations of the methods used in RSH and YUI History. Namely, to use the hash (“#”) at the end of the location to store the state information in the browser’s history.

With AJAX, the URL can not be changed since this would fire a reload of the whole page. However, the portion after the hash (“#”) is handled differently. Changing this portion (with the Javascript “document.location.hash” property) will not fire a reload, but will cause the whole location to be stored in the browser’s history (at least for non-IE browsers). Therefore, if you store the current AJAX state in the hash portion, you would be able to resolve the “back” button issue. At least in theory.

However, since this is not the intended usage of the hash, browsers deviate in how this is handled. Changing the location does not trigger IE browsers to store the changed location in the browser’s history, and early versions of Safari did not provide a way to read the hash portion after it was changed. To support IE, both RSH and YUI store browser history in an iframe and send an otherwise unnecessary request to the server, an unfortunate hack. An even more troublesome hack is that RSH and YUI use polling to read the hash portion, even with the more recent browsers. Polling is the action of executing a method in regular intervals, even when the user is not interacting with the browser. With RSH, the polling frequency is an alarming 0.5 seconds. This can cause the whole computer to slow down and will cause laptops to consume more battery charge. Polling is generally a despised hack that most programmers try hard to avoid. (As mentioned above, HTML5 provides the window.onhashchange event so that you don’t have to rely on polling, but this is only supported on the most recent of browsers.)

Going through all these loops and potentially causing a slow down on the user’s computer sounds simply too much to solve the “back” button problem. HTML5 will hopefully make things much easier, but as things stand today, I think we need a different solution.

A solution using form elements

The issue with the “back” button boils down to how we can store AJAX state information. As described above, I think that using the hash (“#”) in the location is much too troublesome. In the method below, I used regular form elements.

Form elements do preserve their contents between “back” button clicks. Remember when you were editing a form on a web site, and you realized that you had made a mistake after hitting the submit button. If you go back to the data entry page by clicking the “back” button, you will see all the form elements containing the same information they had just before you submitted it. Most browsers (even the older ones), automatically restore this information, even if they are different from the default values in the HTML. In my method, I use this feature to store the AJAX state. (better description here)

Browsers store this information inside their caches. If this solution doesn’t work on your web app, try adding the HTTP header “Cache-control: private” in your response, which should tell browsers that they can cache content locally.

I’m not sure if the above behavior is dictated by a standard, or is just how the browsers have opted to behave. I do think that this feature is central to the end-users’ browsing experience, and this is probably why this solution seems to work on all browsers without any hacking.

With the form solution, we do not create a history entry, and as a result, we can only solve a small portion of what RSH and YUI History provides. This small part however, is in my opinion, the most frequent source of end-user headaches, and is by far the most important problem. Being able to solve this with a simple solution should really help.

The Code

The code below is all that I need to run a demo and illustrate the method. I am using Prototype.js to manipulate the DOM, but it should be possible to write it just as easily with raw JavaScript (I’m just too lazy to do it). I also have a demo site to make it easier to follow my explanations.

 1  <html>
 2  <head>
 3    <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/prototype/1.6.1/prototype.js"></script>
 4    <script>
 5      function setColor() {
 6        $('field').setStyle('background-color:' + $F('field'));
 7      }
 8    </script>
 9  </head>  
 10 <body>
 11   <form>
 12     <input type="text" id="field" value="red"/>
 13   </form>
 14   <a href="Javascript:void()" onclick="$('field').setValue('blue');setColor();">blue</a>
 15   <a href="http://google.com">go to Google</a>
 16   <script>
 17     setTimeout("setColor();", 0);
 18   </script>
 19 </body>
 20 </html>

This demo works as follows.

  1. When you click the link “blue”, the content of the text field and its background color are changed to blue.
  2. Then go to another web site by clicking on the “go to Google” link that I provided.
  3. When you return to the demo web page by clicking the “back” button, the text field will contain the word “blue” and the background-color will also be blue. Just the way it was before going to Google. Compare this to another demo page with the 17th line commented out.

A description of the source follows.

  1. Lines 11-13 are a form to preserve the state (the state form). When the “back” button on the browser is pressed, the state values are restored from the browser cache.
  2. Line 14 is the link “blue”. When this is clicked, the value of “blue” is written to the state form with “$(‘field’).setValue(‘blue’)”. The next “setColor();” command changes the background color to synchronize with the value in the state form.
  3. Lines 17 is executed when the page is first loaded, and after the page is redrawn after coming back with the “back” button. The “setColor();” command will ensure that the background color is synchronized with the value in the state form. Safari and FF work fine without the setTimeout wrapper, but I found that it is necessary to get it working on IE when you come back with the “back” button.
  4. I have checked that this demo works on FireFox3.6, Safari 4 and IE6-8.

In this simple example, I only changed the background color. With an AJAX web app, you would save the AJAX request parameters in the state form, either before or after you made the AJAX request. You would also provide a command that sends the AJAX request after coming back with the “back” button, so that the DOM state will be synchronized with the state form values, which have been automatically restored from the browser hash.

What you can and what you cannot do with this method.

With this method, you can use the “back” button to return to your prior state after being sent to a new URL. You will be returned to the very last state. You cannot return to any intermediate AJAX state.

Ajax javascript back button 2.jpg

Of course, this is not a 100% solution covering bookmarking and stuff. I do believe that it does however cover more than 80% of the end-user needs. With a regular AJAX page, the end-user will lose all of their efforts; every click and every form entry. You are essentially showing the end-user the exit, and telling him that he doesn’t have to bother coming back. With the method that I described, the end-user will be sent back to his final state and he can simply modify things without going through the same procedure from the start. The equivalent of politely asking “Pardon. Could you please repeat that final request, Sir?”.

AJAXで書き換えたウェブページでバックボタンを使う


アップデート
下記のコード例では状態を保存するために input type=”text” を使いました。この値を表示したくない場合は、input type=”hidden” を使わずに style=”display:none” とした方が良いようです。ブラウザによっては(例えばSafariの5以降) type=”hidden”の値が保存されるとは限りません(Webkit Bugzilla)。input type=”text”とstyle=”display:none”の組み合わせに方が、異なるブラウザ間で確実に動作してくれそうです。

AJAXをたくさん使ったページでバックボタンを使えるようにしたいと思って、ウェブでいろいろ探しました。見つかったのは例えば Really Simple History (RSH)YUI Historyなどで、以下に述べるようにかなり怖いぐらいのハックをしているものでした。とても手軽に使う気にはなれません。(どれも結構歴史のあるものですが、最新バージョンでも同じハックを使い続けていると思います)

とは言うものの、バックボタンやフォワードボタンがうまく使えないと、ウェブサイトの利用者を非常に不満を与えてしまうことがあります。なんとか解決策は無いかと考えていたところ、非常にコードが少なく、なおかつ古いブラウザでもそのまま使える簡易的な方法が見つかりましたので以下に紹介します。

バックボタンの問題とは?

AJAXをたくさん使ってプログラミングしている人には紹介するまでもないのですが、今回の解決策の範囲を示すためにバックボタンの問題を簡単に紹介します。

AJAXはページ全体を再描写しないで済むので、レスポンス間の良いウェブサイトを作るのに非常に適しています。しかしAJAXでページを細かく書き換えても、ブラウザが指しているURL(ロケーションバーのURL)を全く書き換えられません。バックボタンはロケーションバーのURLに連動して動作しますので、結果として思い通りの動作をしてくれません。

例えば製品カタログのウェブサイトを作っているとします。カタログの中を調べていくとき、AJAXで画面を再描写するようにしてレスポンスを向上させていたとします。そしてある程度深い階層(ここでは「マック」)にユーザが入ったところで、検索を行ったとします。この検索はAJAXではなく、普通のHTTPでページを切り替えたとします。さて問題となるのは、検索を行ったユーザがバックボタンを押したときです。ユーザとしては「マック」の階層に戻ることを期待しています。しかしAJAXを使った場合はURLは最初のページのままなので、実際には「製品カタログ」のトップページに戻ってしまいます。ユーザが苦労してやっと「マック」のカテゴリーにたどり着いたのに、もう一度最初からやり直さなければならないのです。フォワードボタンを押そうが何をしようが無駄です。今までの作業は永遠に消えてしまうのです。

AJAX_back_button.jpg

このようにAJAXのバックボタン問題は、ユーザに大きな不快感を与えます。せっかくAJAXで使いやすいサイトに作ったとしても、バックボタン問題でそのすべてが吹っ飛んでしまいます。逆にネガティブになるリスクも高いと思います。

一般的な解決策

グーグルで調べた限り、ブラウザのロケーションのハッシュ部分にステートを保存する解決策やライブラリーが多く作られているようです。上述のRSHやYUI Historyと同じやり方です。

AJAXでは基本的にはURLは変わらない(変えられない)のですが、URLの”#”の後ろの部分(ハッシュ)だけは違います。この部分はブラウザの履歴に保存されますので、バックボタンを押したときにも残っています。そこでAJAXページの最後の状態(ステート)をハッシュ部分に保存すれば、原理的にはバックボタン問題を解決できます。

しかしロケーションのハッシュ部分にステートを保存するというのはかなり特殊な使い方で、ブラウザによってはよっぽど変わったことをやらないとうまく動作しないようです。例えばRSHとYUIのライブラリーではIEをサポートするために新たにiframeを作成し、本来必要の無いリクエストをサーバに送っているようです。YUIは古いSafariのバージョンではもっと訳の分からないことをやっています。またSafari 3 4やFireFoxなどの最新のブラウザでもpollingという禁断の技法を使っています。Pollingというのは例えば0.5秒おきにプログラムがロケーションを確認するというもので、ユーザがブラウザを全く触っていないときでも動作させるやり方です。パソコンの動作を無駄に遅くさせ、電池を消耗させることから、非常に嫌われている方法です。

バックボタンという比較的シンプルな機能を実現するためにこんなに無理をし、ユーザのパソコン全体の動作を重くさせていいのでしょうか。僕は絶対にこれはやりたくないので、この方法は使えません。ただHTML5ではdocumentwindow.onhashchangeというハンドルが加わり、将来的にはpollingは不要になりそうです。またブラウザの履歴をより直接的に操作できるようにもなるらしいですので、そのときはもうちょっとマシな方法が一般的になっていくでしょう。でも今はとにかくダメなので、別の方法を探りました。

フォームを使った解決策

バックボタンの問題を解決するためには、ブラウザのステートを保存しなければなりません。それを上述のようにロケーションバーに保存するのは現時点では無理があります。ここで紹介する方法ではフォームの中に保存しています。

例えばブラウザでフォームを編集して、それを送信した後、間違いに気づいたとします。通常バックボタンを押せば、送信する直前の状態、つまり各フィールドに情報を入力した状態に戻ります。このように、大部分のブラウザではフォームの内容を保持していて、バックボタンを押したときにそれが復活するようになっているのです。今回はこれを利用します。

フォームの内容を保持することは、ブラウザを使っているすべての利用者の利便性に直接関わります。ですから古いブラウザでも新しいブラウザでも同じ動作をしてくれますし、パソコン全体の動作を重くしてしまう心配もありません。その代わり、RSHやYUIと比べると実現できる機能はごく一部になってしまいます。しかしフォームを使って実現できるわずかな機能が、最も重要な機能だと思います。具体的には以下に紹介していきます。

コード

以下に示したのが、今回の方法を説明するのに必要なコードのすべてです。DOMを操るためにPrototype.jsを使っていますが、非常に簡単なことしかしていませんのでJQueryや生のJavaScriptしか使っていない人でも分かりやすいと思います。このコードを載せたデモサイトも用意しました。

 1  <html>
 2  <head>
 3    <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/prototype/1.6.1/prototype.js"></script>
 4    <script>
 5      function setColor() {
 6        $('field').setStyle('background-color:' + $F('field'));
 7      }
 8    </script>
 9  </head>  
 10 <body>
 11   <form>
 12     <input type="text" id="field" value="red"/>
 13   </form>
 14   <a href="Javascript:void()" onclick="$('field').setValue('blue');setColor();">blue</a>
 15   <a href="http://google.com">go to Google</a>
 16   <script>
 17     setTimeout("setColor();", 0);
 18   </script>
 19 </body>
 20 </html>

このデモの動作は以下のようになります。

  1. “blue”のリンクをクリックすると、Javascriptを使ってテキストフィールとのバックグラウンドカラー(background-color)を変えます。
  2. そのあと”go to Google”のリンクをクリックして、google.comのページに移動します。
  3. ブラウザのバックボタンを押してこのデモのページに戻ったとき、テキストフィールドは最後の”blue”のままです。何もしていない普通のページであれば、テキストフィールドのバックグラウンドカラーは”blue”のリンクをクリックする前の色であったはずです。17行目をコメントアウトしたデモサイトも用意しましたので、動作を見比べてください。

プログラムを解説します。

  1. 11-13行目はステートを保持するためのフォームです。ブラウザのバックボタンを押すとこのフォームの内容は直前の状態になります。
  2. 14行目が”blue”のリンクです。これをクリックすると”$(‘field’).setValue(‘blue’)”でステート保持フォームに”blue”が書き込まれます。次に”setColor();”を呼んで、ステート保持フォームの内容がバックグラウンドカラーに反映されるようにしています。
  3. 16-17行目はページが最初に読み込まれたり、バックボタンで再表示されたときに実行されるJavascriptです。”setColor();”を読んで、ステート保持フォームの内容がバックグラウンドカラーに反映されるようにしています。なおSafariやFFではsetTimeoutで囲まなくてもうまく動作してくれたのですが、IEの場合はsetTimeoutで囲まないと、バックボタンを押したときに動作してくれませんでした。
  4. 動作確認はFireFox3.6, Safari 3 4, IE6-8で行いました。確認した限り、どれも同じように動作しました。

今回はバックグラウンドカラーを変えるという簡単なデモでしたが、AJAXを使ったウェブページであればAJAXのパラメータをステート保持フォームに保存することになるでしょう(AJAX呼び出し直前もしくは直後にJavascriptでこのフォームにパラメータを保存します)。バックボタンを押してこのページが再読み込みされたときは、まずステート保持フォームが空かどうかを確認し、空でなければそのパラメータでAJAX呼び出し行います。空であればAJAX呼び出しを行いません。

この方法でできること、できないこと

この方法でできるのは、新しいURLに移動した後、バックボタン(もしくはフォワードボタン)で戻る動作です。そしてAJAXを使ったページの一番最後の状態に戻ります。AJAXページの途中の状態に戻ることはできません。フォワードボタンでも同様です。

Ajax javascript back button 2.jpg

もちろんこれで100%十分という訳ではないのですが、恐らく80%近くは行けてると思います。単なるAJAXのページではバックボタンで今までの作業がすべて白紙に戻されるため、非常に頭にきます。しかし今回の方法を使えば、最後の状態に戻せます。逆に言うと、最後の状態にしか戻せません。新たな利便性を提供できないのは残念ですけど、少なくとも作業は失わないで済むのです。これは非常に大きな違いだと思います。