pjax こそが pushState + Ajax の本命
pjaxの前にpushStateとは
AjaxとjQueryの説明は不要として、pushStateとはなんぞや。
pushStateを使ってブラウザの履歴に対する操作をし、HTMLの一部のみを書き換える動作でもブラウザの戻る/進む機能を実現できる方法のひとつ。Ajaxなページを再現し、かつURLを見慣れた方法で自然にpermalinkを表現できる。
有名なところではGitHubで使われてるアレ。
hash fragment (/#!/)
ブラウザの履歴を機能させるため、URL の fragment (#) を使ってAjaxなページを実現する方法。一時期もてはやされた感があるが、さらなる「#!」URL批判 - karasuyamatenguの日記 など合理的な反論があり、これから導入するのはためらわれるところ。
有名なところではTwitterで使われているあの厄介者。
pjaxとは
pjax とは pushState + ajax を合わせた語で、その名のとおり pushState を使いつつ Ajax な処理を行う為の jQuery ライブラリ。GitHubのdefunktが開発していることもあり、今後pushStateを使ったものでは導入が進んでいく可能性がかなり高いのではないかと読んでいる。
また、最近ではページの見た目の面では非クロスブラウザを許容する風潮があるように見受けられ、この流れともpjaxは親和性が高い。
「高機能なWebブラウザでは見栄えよく、そうでないWebブラウザで“も”それなりに」
Webページの見栄えにどこまでこだわるのか | 日経 xTECH(クロステック)
pjaxの振る舞いは、ChromeのようなブラウザではpushStateを使い、IEのようなブラウザでは通常のアクセスと同じように、全く同じpermalinkでアクセスできるようにしてくれる。
「コンテンツがcurlでロードできなければそのサイトは壊れている勢力」の救世主
私もこの勢力のうちのひとりだと内心思っているのでやや傾倒している感はある。
しかし hash fragment を使用したページは curl では取得できない。これはサーバ側にfragment以降は送信されないためであるが、publicなものにcurlやLWP::UserAgentやGoogleのクローラーといったクライアントでアクセスさせるためにURLに細工することは解せない。かと言って自分でゴリゴリとpushStateの実装を書くのも骨が折れる。そういった問題を解決してくれるものになると思われる。
使い方
ここではざっくりとした使い方を書いておく。
細かい使い方はいろいろあるようなので、GitHubにあるREADMEを参照すると良い。GitHub - defunkt/jquery-pjax: pushState + ajax = pjax
クライアントサイド
"js-pjax" クラスのアンカーに対してのみ機能させる場合はこのようにセレクタを書く。
<script src="/jquery.min.js"></script> <script src="/jquery.pjax.js"></script> <script type="text/javascript"> $(function () { $('a.js-pjax').pjax('#main'); }) </script>
実装
サーバをPerlで実装したので
$ cd /tmp $ curl https://gist.github.com/raw/901139/c13279a29cfcca8cc75e63fb9eeb65b3ca2785c7/app.psgi -LO $ plackup
で起動し、リクエスト/レスポンスをチェックできる。
app.psgi
use strict; use warnings; use feature qw/say switch/; use Data::Section::Simple; use Text::Xslate; use Plack::Request; my $tx = Text::Xslate->new( path => [ Data::Section::Simple->new->get_data_section ], ); my $app = sub { my $req = Plack::Request->new(shift); my %data = ( %ENV, TIME => scalar localtime, PJAX => ($req->header('X-PJAX') ? 1 : 0) ); say "----- X-PJAX is " . ($data{PJAX} ? 'TRUE' : 'FALSE'); my $type = $data{PJAX} ? 'pjax' : 'default'; my $res = $req->new_response(200); $res->content_type('text/html; charset=utf-8'); given ($req->path_info) { when ('/') { $data{title} = "root"; $res->body( $tx->render("root-$type.tx", {data => \%data}) ); } when ('/home') { $data{title} = "/home"; $res->body( $tx->render("home-$type.tx", {data => \%data}) ); } when ('/help') { $data{title} = "/help"; $res->body( $tx->render("help-$type.tx", {data => \%data}) ); } when ('/favicon.ico') { $res->redirect("http://www.google.com/favicon.ico", 301); } default { $res->status(404); $res->body('Not Found'); } } return $res->finalize; }; $app; __DATA__ @@ home-pjax.tx <: if $data.PJAX { :> <title><: $data.title :></title> <: } :> <p> Hello, <: $data.USER :> </p> @@ home-default.tx : cascade base; : override main -> { include "home-pjax.tx" } : override title -> { "/home" } @@ help-pjax.tx <: if $data.PJAX { :> <title><: $data.title :></title> <: } :> <pre> <: $data | dump :> </pre> @@ help-default.tx : cascade base; : override main -> { include "help-pjax.tx" } : override title -> { "/help" } @@ root-pjax.tx <: if $data.PJAX { :> <title><: $data.title :></title> <: } :> <p> pjax!! pjax!! pjax!!</p> @@ root-default.tx : cascade base; : override main -> { include "root-pjax.tx" } : override title -> { "root" } @@ base.tx <!DOCTYPE html> <html> <head> <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.5.2/jquery.min.js"></script> <script src="http://pjax.heroku.com/jquery.pjax.js"></script> <meta charset='utf-8'> <title> <: block title -> { :> hello pjax <: } :> </title> <script type="text/javascript"> $(function () { $('a.js-pjax').pjax('#main'); }) </script> </head> <body> : include "nav.tx" <div> : $data.TIME </div> <div id="main"> : block main -> { } </div> </body> </html> @@ nav.tx <ul id="nav"> <li><a href="/" class="js-pjax">Index</a></li> <li><a href="/home" class="js-pjax">Home</a></li> <li><a href="/help" class="js-pjax">Help</a></li> </ul>
結論
簡単にpushStateによる履歴操作とhash fragment (裏側のURLは汚い)に勝る綺麗なURLを実現できるので流行るといいな。