の続きです。
私の仕組みでは、Application層の検証不正に対しても画面項目と関連付け出来るようにしています。
そこで その仕組みを利用して 先のパターンに独自の実装を追加します。
やりたいこと
メッセージ出力に関係なく、任意の入力項目の背景色を変更する
やりかた
- BeanValidationExceptionの対象となったUIInputのClientIdを取得して出力先情報として保持
- PhaseListnerで保持した情報を使う
前回のやり方は getClientIdsWithMessages
を使って、h:message
が出力対象になっている事が前提になっていますが、このやり方であれば h:message
の有無に関係なく背景色を変えられます。
実装
UIInputのClientIdを取得
前回の h:message
に関する情報を取得するだけでしたが、今回は コンポーネント内すべてのUIInputのClientIdも取得します。
この情報とBeanValitionの結果をPhaseListnerで参照するようにします。
なお、メッセージ出力に関係する箇所は、前回同様 h:message
に関する情報を元に編集をします。
@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; @Inject public BeanValidationExceptionInterceptor(CurrentViewContext context, MessageConverter messageConverter, MessageWriter messageWriter, ClientComplementManager clientComplementManager) { this.context = context; this.messageConverter = messageConverter; this.messageWriter = messageWriter; this.clientComplementManager = clientComplementManager; } @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); return currentViewId; } } }
PhaseListner
UIInputのClientIdと検証不正対象となった入力項目の関連付けを保持したインスタンスを参照先として追加します。
public class InputFieldColorHandlerPhaseListner implements PhaseListener { private static final long serialVersionUID = 1L; @Override public void afterPhase(PhaseEvent phaseEvent) { } @Override public void beforePhase(PhaseEvent phaseEvent) { FacesContext context = phaseEvent.getFacesContext(); InputFieldColorHandler fieldColorHandler = CDI.current().select(InputFieldColorHandler.class).get(); ClientComplementManager clientComplementManager = CDI.current().select(ClientComplementManager.class).get(); fieldColorHandler.updateErrorFieldColor(context, clientComplementManager); } @Override public PhaseId getPhaseId() { return PhaseId.RENDER_RESPONSE; } }
実際のUIComponentの更新をしているクラス
既存の仕組み(updateColorHtmlMessage
)も残しつつ、新たな制御(updateColorInputComponent
)を追加します。
@ApplicationScoped public class InputFieldColorHandler { private String errorClass; @PostConstruct public void init() { this.errorClass = "error-field"; } public void updateErrorFieldColor(FacesContext context, ClientComplementManager clientComplementManager) { this.clearErrorColor(context); if (context.isValidationFailed() == false) { return; } this.updateColorHtmlMessage(context); this.updateColorInputComponent(context, clientComplementManager); } private void clearErrorColor(FacesContext context) { recursiveScan(context.getViewRoot().getChildren()) .forEach(c -> { String styleClass = String.valueOf(c.getAttributes().get("styleClass")); if (styleClass != null && styleClass.contains(errorClass)) { c.getAttributes().put("styleClass", styleClass.replace(errorClass, "").trim()); } }); } private Set<UIComponent> recursiveScan(List<UIComponent> components) { Set<UIComponent> set = new HashSet<>(); if (components == null) { return set; } components.forEach(component -> { set.add(component); set.addAll(recursiveScan(component.getChildren())); }); return set; } private void updateColorHtmlMessage(FacesContext context) { context.getClientIdsWithMessages().forEachRemaining(clientId -> { if (clientId == null) { return; } UIComponent component = context.getViewRoot().findComponent(clientId); String styleClass = String.valueOf(component.getAttributes().get("styleClass")); if (styleClass != null) { component.getAttributes().put("styleClass", styleClass.trim() + " " + errorClass); } }); } private void updateColorInputComponent(FacesContext context, ClientComplementManager clientComplementManager) { clientComplementManager.clientIds().stream() .forEach(clientId -> { UIComponent component = context.getViewRoot().findComponent(clientId); String styleClass = String.valueOf(component.getAttributes().get("styleClass")); if (styleClass != null && styleClass.contains(errorClass) == false) { component.getAttributes().put("styleClass", styleClass.trim() + " " + errorClass); } }); } }
Code
vermeer_etc / jsf-ddd / source / — Bitbucket
さいごに
背景色の変更は、概ね これで良いかなと思います。
ただ、参考にした
https://github.com/system-sekkei/isolating-the-domain
のように 背景色というよりも、指定領域のスタイルを変更する という要件には応えられていません。
つぎは、そのあたりかな?