JSF コンポーネント ツリーは、ビューのビルド後にのみ使用できます。このRENDER_RESPONSE
フェーズは、完全な JSF コンポーネント ツリーがレンダリングされる前にアクセスできる適切なタイミングであるとは限りません。を使用しない最初の GET リクエスト<f:viewAction>
では、完全なコンポーネント ツリーは でのみ使用できafterPhase
ますRENDER_RESPONSE
。ポストバック中は で完全なコンポーネント ツリーを使用できますがbeforePhase
、別のビューへのナビゲーションが行われると、フェーズ中に変更されるため、変更が失われます。RENDER_RESPONSE
ビューのビルド時間とは何かを正確に知るには、ビューのビルド時間は何ですか? という質問に進んでください。
beforePhase
基本的に、RENDER_RESPONSE
フェーズではなく「ビューのレンダリング時間」にフックする必要があります。JSF には、フックする方法がいくつか用意されています。
preRenderView
一部のマスター テンプレートで、リスナーをにアタッチし<f:view>
ます。
<f:view ...>
<f:event type="preRenderView" listener="#{bean.onPreRenderView}" />
...
</f:view>
public void onPreRenderView(ComponentSystemEvent event) {
UIViewRoot view = (UIViewRoot) event.getSource();
// The view is the component tree. Just modify it here accordingly.
// ...
}
SystemEventListener
または、 のグローバルを実装しPreRenderViewEvent
ます。
public class YourPreRenderViewListener implements SystemEventListener {
@Override
public boolean isListenerForSource(Object source) {
return source instanceof UIViewRoot;
}
@Override
public void processEvent(SystemEvent event) throws AbortProcessingException {
UIViewRoot view = (UIViewRoot) event.getSource();
// The view is the component tree. Just modify it here accordingly.
// ...
}
}
実行するには、次のように登録しますfaces-config.xml
。
<application>
<system-event-listener>
<system-event-listener-class>com.example.YourPreRenderViewListener</system-event-listener-class>
<system-event-class>javax.faces.event.PreRenderViewEvent</system-event-class>
</system-event-listener>
</application>
または、 でViewHandler
ジョブを実行するカスタムを提供しますrenderView()
。
public class YourViewHandler extends ViewHandlerWrapper {
private ViewHandler wrapped;
public YourViewHandler(ViewHandler wrapped) {
this.wrapped = wrapped;
}
@Override
public void renderView(FacesContext context, UIViewRoot view) {
// The view is the component tree. Just modify it here accordingly.
// ...
// Finally call super so JSF can do the rendering job.
super.renderView(context, view);
}
@Override
public ViewHandler getWrapped() {
return wrapped;
}
}
実行するには、次のように登録しますfaces-config.xml
。
<application>
<view-handler>com.example.YourViewHandler</view-handler>
</application>
または、 on をフックしViewDeclarationLanguage#renderView()
ますが、これはコンポーネント ツリーを操作することを意図したものではなく、ビューをレンダリングする方法を操作することを目的としているため、少しばかりです。
具体的な問題とは関係ありませんが、質問に記載されているように、これはすべて具体的な機能要件の正しい解決策ではありません。
これは、メッセージが添付されているツリー内の UIInput コンポーネントにスタイル クラスを追加し、メッセージが添付されていない場合はスタイル クラスを削除します。
コンポーネントツリーを操作するのではなく、クライアント側のソリューションに向かうほうがよいでしょう (最終的には JSF コンポーネント状態になります!)。のような反復コンポーネントの入力の場合を想像してください<ui:repeat><h:inputText>
。ツリーには、複数ではなく、物理的に 1 つの入力コンポーネントしかありません。経由でスタイル クラスを操作するUIInput#setStyleClass()
と、すべての反復ラウンドで表示されます。
以下を使用してコンポーネント ツリーにアクセスし、UIViewRoot#visitTree()
無効な入力コンポーネントのすべてのクライアント ID を収集することをお勧めします (このvisitTree()
方法では、反復コンポーネントが透過的に考慮されます)。
Set<String> invalidInputClientIds = new HashSet<>();
view.visitTree(VisitContext.createVisitContext(context, null, EnumSet.of(VisitHint.SKIP_UNRENDERED)), new VisitCallback() {
@Override
public VisitResult visit(VisitContext context, UIComponent component) {
if (component instanceof UIInput) {
UIInput input = (UIInput) component;
if (!input.isValid()) {
invalidInputClientIds.add(input.getClientId(context.getFacesContext()));
}
}
return VisitResult.ACCEPT;
}
});
その後invalidInputClientIds
、JSON 配列のフレーバーを JavaScript に渡します。JavaScript はそれらを取得して属性document.getElementById()
を変更しclassName
ます。
for (var i = 0; i < invalidInputClientIds.length; i++) {
var invalidInput = document.getElementById(invalidInputClientIds[i]);
invalidInput.className += ' error';
}
JSF ユーティリティ ライブラリOmniFacesには、<o:highlight>
まさにこれを行うコンポーネントがあります。