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

Javaで主にシステム開発をしながら思うところをツラツラを綴る

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を全て見たわけではないので、あるかもしれませんが