SE成長痛日記

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

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追記 再度確認したら発生しなくなりました。謎・・・・

JSFにマネージドビーンを追加する その2

前回、作成してうまく動かなった部分ですが、 単純にアノテーションのimportがまちがっており、修正したら正しく動作しました。

では今回は、「投稿」ボタンを押下したら、 レスが上に表示されるような仕組みを作成します。

細かい体裁やクラス構造はいったん無視します。 リクエスト送って、再度表示ができるとこまでです。

XHTMLはいかのように修正

    <h:body>

        <h:outputText value="#{responseBean.responseList}"></h:outputText>
        <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}"></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>
    </h:body>

管理beanはボタン押下時のメソッドを作成します。

    public void tweetResponse(){
        this.responseList += this.name + this.EMail + this.Res;
        return;
    }

とりあえずこんな感じになりました。 体裁やクラス構造は気にしない!

f:id:hirohisoEx:20190303182805p:plain
結果

JSFにマネージドビーンを追加する

前回作成したJSFにマネージドビーン(管理Bean)を紐付けてみます。

まず初期表示時に、管理Beanの指定する文字列を各テキストエリアのプレースホルダとして表示するように してみたいと思います。

投稿管理ということで、ResponseBeanとし、 定数文字列を追加し、JSFに関連付けてみます。。

ところでJSFでplaceholdertってどうやって指定するのでしょうか・・・・・

なんか以下のサイトを見るに、パススルーアトリビュートってやつを使用するみたいです。 https://ittoybox.com/archives/175

とりあえずこれで一回動かしてみます。

うん。なんかエラー出た。

Bean declaring a passivating scope must be passivation capable

このページを見ると、シリアライズ インターフェースを実装すればよいっぽい https://sites.google.com/site/javar4java/contexts-and-dependency-injection-cdi/scope

エラーにはならなくなったが、値が出ないorz 初期表示から管理Beanで値を設定することってできないんでしょうかね?

ちょっと調べが必要です。

定数にしたのがわるいのか? プロパティにしてPostConstructで設定。

でない。

getterの戻りをべた書きで設定。でない。

JSFにべた書き。これは出ました。

管理Beanの値をとれてないってことがわかったので、後日もうちょっと調べていきたいと思いまあす。