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

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

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

さいご

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