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のページではバックボタンで今までの作業がすべて白紙に戻されるため、非常に頭にきます。しかし今回の方法を使えば、最後の状態に戻せます。逆に言うと、最後の状態にしか戻せません。新たな利便性を提供できないのは残念ですけど、少なくとも作業は失わないで済むのです。これは非常に大きな違いだと思います。