1

問題が発生しています。

メッセージが添付されているツリー内の UIInput コンポーネントにスタイル クラスを追加し、メッセージが添付されていない場合はスタイル クラスを削除する PhaseListener を実装しました。

PhaseListener は RENDER_RESPONSE フェーズで実行され、デバッグ中に beforePhase メソッドと afterPhase メソッドの両方で機能します。デバッグ中に、beforePhase では完全なコンポーネント ツリーにアクセスできないことがわかりましたが、afterPhase ではアクセスできます。ただし、afterPhase で行われた変更はレンダリングされません。

どうすればいいですか?これを完全にサーバー側にしたい。

ありがとう、

ジェームズ

4

2 に答える 2

2

JSF コンポーネント ツリーは、ビューのビルド後にのみ使用できます。このRENDER_RESPONSEフェーズは、完全な JSF コンポーネント ツリーがレンダリングされる前にアクセスできる適切なタイミングであるとは限りません。を使用しない最初の GET リクエスト<f:viewAction>では、完全なコンポーネント ツリーは でのみ使用できafterPhaseますRENDER_RESPONSE。ポストバック中は で完全なコンポーネント ツリーを使用できますがbeforePhase、別のビューへのナビゲーションが行われると、フェーズ中に変更されるため、変更が失われます。RENDER_RESPONSE

ビューのビルド時間とは何かを正確に知るには、ビューのビルド時間は何ですか? という質問に進んでください。

beforePhase基本的に、RENDER_RESPONSEフェーズではなく「ビューのレンダリング時間」にフックする必要があります。JSF には、フックする方法がいくつか用意されています。

  1. 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.
        // ...
    }        
    
  2. 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>
    
  3. または、 で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>
    
  4. または、 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>まさにこれを行うコンポーネントがあります。

于 2015-11-10T16:47:46.170 に答える
0

ViewHandler を使用して実装されていますが、効率的ではありません。Render Response フェーズの PhaseListener は、コンポーネント ツリーにアクセスできません。

于 2010-03-23T14:11:08.597 に答える