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

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

JSFでツールチップ

vermeer.hatenablog.jp

の続きと言うか、ついでに。

やりたいこと

エラーメッセージを入力領域にツールチップを出力する

やりかた

xhtmlのinput要素のtitleにエラーメッセージを出力する

前回の記事と ほぼ同じ発想です。

実装

Titleに出力メッセージを扱うクラス

BeanValidationExceptionのInterceptorで

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

をマージして、Titleに出力するメッセージ用のクラスを編集します。

@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;
    private final ErrorTooltip errorTooltip;

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

    @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);

            //ツールチップ用のインスタンスに情報を渡します
            this.errorTooltip.set(clientIdsWithInputComponents, clientidMessages);
            return currentViewId;
        }

    }
}


情報を保持するクラス

@Named
@RequestScoped
public class ErrorTooltip {

    private ClientIdsWithComponents clientIdsWithComponents;
    private ClientIdMessages clientIdMessages;

    @PostConstruct
    private void init() {
        this.clientIdsWithComponents = new ClientIdsWithComponents();
        this.clientIdMessages = new ClientIdMessages();
    }

    public void set(ClientIdsWithComponents clientIdsWithInputComponents, ClientIdMessages clientIdMessages) {

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

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

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

    /**
     * 指定したClientId(フルパス)の項目が検証不正だった場合に適用する メッセージ を返却します.
     *
     * @param clientId 対象となるコンポーネントのID(JSFのクライアントIDではありません)
     * @return 当該項目IDにエラーがない場合は 空文字を返却します.
     */
    public String byClientId(String clientId) {
        return clientIdMessages.getMessage(clientId);
    }

}

xhtmlの記述

画面IDとクライアントID、いずれからでもメッセージを取得するメソッドを準備していますが、input要素に直接出力するので 通常は クライアントIDをパラメータとするメソッドを 使うことになると思います。
引数はcomponent.clientIdを使えば、当該コンポーネントのクライアントIDが取得できます。

<div class="field">
    <label>利用者ID</label>
    <input jsf:styleClass="short-input" type="text" placeholder="someone@example.com"
           jsf:id="email" jsf:value="#{userRegistrationPage.email}"
           title="#{errorTooltip.byClientId(component.clientId)}"/>

</div>

Code

vermeer_etc / jsf-ddd / source / — Bitbucket

さいご

まだ繰り返し領域に関する制御は保留だけど
とりあえず、これで メッセージ関連は一段落かな?