システム開発で思うところ

Javaで主にシステム開発をしながら思うところをツラツラを綴る。主に自分向けのメモ。EE関連の情報が少なく自分自身がそういう情報があったら良いなぁということで他の人の参考になれば幸い

JSFでエラーフィールドの背景色を変える(3)

vermeer.hatenablog.jp

の続き。

前回の記事で 課題とした

https://github.com/system-sekkei/isolating-the-domain
のように 背景色というよりも、指定領域のスタイルを変更する という要件には応えられていません。

について、今回は取り組みたいと思います。

やりたいこと

  • エラー対象に関連付けた任意の場所のスタイルを変更する
  • (ついでに)jsf:styleClassを使わないHTML Friendlyなxhtmlの記述ができる

やりかた

  • xhtmlで 関連付け対象のIDを引数としてスタイルを返却する

イメージは、

JSFでエラーのある項目の背景色を変える - じゃばらの手記

に近しいですが、異なるのは component.clientIdではなく、通常のIDを指定するところです。 ここまでに、通常のIDとJSFのClinetIdを関連付ける仕組みを実装済みなので、それを使います。

実装

xhtmlから使用するスタイル制御を行うクラス

BeanValidationExceptionのInterceptorで

  • 画面上のコンポーネントを取得して通常のIDとJSFのClinetIdを関連付けたクラス
  • エラーとなったClientIdのリスト

を取得しているので、それらをマージして目的の判定を行う情報を編集します。

@Action
@Interceptor
@Priority(Interceptor.Priority.APPLICATION)
@Dependent
public class BeanValidationExceptionInterceptor {

    private final CurrentViewContext context;
    private final MessageConverter messageConverter;
    private final MessageWriter messageWriter;
    private final ClientComplementManager clientComplementManager;
    private final ErrorStyle errorStyle;

    @Inject
    public BeanValidationExceptionInterceptor(CurrentViewContext context,
                                              MessageConverter messageConverter, MessageWriter messageWriter,
                                              ClientComplementManager clientComplementManager, ErrorStyle errorStyle) {
        this.context = context;
        this.messageConverter = messageConverter;
        this.messageWriter = messageWriter;
        this.clientComplementManager = clientComplementManager;
        this.errorStyle = errorStyle;
    }

    @AroundInvoke
    public Object invoke(InvocationContext ic) throws Exception {

        String currentViewId = context.currentViewId();

        try {
            return ic.proceed();
        } catch (BeanValidationException ex) {

            ClientIdsWithComponents clientIdsWithInputComponents = new InputComponentScanner().scan();

            ClientIdMessages clientidMessages
                             = messageConverter.toClientIdMessages(ex.getValidatedResults(),
                                                                   ic.getTarget().getClass().getSuperclass(),
                                                                   clientIdsWithInputComponents);

            ClientIdsWithComponents clientIdsWithHtmlMessages = new HtmlMessageScanner().scan();
            messageWriter.appendErrorMessageToComponent(clientidMessages.toClientIdMessagesForWriting(clientIdsWithHtmlMessages));

            FacesContext.getCurrentInstance().validationFailed();
            clientComplementManager.setClientidMessages(clientidMessages);

            //ここで、エラースタイルを管理するクラスへ情報を渡す
            this.errorStyle.set(clientIdsWithInputComponents, clientidMessages);
            return currentViewId;
        }

    }
}


実際に状態を保持するクラス

@Named
@RequestScoped
public class ErrorStyle {

    private ClientIdsWithComponents clientIdsWithComponents;

    private String errorStyle;

    @PostConstruct
    private void init() {
        this.errorStyle = "error";
        this.clientIdsWithComponents = new ClientIdsWithComponents();
    }

    public void set(ClientIdsWithComponents clientIdsWithInputComponents, ClientIdMessages clientidMessages) {

        Set<String> clientIds = clientidMessages.getList().stream()
                .map(ClientIdMessage::getClientId)
                .collect(Collectors.toSet());

        this.clientIdsWithComponents = clientIdsWithInputComponents.filter(clientIds);
    }

    /**
     * 指定したIDの項目が検証不正だった場合に適用する styleClass を返却します.
     * <P>
     * xhtmlでのパラメータ指定時には、シングルクウォートで値を指定してください.
     *
     * @param id 対象となるコンポーネントのID(JSFのクライアントIDではありません)
     * @return 当該項目IDにエラーがない場合は 空文字を返却します.
     */
    public String byId(String id) {
        return this.clientIdsWithComponents.getOrNull(id) == null ? "" : errorStyle;

    }

}

xhtml側での記述

エラーになった際に関連付けさせてCSSクラス文字列を返したいところに EL式を記述します。 ポイントは xhtmlのIdを そのまま使っているところです。

また、jsf:styleClassで記述していた箇所を classとすることで、HTML Friendlyな記述が出来ました。
これで デザイナーとの協業も実現できますね。

気をつけるのは、Idの指定は シングルクウォートで囲むというところです。*1

<div class="field #{errorStyle.byId('email')}">
    <label>利用者ID</label>
    <input class="short-input" type="text" placeholder="someone@example.com" jsf:id="email" jsf:value="#{userRegistrationPage.email}"/>
    <h:message for="email" styleClass="error-message ui left pointing red basic label" />
</div>

Code

vermeer_etc / jsf-ddd / source / — Bitbucket

さいごに

結局のところ、xhtmlで記述したIDと JSFが導出するIDとの関連付けを どうにかすれば色んなことが出来るんですよね。
私は自作をしましたが*2 JSFのオリジナルの部品として、そういうのを扱うAPIなりクラスがあれば もっと使いやすくなるんじゃないでしょうかねぇ。*3


*1:ダブルクウォートで起動時にエラーになったのは凡ミス。。

*2:まだ繰り返し領域についての扱いは未対応ですが

*3:JavaDocを全て見たわけではないので、あるかもしれませんが