JSF 2.0AjaxサポートとjQueryの連携

前回はjQueryはおまけ程度の扱いだったが、今回はjQuery全開でいくぞい。

JSF 2.0のAjaxは非常に強力ではあるが、を利用した方法だとjavascriptのライブラリとの連携が出来ない。たくさんのライブラリがあるのにこれはもったいない。業務系ではなくてもかまわないものの、ダイアログ系は業務系でもぜひともほしいところ。画面遷移をするのと操作内容そのものは変わらないものの、画面遷移があると思考が切断されてしまうようで、どうにも受けは悪いものだ。

ぜひそれらと連携をしたい。

もちろん、JSF 2.0にはそれは用意されている。一応先にいっておくとjQuery前提ではないので、他のjavascriptのライブラリでもよい。


以前作成した足し算と引き算のプログラム。Ajax未対応なものと対応したものではコードは同じであったが、タグを入れるためにテンプレートが少し違っていた。Wicketは逆にテンプレートは同じまま、コードが違う。ではもっといい方法はないのか。

ある。Ajax未対応なテンプレートとコードをそのままでAjax対応にすることが可能なのだ!


たとえば以下のテンプレートは以前足し算と引き算をやるものだった。

<body>
    <form jsfc="h:form" id="f">
        <input jsfc="h:inputText" id="p1" value="#{calc.param1}"/>

        <input jsfc="h:commandButton" id="add" value="+" action="#{calc.add}" />
        <input jsfc="h:commandButton" id="sub" value="-" action="#{calc.sub}" />

        <input jsfc="h:inputText" id="p2" value="#{calc.param2}"/>
           =
        <input jsfc="h:outputText" id="r" value="#{calc.result}"/>

    </form>
    
</body>

コードは以下の通り。

import java.io.Serializable;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.ViewScoped;

@ManagedBean
@ViewScoped
public class Calc implements Serializable{
    int param1;
    int param2;
    int result;

    public int getParam1() {
        return param1;
    }

    public void setParam1(int param1) {
        this.param1 = param1;
    }

    public int getParam2() {
        return param2;
    }

    public void setParam2(int param2) {
        this.param2 = param2;
    }

    public int getResult() {
        return result;
    }

    public String add(){
        result = param1 + param2;
        return null;
    }
    public String sub(){
        result = param1 - param2;
        return null;
    }
}

この時点ではAjax未対応だ。

これをAjax対応にする。以下の1行をどこでもいいのでいれる。これはscriptの生成をするコード。targetの場所に自動で生成されるためにコンポーネントと一緒にまとめて管理できる。リソースの扱いはかなり楽になった。を使っている場合自動で生成されるのであくまでもscriptを手動でやる場合に必要。

<h:outputScript name="jsf.js" library="javax.faces" target="head"/>

続いてscript。ここではjQueryを利用してイベントをバインドした。もちろんhtmlにかいてもいいし、別ファイルにしてもよい。

$(document).ready(function(){

    $("#f\\:add").click(function(event){
        jsf.ajax.request(event.target , event,
            {execute:"f:p1 f:p2" , render:"f:r"}
        );
        event.preventDefault();
    });
    $("#f\\:sub").click(function(event){
        jsf.ajax.request(event.target , event,
            {execute:"f:p1 f:p2" , render:"f:r"}
        );
        event.preventDefault();
    });

});

jsf.ajax.request」がポイント。これを自動生成してくれるのが「」。このscriptの関数はちゃんとJSFの仕様として定義されているので安心して使ってよい。これで3つ目の引数の中のオプションの中のexecuteのパラメータを送信して、renderが再レンダリングされる。1つ目の引数があるのでどのボタンが押されたかはexecuteに渡す必要はないようだ。

「event.preventDefault();」は本来のイベントを実行しないという意味がある。submitボタンなのでこれがないとAjaxによるリクエストと画面遷移する通中のSubmitと2つ動いてしまう。

あと、jQueryで注意をしないといけないのがidにコロン等がある場合エスケープしないといけないこと。


項目が増えるとexecuteとrenderを指定するのが大変になると思うかもしれない。大概、フォームまとめて送信することが多いと思われるため以下のようにして丸ごと送ってもよい。

        jsf.ajax.request(event.target , event,
            {execute:"f" , render:"f"}
        );

「f」というのが対象のフォームIDを示している。「f:@form」や「@form」でも動いているように見える。


ただし、ここで問題が起きる。

これはフォームを丸ごと再レンダリングしている。ということはボタンも再レンダリングされる。この時点でフォーム内に設定したイベントが吹っ飛ぶ。

つまり、1回はAjaxで実行されるが、次は画面遷移を伴うsubmitになる。Ajaxが2回に1回だけ成功しているので気持ち悪いことに。

この現象、jQueryとかガンガン使ったことがある人なら覚えがあるだろう。


ではどうするか。まず単純に考えられるのが毎回付け直すこと。executeなどのオプションに「onevent:function(){〜}」を指定するとよい。そうすると個別のイベントで前回やったイベントが返ってくる。つまり、1回のアクセスで3回、Ajax発行前、発行後、再レンダリング後と発行される。statusを見て分岐するとよいのも同じ。

    jsf.ajax.request(event.target , event,
            {
                execute:"f" ,
                render:"f" ,
                onevent:function(event){
                    alert(event.status);
                    //ここでイベントをつける。しかもフォーム全部の。
                }
            }
    );


でもそれはあまりにもめんどくさい。そこでjQuery1.3の新機能のliveを使うとよい。これは後から作られてもイベントが発行可能だ。

    $("#f\\:add").live("click",function(event){
        
        jsf.ajax.request(event.target , event,
            {
                execute:"f" ,
                render:"f"
            }
        );
        event.preventDefault();
    });


以上のようにAjax未対応なアプリをAjax対応させることをsciptだけで行うことが可能になっている。scriptから実行できるのでライブラリのモーダルな「はい/いいえ」ダイアログなどを用意して、選択されたらこっちを実行、再レンダリングする、などといったことが簡単に可能になる。

とはいえ、何がレンダリングされるか、イベントはいつ紐づいたか、消えてないかなどをしっかり把握する必要があり、基本はを利用したほうがはるかに便利だろう。jQuery UIのダイアログ等と連携したりする部分だけ使うようにするとよいだろう。