JSF 2.0はAjaxがちょー簡単に書けるというのは以下のエントリで書いた。
http://d.hatena.ne.jp/shin/20090726/p1
足し算と引き算は以下のようにやればよかった。今回はカスタムタグで表記してみたが。
<h:form> <h:inputText id="p1" value="#{calc.param1}"/> <h:commandButton value="+" action="#{calc.add}" > <f:ajax execute="p1 p2" render="r"/> </h:commandButton> <h:commandButton value="-" action="#{calc.sub}" > <f:ajax execute="p1 p2" render="r"/> </h:commandButton> <h:inputText id="p2" value="#{calc.param2}"/> = <h:outputText id="r" value="#{calc.result}"/> </h:form>
これでAjax対応の足し算と引き算のアプリが完成だ。しかし、どちらのボタンも同じレンダリングと渡すパラメータなのに2箇所に記述するのはたるい。
そこでこういう書き方も出来る。
<h:form> <h:inputText id="p1" value="#{calc.param1}"/> <f:ajax execute="p1 p2" render="r"/> <h:commandButton value="+" action="#{calc.add}" /> <h:commandButton value="-" action="#{calc.sub}" /> </f:ajax> <h:inputText id="p2" value="#{calc.param2}"/> = <h:outputText id="r" value="#{calc.result}"/> </h:form>
f:ajaxで囲んでやることが出来るのだ。
以下のようにさらに範囲を広げると
<h:form> <f:ajax execute="p1 p2" render="r"/> <h:inputText id="p1" value="#{calc.param1}"/> <h:commandButton value="+" action="#{calc.add}" /> <h:commandButton value="-" action="#{calc.sub}" /> <h:inputText id="p2" value="#{calc.param2}"/> = <h:outputText id="r" value="#{calc.result}"/> </f:ajax> </h:form>
InputTextでonChangeイベント(つまり文字を打ち込むたび)でもガンガン送信される。
ただし、ボタンをクリックしていないため計算はされない。あくまでもf:ajaxに記述したものがまとめて処理される。
つまり、パラメータ1を入力するとパラメータ1とパラメータ2が送信され、結果が更新される。addやsubは呼ばれないので計算はしていないものの結果も再表示しようとしている。これでは効率が悪すぎる。
ではどうするか。イベントをフィールドごとに種類を指定して絞るという方法がある。
<h:form> <h:inputText id="p1" value="#{calc.param1}"> <f:ajax execute="@this"/> </h:inputText> <f:ajax render="r"> <h:commandButton value="+" action="#{calc.add}" /> <h:commandButton value="-" action="#{calc.sub}" /> </f:ajax> <h:inputText id="p2" value="#{calc.param2}"> <f:ajax execute="@this"/> </h:inputText> = <h:outputText id="r" value="#{calc.result}"/> </h:form>
いわゆるパーシャルサブミットだ。
つまり、パラメータ1を入力するとその結果がサーバーに送信されて、サーバーのparam1のプロパティが更新され、ブラウザの状態と同期される。
この時点で足し算ボタンを押すとparam1とparam2の計算がされて、結果だけがレンダリングされる。
つまりparam2の送信はしていない。
このまま計算ボタンを連打した場合、サーバー内で計算はされて結果がレンダリングされる(表示上は変わらないが)だけだ。余計なデータは送信していない。
これが非常に軽いAjax。ボタンクリックのタイミングでまとめて送るほうが効率がよい場合もあるが、必要な情報を即時にばらばらにおくったほうが体感上とてもよい。なんせ更新されていないテキストは送信されないのだから。
「@this」というのが登場しているが、これは自分自身を表す。非常に使用頻度は高いはずだ。
ここでぴんと来た人はICEfacesを思い出したかもしれない。あれもパーシャルサブミットに特化したよりオートマチックなAjaxなフレームワークだが、あれが非常に体感速度が速いのはそういう原因があるからだ。(後はCometでストリーミングつかってるのも体感上のポイントである)
ちなみにICEfacesもJSF2.0に対応中のようだ。違いは「f:ajax」を一切記述せずに全自動で同じことをやってくれるという優れもの。JSF 1.x時代はまさに最強のAjaxなフレームワークだったが、2.0の登場で多少の手間はあれ、似たようなことが出来るのでインパクトはだいぶ落ちたかな。
たくさんのコンポーネントが並びやすい業務系などではこういうのは圧倒的な効率が期待される。
ただし、イベントはonChangeで送られるため入力するたびに送られるのはサーバーが遠い場合など気持ち悪いかもしれない。
そういうときには以下のようにすると
<h:inputText id="p1" value="#{calc.param1}"> <f:ajax event="blur" execute="@this"/> </h:inputText>
フォーカスが外れた瞬間に中身が送信されることになる。ボタンを押すときには必ずフォーカスが外れるためにボタンクリック時には大丈夫だ。
たとえば意味はないけど、このように書くと
<h:inputText id="p1" value="#{calc.param1}"> <f:ajax event="click" execute="@this"/> </h:inputText>
テキストフィールドをクリックしないとサーバーにデータが送信されないということ。まぁそんなUIまずないけどね。
ui:repeatで確認したが、表形式などのように繰り返し大量の入力項目があるような場合(業務系では普通にあるよね。マスタメンテナンスとか)でも入力されたところだけしか送信しないので非常に軽い。