pjax1
TRANSCRIPT
PjaxRubyist 九州山崎重一郎
Rubyist 九州 2011 年 11 月例会2011 年 11 月 26 日
Web アプリっていちいちページ遷移して使いにくい反応が悪いそもそも、サーバ側でユーザの操作に対するアクションを処
理したり、ビューを生成したりする必要があるのか?
プラウザ
サーバ
アクション
ビュー生成データとビュー
HTML
Web アプリってブラウザ側でやれることはブラウザ側でやれば?ひとつのアプリの中でページ遷移って必要?ブラウザ内で、 DOM 要素だけ処理すればいいんじゃない?必要なデータだけ JSON とかでサーバからもらえばいいじゃん。
プラウザ サーバ
アクション
ビュー生成
データのみJSON 等
じゃあ Ajax なの?
リソースにちゃんとした URL がついてないとブックマークできない、検索エンジンにひっかからないというか、そもそもそのリソースを Web で共有できないじゃないか
REST の基本は重要!リソース状態の流通 (REpresentational State Transfer)
→ サーバをステートレスに保ちながら、リソースの状態は表現できる例:リソースへのアクセス権限状態( OAuth のアクセストークンなど)
ほら! やっぱり Ajax じゃだめじゃん
ブラウザ側のリソースとサーバ側のリソース
Ajax 的なシステムでも同一の URL が指すのは同一のリソース直接サーバに URL でアクセスした場合 → サーバから GET
Ajax 的に同じ URL でアクセスした場合 → ブラウザ側で生成
プラウザ サーバ
アクション
ビュー生成
リソース
リソース
リソース
リソース
リソース
リソース
ブラウザ側にリソース表現の実体が構成された場合
Ajax 的に DOM 要素の構成でできたリソースその場合も仮想的にサーバ側にもリソースが存在しているよう
にしたい → バックボタン、ブックマーク、検索エンジン、 Web 共有
プラウザ サーバ
アクション
ビュー生成
リソース
リソース
リソース
リソース
リソース
リソース
URL で直接的にリソースにアクセスした場合
もともとそのリソースはサーバに存在しているよ、という感じページの姿はブラウザ側で構成したものと全く同一にしたい
プラウザ サーバ
リソース
リソース
リソース
Web ブラウザの基本構成(かなりいいかげんですが ... )<a href="">...</a> をクリックしたとき
Web ブラウザ
エンティティマネージャ
DOMレンダラ
クリック
表示
http GET など
history スタック
サーバLF ( ヘッダ ) CR LF ( ボディ ) CR
<h1>aaa</h1><p>...
Pjax 魔法のしくみ 1jQuery で、ブラウザの機能を抑制/入替HTML5 の pushState で history スタックに履歴をプッシュ
Web ブラウザ
エンティティマネージャ
DOMレンダラ
クリック
表示
http GET などをしたつもり
history スタック
XjQuery
preventDefault()HTML5
pushState
ブラウザの機能を抑制/入替えpreventDefault()
<!doctype html><html><head><meta charset="UTF-8"><title>preventDefalut() のテスト </title><script type="text/javascript" src='jquery-1.7.js'></script></head><body><h1>preventDefault() のテスト </h1><p><a id="enable" href="http://www.cacanet.org">このリンク</a>は有効にされています。<a id="disable" href="http://www.cacanet.org">このリンク</a>は無効にされています。</p><p id="message"></p><script type="text/javascript"> $("#disable").click(function (e) { e.preventDefault(); $("#message").html(" ほら、ページ遷移しないでしょ ?"); });</script></body></html>
HTML5 の history.pushState
history.pushState(historyオブジェクト , タイトル , URL);ブラウザの閲覧履歴スタックに強引にタイトルや URL を強引にプッシュ→ とりあえずブックマークできる。
<html><head><meta charset="UTF-8"><title> 最初のページ </title><script> function ps() {history.pushState(null,null,"http://rubyist.org/");} </script> </head> <body><h1>pushSate のテスト </h1><form> <input type="button" value="URL に注目 " onclick="ps()" /></form></body></html>
Pjax = jQuery の jquery.pjax.jsこれが Pjax のオリジナル
pushState + Ajax = Pjaxhttps://github.com/defunkt/jquery-pjax
デモページ: http://pjax.heroku.com/
直接 URL を入れても同じページが表示される
jquery.pjax.jsリクエストヘッダに HTTP_X_PJAX を含めるサーバ側は、 HTTP_X_PJAX のときは Ajax 的にデータだけ送る
そうでなければ、レイアウト付きの HTML を返す
Web ブラウザ
エンティティマネージャ
DOMレンダラ
クリック
表示
history スタック
XjQuery
jquery.pjax.js
HTML5pushState
サーバ
HTTP_X_PJAX
データだけ
jquery.pjax.js の使用例sinatra の場合
# -*- coding: utf-8 -*- require 'sinatra'
get '/' do erb "<h1> トップページです </h1>", :layout => !env['HTTP_X_PJAX']end
get '/hello' do erb "<h1> こんにちは! <%=Time.now%></h1>", :layout => !env['HTTP_X_PJAX']end
__END__@@layout<!doctype html><html><head><title><%= @title %></title><script type="text/javascript" src="http://code.jquery.com/jquery-1.7.min.js"></script><script type="text/javascript" src="http://pjax.heroku.com/jquery.pjax.js"></script><script type="text/javascript">$("a.pjax").pjax("#main");</script></head><body><ul> <li><a href="/" class="pjax"> ホーム </a></li> <li><a href="/hello" class="pjax"> あいさつ </a></li></ul><div id="main"><%= yield %></div></body></html>
サーバ側の魔法の種明かしリクエストヘッダの HTTP_X_PJAX の有無チェックとレイアウト
の抑制判定
Ajax 的に DOM 要素を更新する部分の指定( div で yield を囲む)
a タグ( pjax クラスの)に対する pjax の適用
get '/' do erb "<h1> トップページ </h1>", :layout => !env['HTTP_X_PJAX']end
<div id="main"><%= yield %></div>
<script type="text/javascript">$("a.pjax").pjax("#main");</script>
<li><a href="/" class="pjax"> ホーム </a></li> <li><a href="/hello" class="pjax"> あいさつ </a></li>
Pjax は Rails3.2 から標準になる
利点は明らかだから当然今後の Web アプリケーションは基本的にページ遷移しなくな
るRails 流の MVC も見直しが必要
コントロールやビュー生成の大部分を coffeeScript でやるの?
Pjax を基本にした、すっきり新しい Web アプリフレームワークを新規設計した方がはやい
でも、なぜこんなにうまくいくのか?
history に pushState を追加しただけなのに?なにか基本的な原理がありそう
Rubyist の目線からの妄想history へのプッシュは、クロージャによるメモ化に似てい
る理論的なかっこいいアプローチもあるかもクロージャによるメモ化
関数の入力と出力の視点からの意味は変わらない既存の計算結果のキャッシュを使うか、関数の処理を実際に計算するか
Pjax同じ URL にアクセスしたときのページの姿は変わらないブラウザ側でページ要素のみを構成するか、サーバからページ全体を持ってくるか
Haskell の人は、「それは xx モナドだよ」とか言うかも ...