SE成長痛日記

アラサーSEが技術力を高めるために日々感じる痛みを綴るBlogです。

Ajaxを使う

今回はAjaxの機能をみてみます。

一番簡単な処理として、 あるリンクを押下すると、サーバ処理で文字列を生成し、 その結果を指定の場所に出力する。

というものを作ってみます。

JSFAjax処理では、まずイベントの起点となるタグに対して、 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);
    }

実行するとこんな感じになります。

f:id:hirohisoEx:20190401205154p:plain
Ajax

では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処理になります。

f:id:hirohisoEx:20190401205746p:plain
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}"/>

f:id:hirohisoEx:20190323135606p:plain 特に何事もなく、実行できました。

次に、オブジェクトのメンバにアクセスしてみます。

    <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>

こんな感じで、一つだけ書き換えてみました。 f:id:hirohisoEx:20190323140046p:plain 普通にうまくいきました。 どんな仕組みかわかりませんが、複合コンポーネントで受け取ったオブジェクトの型を うまく識別しています。

なので、全体的に次の様に書き直しました。 これでオブジェクトそのものを複合コンポーネントに渡せるため、呼び出し側は非常にすっきりします。 また、オブジェクトのメンバが増えても複合コンポーネントの定義側だけを書き直せばよく。 呼び出し側には手を加える必要がないのもメリットとして大きいです。

        <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>

これで動かします。

・・・・・

本当にあってるのでしょうか?

f:id:hirohisoEx:20190318001148p:plain
エラー

エラー、、、出ていますが

ものはためしに動かしてみましょう。

f:id:hirohisoEx:20190318001532p:plain
エラーorg

知ってた。

resourceフォルダに読み込めてないみたいですね。 白猫本を見ると以下のように記載してありました。

コンポジットコンポーネント は、 画像 や CSS ファイル などと 同様 に resources フォルダ に 配置 し て 利用 し ます。

あ、入れてるresouce違いました。

f:id:hirohisoEx:20190318002220p:plain
フォルダ修正前

次のように修正します。

f:id:hirohisoEx:20190318002350p:plain
フォルダ修正後

これで一回、動かしてみます。

エラーは解消されました。 f:id:hirohisoEx:20190318002830p:plain

なんも出なくなりましたが。 f:id:hirohisoEx:20190318002740p:plain

div要素は追加されて行ってるので、 うまいことデータが渡せっていないっぽいです。 f:id:hirohisoEx:20190318002810p:plain 今日はここまでで、次回、このエラー解消したいと思います。

追記

普通に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;
    }
}

f:id:hirohisoEx:20190309225140p:plain
バリデート
これを見る限り、コンバートされた後のオブジェクトがわたってきていそうです。

では、できるだけデータとそれに関する処理はまとめていきたいため、 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>

f:id:hirohisoEx:20190309230139p:plain
バリデートエラー

このような感じになります。

フェーズを確認すると、バリデーションエラーの場合は、 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のアドレス部分をクラスを使用するように置き換えます。

ここで実行してみます。

表面上は、なんら変わりはありませんが、

f:id:hirohisoEx:20190308003045p:plain
ブラウザ上

コンソールを見ると、カスタムコンバータが動作していることがわかります。

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>

こんな感じですかね?

とりあえず実行します。

f:id:hirohisoEx:20190303214924p:plain
エラー

そーですか・・・ あ、もともとも作成していたString型のプロパティを要素に指定してしまってました。

f:id:hirohisoEx:20190303220423p:plain
エラー2
そしてまたエラーorz

あれ、これui:repeatのvarもnameアノテーションで指定させて一致させないとだめなやつ? とりあえず修正したみたらうごきました 。。。。。。

f:id:hirohisoEx:20190303220857p:plain
動作した

でもどこにも、varはnameアノテーションのやつ指定しないとダメなんて、記載ないんだが・・・・

要調査です。

3/4追記 再度確認したら発生しなくなりました。謎・・・・