GitHub が採用している、非同期でぬるぬる動く画面遷移、これ pushState と Ajax を組み合わせたテクニックで実現されているんですね。その名も Pjax。
HTML5 の history.pushState を使うからブラウザの履歴にも対応でき、しかも URL がキレイ。Pjax についての詳細な説明は下記のエントリが参考になりました。
Pjax 始まったな。
|i \ |.| ト\ /| ト | トヽ / | | ト | | トヽ\/| | | ト / | | | ト\≧三ミゞ=イ/ ム彡''´ ̄ ̄  ̄ ヽ{__.. / V´ ノ __ ', ,. == y ̄, __、\_ ) 世 界 的 で す も ん ね |i }-| ゝ二 |/ ̄ ̄ /ニ,l ヽ__ノ/ヾ _ ノ > }} / >≦'__ し / 乗 る し か な い Vて二オカ (_,/} Yこ二ノ!!| } こ の ビッ グ ウ ェ ー ブ に Y⌒ 从 ∠) 从从从トミ _.ィニ二 ̄丶 ミ三三彡 ' ´ \ \ / \ヽ / ミ;,. ', ', | _ _ __ \',.', ノ! | V7\ ´/ / l /_ゝ| ト >__/ / | ヽン ´ ヽー' i| l |:! ヽ | | ト、 `ミ, l
さっそく、Github から最新の jquery.pjax を入手して試してみます。
Web アプリは App Engine 向けに作ることが多いので、今回も当然のように App Engine/Python + Kay Framework でサンプルを作成しました。
# -*- coding: utf-8 -*- """ core.views """ from datetime import datetime from kay.utils import render_to_response, get_response_cls def index(request, name=None): return greet(request) def greet(request, name="world"): message = "Hello %s!" % name # Pjax のリクエストはヘッダに X-PJAX がついている if "X-PJAX" in request.headers: # Pjax のときはコンテンツだけを返す return get_response_cls()("Hello %s!" % name) else: # Pjax じゃないときはページ全体を返す data = dict( now=datetime.now(), message=message) return render_to_response('core/index.html', data)
# -*- coding: utf-8 -*- # core.urls # from kay.routing import ( ViewGroup, Rule ) view_groups = [ ViewGroup( Rule('/', endpoint='index', view='core.views.index'), Rule('/greet/<name>', endpoint='greet', view='core.views.greet'), ) ]
<!DOCTYPE HTML> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Pjax Test</title> </head> <body> <div id="header"> <h1>Pjax Test({{ now }})</h1> </div> <div id="navi"> <!--pjax を適用するアンカータグにクラス js-pjax を指定--> <a class="js-pjax" href="{{ url_for('core/index') }}">index</a> <a class="js-pjax" href="{{ url_for('core/greet', name='foo') }}">foo</a> <a class="js-pjax" href="{{ url_for('core/greet', name='bar') }}">bar</a> </div> <div id="main"> <!--ここが書き変えられる--> {{ message }} </div> <script type="text/javascript" src="{{ media_url }}/js/jquery-1.6.2.js"></script> <script type="text/javascript" src="{{ media_url }}/js/jquery.pjax.js"></script> <script type="text/javascript"> $(function() { // PJax 適用 $("a.js-pjax").pjax("#main"); }); </script> </body> </html>
これで上手くいくと思ったんですが、リンクをクリックしても #main の中身が非同期で書き換えられません。あれ?使い方間違ってる?ReadMe の通りに実装したんですけど…。
Firebug でデバッグしてみたら、下記のエラーがコンソールに表示されていました。
options is not defined エラー this.trigger('pjax.start' [xhr, options])
さらにデバッグを進めたところ、jquery.pjax.js の下記の場所で失敗。
pjax.defaults = { timeout: 650, push: true, replace: false, // We want the browser to maintain two separate internal caches: one for // pjax'd partial page loads and one for normal page loads. Without // adding this secret parameter, some browsers will often confuse the two. data: { _pjax: true }, type: 'GET', dataType: 'html', beforeSend: function(xhr){ this.trigger('start.pjax', [xhr, options]) // ← options が無い! xhr.setRequestHeader('X-PJAX', 'true') }, error: function(){ if ( textStatus !== 'abort' ) window.location = options.url }, complete: function(){ this.trigger('end.pjax', [xhr, options]) // ← xhr と options が無い! } }
この options はどこで定義されてるんですかね?ざっとみた感じでは見当たりません。ローカル変数ではいろんなところで定義されていますけど。
Pjax を呼び出している部分を下記のように修正したら回避できました。
$(function() { // Pjax 適用 $("a.js-pjax").pjax("#main", { timeout: 3600, beforeSend: function(xhr) { // beforeSend を指定 this.xhr_ = xhr; // xhr を保持しておく this.trigger('start.pjax', [xhr, this]) xhr.setRequestHeader('X-PJAX', 'true'); }, complete: function() { // complete も指定 // 保持しておいた xhr を使う this.trigger("end.pjax", [this.xhr_, this]); } }); });
めでたしめでたし。
それにしても、これってバグ?それとも使い方間違ってる?みんなすんなり使えてるみたいなので、私の環境だけなんでしょうかね…。