Play Pjax

Play! framework Advent Calendar 2011 jp #play_ja : ATND の9日目です。

Play framework で、Pjaxを実現する方法について書きます。

Pjax については、pjax こそが pushState + Ajax の本命 - punitan (a.k.a. punytan) のメモが詳しいのでそちらを参照してください。簡単にいうと、Ajaxを使って画面の一部を動的に更新する仕掛けなんだけど、URLもページの中身に合わせてちゃんとしていますよ、というものですね。Pjax を使うととてもスマートな開発しているように感じられて、いい気持ちになれますよ。

サンプルソースは、ironpeace/PlayPJAX · GitHubにアップ済みです。このサンプルを元に説明していきます。

まずは画面実装

#{extends 'main.html' /}
#{set title:'Play PJAX' /}

#{get 'moreScripts' /}
<script src="@{'/public/javascripts/jquery.pjax.js'}" type="text/javascript" charset="${_response_encoding}"></script>
 
<script type="text/javascript">
$(function(){
	$("a.js-pjax").pjax("#main");
});
</script>

<a href="/?data1=foo" class="js-pjax">/?data1=foo</a>

<div id="main">
#{if results}
	*{ #{result /}とやってしまうと駄目。 }*
	#{include 'tags/result.html' /}
#{/if}
</div>

PlayPJAX/app/views/Application/index.html at master · ironpeace/PlayPJAX · GitHub

jquery.pjax.jsを使っています。

defunkt/jquery-pjax · GitHubからダウンロードしたものです。

$("a.js-pjax").pjax("#main");

/?data1=fooのリンクをクリックすると、 < div id="main" > 内にリターンが入ります。

#{include 'tags/result.html' /}

result.htmlがhtml部品として < div id="main" > 内に入ります。result.html の中身は後述します。

#{result /}とやってしまうと駄目。とコメントを入れていますが、実はここが一番ハマったところです。include と書こうが書くまいが効果は一緒のはずなのですが、include で書かないとうまく動いてくれません。

#{if results}

Pjax なので、画面全体がURLから呼ばれた時でも同じ描画となる必要があります。/?data1=fooのリンク先が Ajax でなく、URL で呼ばれた場合は、この IF 文が true になるので、Ajax 通信無しに中身がロードされるというわけです。

resultタグ

<h3>result</h3>
<ul>
	#{list items:results, as:'result' }
	<li>${result}</li>
	#{/list}
</ul>

PlayPJAX/app/views/tags/result.html at master · ironpeace/PlayPJAX · GitHub

こいつが index.html の < div id="main" > 内に入るわけですね。

サーバサイド実装

package controllers;

import play.*;
import play.mvc.*;
import play.mvc.Http.Request;
import java.util.*;
import models.*;

public class Application extends Controller {

    public static void index() {
        String data1 = params.get("data1");
        
        if(data1 == null){
        	render();
        }else{
	        List<String> results = new ArrayList<String>();
	        for(int i=0; i<10; i++){
	        	results.add("data" + i);
	        }
        
        	if(isPjax()){
        		render("/tags/result.html", results);
        	}else{
	        	results.add("directCalled");
        		render(results);
        	}
        }
    }
    
    private static boolean isPjax(){
    	if(params._contains("_pjax")){
    		return true;
	}else if(request.headers.containsKey("X-PJAX")){
    		return true;
    	}else{
    		return false;
    	}
    }

}

PlayPJAX/app/controllers/Application.java at master · ironpeace/PlayPJAX · GitHub

isPjax

このメソッドでPjax通信かどうかを判断しています。GETであればクエリストリング、POSTであればヘッダーを参照して判断します。

メイン実装
        	if(isPjax()){
        		render("/tags/result.html", results);
        	}else{
        		render(results);
        	}

Pjax 通信の時は、result タグだけ応答して、そうでない時は index.html 全体を応答します。これだけです。

まとめ

PlayFramework では HTML の一部を部品かするテンプレート機能があるので、 Pjax の実装もとても簡単に効率的に行うことができます。

以上です。

アドベントカレンダーなかなか楽しいですね(^^
明日は、@somali_cat さんです!