Ajaxを使う
今回はAjaxの機能をみてみます。
一番簡単な処理として、 あるリンクを押下すると、サーバ処理で文字列を生成し、 その結果を指定の場所に出力する。
というものを作ってみます。
JSFのAjax処理では、まずイベントの起点となるタグに対して、 jsf:ajaxタグを指定するところから始めます。
準備として、非Ajaxの処理を作成しておきます。 更新ボタンを押下したら、サーバのOS名とメモリ使用量を出力する処理です。
<div> <h:outputText value="#{responseBean.serverInfo}"/>:: <h:commandLink action="#{responseBean.updateServerInfo()}" value="更新"></h:commandLink> </div>
public void updateServerInfo() { MemoryMXBean mbean = ManagementFactory.getMemoryMXBean(); MemoryUsage heapUsage = mbean.getHeapMemoryUsage(); long init = heapUsage.getInit(); long used = heapUsage.getUsed(); long committed = heapUsage.getCommitted(); long max = heapUsage.getMax(); this.serverInfo = System.getProperty("os.name").toLowerCase(); this.serverInfo += String.format("[init = %s, used = %s, committed = %s, max = %s.]", init, used, committed, max); }
実行するとこんな感じになります。
ではAjax化してみます。
<div> <h:outputText id="serverInfo" value="#{responseBean.serverInfo}"/>:: <h:commandLink action="#{responseBean.updateServerInfo()}" value="更新"> <f:ajax render="serverInfo"></f:ajax> </h:commandLink> </div>
以上です。 イベントの起点となるタグにajaxタグを指定し、 更新対象となるタグのidを指定してあげることでajax処理になります。
IEのデバッガでネットワークを調べると、通信の種別がXMLHTTMLResponceとなっており、さきほどと処理が異なっているのがわかります。
ajaxタグを指定した箇所は以下の様にhtml出力されています。
<a name="j_idt14:j_idt17" id="j_idt14:j_idt17" onclick="mojarra.ab(this,event,'action',0,'j_idt14:serverInfo');return false" href="#">更新</a>
mojarra.abというのが肝なようです。こんなscripを作った覚えはないのですが、 JSFページを読み込むときに、jsf.jsと呼ばれるjsファイルを読み込んでいるため、 このJavascriptと連動して、一連のAjax処理が実現されていると考えられます。
今日はここまで。
複合コンポーネントのインターフェイスに何が渡せるか
表題どおりです。
複合コンポーネントのインターフェイスに何が渡せるのか調査してみます。
前回は、3つのString型でしたが、 オブジェクトそのものが渡せると、夢がきっと広がります。
まず次のように定義して受け取ることができるか確認します。
<composite:interface> <composite:attribute name="name"/> <composite:attribute name="mail"/> <composite:attribute name="body"/> <composite:attribute name="responce"/> </composite:interface>
<comp:respons name="#{respon.name}" mail="#{respon.mail}" body="#{respon.body}" responce="#{respon}"/>
特に何事もなく、実行できました。
次に、オブジェクトのメンバにアクセスしてみます。
<composite:interface> <composite:attribute name="name"/> <composite:attribute name="mail"/> <composite:attribute name="body"/> <composite:attribute name="responce" /> </composite:interface> <composite:implementation> <div> <h:outputText value="#{cc.attrs.name}"/> <h:outputText value="#{cc.attrs.mail}"/> <h:outputText value="#{cc.attrs.responce.body}"/> </div> </composite:implementation>
こんな感じで、一つだけ書き換えてみました。 普通にうまくいきました。 どんな仕組みかわかりませんが、複合コンポーネントで受け取ったオブジェクトの型を うまく識別しています。
なので、全体的に次の様に書き直しました。 これでオブジェクトそのものを複合コンポーネントに渡せるため、呼び出し側は非常にすっきりします。 また、オブジェクトのメンバが増えても複合コンポーネントの定義側だけを書き直せばよく。 呼び出し側には手を加える必要がないのもメリットとして大きいです。
<ui:repeat var="respon" value="#{responseBean.responseList}" > <comp:respons responce="#{respon}"/> </ui:repeat>
<composite:interface> <composite:attribute name="responce" /> </composite:interface> <composite:implementation> <div> <h:outputText value="#{cc.attrs.responce.name}"/> <h:outputText value="#{cc.attrs.responce.mail}"/> <h:outputText value="#{cc.attrs.responce.body}"/> </div> </composite:implementation>
本日はここまで。
JSFで複合コンポーネントを作成して利用する
JSFでは、複数のコンポートを束ねて切り出して1つのコンポーネントとして扱うことができます。
何度も同じように使用される部分を切り出して、再利用できたら便利ですよね?
前回のやつから投稿したレスの部分を複合コンポーネントにしてみます。
<ui:repeat var="respon" value="#{responseBean.responseList}" > <div> <h:outputText value="#{respon.name}"/> <h:outputText value="#{respon.mail}"/> <h:outputText value="#{respon.body}"/> </div> </ui:repeat>
この部分です。
いきなりrepeat含めて、まとめるのはなんか怖いので、(てかオブジェクトを渡せるか謎) ちきってdiv要素を切り出します。。。。
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:h="http://xmlns.jcp.org/jsf/html" xmlns:composite="http://java.sun.com/jsf/composite" > <composite:interface> <composite:attribute name="name"/> <composite:attribute name="mail"/> <composite:attribute name="body"/> </composite:interface> <composite:implementation> <div> <h:outputText value="#{name}"/> <h:outputText value="#{mail}"/> <h:outputText value="#{body}"/> </div> </composite:implementation> </html>
こんな感じのコンポーネントを定義します。 置き場所はリソースにcompフォルダを作って配置します。
利用する側はこうなります。
<ui:repeat var="respon" value="#{responseBean.responseList}" > <comp:respons name="#{respon.name}" mail="#{respon.mail}" body="#{respon.body}"/> </ui:repeat>
これで動かします。
・・・・・
本当にあってるのでしょうか?
エラー、、、出ていますが
ものはためしに動かしてみましょう。
知ってた。
resourceフォルダに読み込めてないみたいですね。 白猫本を見ると以下のように記載してありました。
コンポジットコンポーネント は、 画像 や CSS ファイル などと 同様 に resources フォルダ に 配置 し て 利用 し ます。
あ、入れてるresouce違いました。
次のように修正します。
これで一回、動かしてみます。
エラーは解消されました。
なんも出なくなりましたが。
div要素は追加されて行ってるので、 うまいことデータが渡せっていないっぽいです。 今日はここまでで、次回、このエラー解消したいと思います。
追記
普通にcc.attrつけるの忘れてました。
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:h="http://xmlns.jcp.org/jsf/html" xmlns:composite="http://xmlns.jcp.org/jsf/composite" > <composite:interface> <composite:attribute name="name"/> <composite:attribute name="mail"/> <composite:attribute name="body"/> </composite:interface> <composite:implementation> <div> <h:outputText value="#{cc.attrs.name}"/> <h:outputText value="#{cc.attrs.mail}"/> <h:outputText value="#{cc.attrs.body}"/> </div> </composite:implementation> </html>
JSFのカスタムバリデータを作成する
今回はカスタムバリデータを作成してみます。
入力されたメールアドレスに 特殊な文字があった場合に、エラーをだすように してみたいと思います。
バリデータを作成するにはjavax.faces.validator.Validatorを実装した クラスを作成し、alidate(FacesContext fc, UIComponent uic, Object o) にバリデートの条件を定義します。
ところObject o はStringとMailAddresのどちらが渡ってくるのでしょうか? (コンバート前なのか、後なのか) ちょっと以下のようなコードで検証してみます。
@FacesValidator(value = "mailAddressValidator") public class MailAddressValidator implements Validator { @Override public void validate(FacesContext fc, UIComponent uic, Object o) throws ValidatorException { if (o == null) { return; } if (o instanceof MailAddress) { System.out.println("validate:MailAddress"); } else if (o instanceof String) { System.out.println("validate:String"); } else { System.out.println("validate:その他"); } return; } }
これを見る限り、コンバートされた後のオブジェクトがわたってきていそうです。
では、できるだけデータとそれに関する処理はまとめていきたいため、 MailAddressクラスにチェック処理を作成する方針でやっていきましょう
とりあえずおためしなので、アドレスに&が含まれてたらエラーとしてみます。
@FacesValidator(value = "mailAddressValidator") public class MailAddressValidator implements Validator { @Override public void validate(FacesContext fc, UIComponent uic, Object o) throws ValidatorException { if (o == null) { return; } if (!(o instanceof MailAddress)) { return; } if(!((MailAddress)o).isValidMailaddres()){ throw new ValidatorException(new FacesMessage("無効なアドレスです")); } } }
public boolean isValidMailaddres() { if (this.address.contains("&")) { return false; } else { return true; } }
<h:form> <h:outputLabel>名前</h:outputLabel> <h:inputText value="#{responseBean.name}" p:placeholder="#{responseBean.defaultName}"></h:inputText> <h:outputLabel>e-mail</h:outputLabel> <h:inputText value="#{responseBean.EMail}" p:placeholder="#{responseBean.defaultEMail}" converter="mailAddressConverter" validator="mailAddressValidator"></h:inputText> <div> <h:inputTextarea cols="50" rows="10" value="#{responseBean.res}"></h:inputTextarea> </div> <h:commandButton action="#{responseBean.tweetResponse()}" value="投稿"></h:commandButton> </h:form>
このような感じになります。
フェーズを確認すると、バリデーションエラーの場合は、 PROCESS_VALIDATIONS → RENDER_RESPONSE となっていました。
JSFのカスタムコンバータを作成する
今回は、PROCESS_VALIDATIONSフェーズで行っている、 コンバート処理とバリデーション処理に関する実装をしてみます。
コンバート処理
JSFではUIコンポーネントを生成後、 HTMLリクエスト パラメータ内の文字列をJavaのオブジェクトに変換して、 Beanの各プロパティに割り当てています。
ここではMailアドレスをストリングではなく MailAdressクラスに変換するように修正してみます。
まずごく簡単なアドレスクラスを作成します。
public class MailAddress { private String address; public MailAddress(String address){ this.address = address; } public String getMailAdress(){ return this.address; } }
そして次のようにしてconverterクラスを作成します。
@FacesConverter(value = "mailAddressConverter") public class MailAddressConverter implements Converter{ @Override public Object getAsObject(FacesContext fc, UIComponent uic, String string) { System.out.println("mailAddressConverterによるコンバート処理:getAsObject"); return new MailAddress((string)); } @Override public String getAsString(FacesContext fc, UIComponent uic, Object o) { if(o != null && o instanceof MailAddress){ System.out.println("mailAddressConverterによるコンバート処理:getAsString"); return ((MailAddress)o).getMailAdress(); } return ""; } }
コンバータを使うときは次のようにxhtmlに記述します。
<h:inputText value="#{responseBean.EMail}" p:placeholder="#{responseBean.defaultEMail}" converter="mailAddressConverter"></h:inputText>
あとは、管理Beanのアドレス部分をクラスを使用するように置き換えます。
ここで実行してみます。
表面上は、なんら変わりはありませんが、
コンソールを見ると、カスタムコンバータが動作していることがわかります。
00:30:20,061 INFO [stdout] (default task-1) before:RESTORE_VIEW 1 00:30:20,068 INFO [stdout] (default task-1) after:RESTORE_VIEW 1 00:30:20,068 INFO [stdout] (default task-1) before:APPLY_REQUEST_VALUES 2 00:30:20,069 INFO [stdout] (default task-1) after:APPLY_REQUEST_VALUES 2 00:30:20,069 INFO [stdout] (default task-1) before:PROCESS_VALIDATIONS 3 00:30:20,070 INFO [stdout] (default task-1) mailAddressConverterによるコンバート処理:getAsObject 00:30:20,071 INFO [stdout] (default task-1) after:PROCESS_VALIDATIONS 3 00:30:20,071 INFO [stdout] (default task-1) before:UPDATE_MODEL_VALUES 4 00:30:20,072 INFO [stdout] (default task-1) after:UPDATE_MODEL_VALUES 4 00:30:20,072 INFO [stdout] (default task-1) before:INVOKE_APPLICATION 5 00:30:20,072 INFO [stdout] (default task-1) after:INVOKE_APPLICATION 5 00:30:20,072 INFO [stdout] (default task-1) before:RENDER_RESPONSE 6 00:30:20,077 INFO [stdout] (default task-1) mailAddressConverterによるコンバート処理:getAsString 00:30:20,079 INFO [stdout] (default task-1) after:RENDER_RESPONSE 6
PROCESS_VALIDATIONSフェーズで文字列→オブジェクトの変換処理 RENDER_RESPONSE フェーズでオブジェクト→文字列の変換処理 がおこなわれているのがわかります。
本日は独自のコンバート処理を実装してみました。
JSFのライフサイクルを理解する
前回で、初期表示 → サーバ処理 → 再表示という 単純な挙動の処理を実装したので、
ここで改めて、各ライフサイクルがどう動いているかがわかるように、 少しコードをいじりたいと思います。
管理Beanを次のように編集してBeanのライフサイクルを見てみます。。
/** * Creates a new instance of ResponseBean */ public ResponseBean() { System.out.println("Viewスコープのコンストラクタ"); this.defaultName = "名前"; this.defaultEMail = "E-Mail"; } @PostConstruct public void init(){ System.out.println("ViewスコープのPostコンストラクタ"); } @PreDestroy public void terminate(){ System.out.println("ViewスコープのPreDestroy"); }
またJSFのライフサイクルが観れるようにします。(Perfect Java EE参考)
package jp.hirohisoex.javawebapplication; import javax.faces.event.PhaseEvent; import javax.faces.event.PhaseId; import javax.faces.event.PhaseListener; /** * * @author hirohiso */ public class CheckPhaseListener implements PhaseListener{ @Override public void afterPhase(PhaseEvent pe) { System.out.println("after:"+pe.getPhaseId()); } @Override public void beforePhase(PhaseEvent pe) { System.out.println("before:"+pe.getPhaseId()); } @Override public PhaseId getPhaseId() { return PhaseId.ANY_PHASE; } }
<?xml version="1.0" encoding="UTF-8"?> <faces-config xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-facesconfig_2_2.xsd" version="2.2"> <lifecycle> <phase-listener>jp.hirohisoex.javawebapplication.CheckPhaseListener</phase-listener> </lifecycle> </faces-config>
これで表示、投稿ボタンを実行すると以下のようにコンソールに出力されます。
23:19:30,306 INFO [stdout] (default task-1) before:RESTORE_VIEW 1 23:19:30,377 INFO [stdout] (default task-1) after:RESTORE_VIEW 1 23:19:30,378 INFO [stdout] (default task-1) before:RENDER_RESPONSE 6 23:19:30,590 INFO [stdout] (default task-1) Viewスコープのコンストラクタ 23:19:30,591 INFO [stdout] (default task-1) ViewスコープのPostコンストラクタ 23:19:30,635 INFO [stdout] (default task-1) after:RENDER_RESPONSE 6 23:21:31,580 INFO [stdout] (default task-1) before:RESTORE_VIEW 1 23:21:31,619 INFO [stdout] (default task-1) after:RESTORE_VIEW 1 23:21:31,619 INFO [stdout] (default task-1) before:APPLY_REQUEST_VALUES 2 23:21:31,620 INFO [stdout] (default task-1) after:APPLY_REQUEST_VALUES 2 23:21:31,621 INFO [stdout] (default task-1) before:PROCESS_VALIDATIONS 3 23:21:31,665 INFO [stdout] (default task-1) after:PROCESS_VALIDATIONS 3 23:21:31,665 INFO [stdout] (default task-1) before:UPDATE_MODEL_VALUES 4 23:21:31,666 INFO [stdout] (default task-1) after:UPDATE_MODEL_VALUES 4 23:21:31,666 INFO [stdout] (default task-1) before:INVOKE_APPLICATION 5 23:21:31,672 INFO [stdout] (default task-1) after:INVOKE_APPLICATION 5 23:21:31,672 INFO [stdout] (default task-1) before:RENDER_RESPONSE 6 23:21:31,680 INFO [stdout] (default task-1) after:RENDER_RESPONSE 6
管理Bean(VIewスコープ)の生成はRENDER_RESPONSEで行っているみたいですね。
ということでここまでです。
JSFにマネージドビーンを追加する その3
前回作成したやつは、投稿ボタンを押下する度に、 文字列を付け足していくものでしたが、
見た目が悪いので、繰り返し構造を持つものに 修正したいと思います。
まず、情報の保持形式をまとも(投稿オブジェクトを作成、リストで保持するように変更)にします。
public class Response implements Serializable { private static final long serialVersionUID = 1L; @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; private String _name; private String _mail; private String _body;
こんな感じの投稿クラスを作成し、
private String responseList; private final List<Response> _responseList = new ArrayList<>(); public void tweetResponse(){ this.responseList += this.name + this.EMail + this.Res; this._responseList.add(new Response(this.name,this.EMail,this.Res)); return; }
投稿ボタンがクリックされるたびに生成、リストに追加されるようにします。
次に、リスト内を繰り返して、 データを取得、表示するように変更します。
リストから要素を取得し、繰り返しHTMLを生成したいです。 ちょっと調べるとui:repeatってのがあるのでそれを使用してみます。
<h:outputText value="#{responseBean.responseList}"></h:outputText> <ui:repeat var="res" value="#{responseBean.responseList}" > <h:outputText value="#{res.name}"/> <h:outputText value="#{res.mail}"/> <h:outputText value="#{res.body}"/> </ui:repeat>
こんな感じですかね?
とりあえず実行します。
そーですか・・・ あ、もともとも作成していたString型のプロパティを要素に指定してしまってました。 そしてまたエラーorz
あれ、これui:repeatのvarもnameアノテーションで指定させて一致させないとだめなやつ? とりあえず修正したみたらうごきました 。。。。。。
でもどこにも、varはnameアノテーションのやつ指定しないとダメなんて、記載ないんだが・・・・
要調査です。
3/4追記 再度確認したら発生しなくなりました。謎・・・・