3

次のように、可変数の入力要素を持つフォームがあります。

<ui:repeat var="_lang" value="#{myBean.languages}">
    <h:inputTextarea value="${_lang.title}" id="theTitle" />
    <h:messages for="theTitle"/>
</ui:repeat>

バッキング Bean の特定のメソッドがトリガーされたときに、たとえば の 2 回目の繰り返しにメッセージを追加したいがui:repeat、他のものには追加したくない。

ここでこの質問のさまざまなバリエーションを見てきましたが、すべての問題ui:repeatは、JSF コンポーネント ツリーで の反復が利用できないことが原因のようです。

私がこれまでに試したこと:

  • h:inputTextareas をMap<String,UIComponent>Beanの a にバインドします。(a) ...Using ...binding="#{myBean.uiMap[_lang.id]}"(_lang.idは一意の文字列)。これにより、JBWEB006017: Target Unreachable, ''BracketSuffix'' returned nullが生成されました。(ID を使用して対応する文字列のマップをダンプしました。同じ構文が の外でも問題なく動作しますbinding) (b) ...または...binding="#{myBean.uiMap.get()}". これにより、ページが正常にレンダリングされますが、メソッドのボタンを押してもセッターが呼び出されないため、UIComponentが に追加されることはありませんMap

  • h:inputTextareas を Bean の配列にバインドUIComponent[]し、適切な数の null を事前に入力してから、行カウンターをui:repeatxhtml ファイルのインデックスとして使用します。Null ポインター例外が発生しました。配列のセッターが呼び出されなかったため、配列に実際UIComponentの s が取り込まれませんでした。

  • 外部h:panelGroupを Bean にバインドし、JSF ツリー内のその子の中から入力要素を再帰的に見つけようとします。入力の 1 つだけが見つかりました。上記の「反復が利用できない」問題を参照してください。

  • また、行番号を手動で置き換えて生成しようとしましたui:repeatc:forEach(JSF ツリーで使用できるようにするため)、レンダリングされた出力はまったく得られませんでした。

(注: 目的は検証エラー メッセージを表示することですが、それらはバッキング Bean からf:validator取得する必要があります。バッキング Bean のデータに対して検証する必要があるため、カスタムのものであっても、または類似のものを使用することは実際にはオプションではありません。 )

率直に言って、私はアイデアがありません。これはそれほど難しいことではありませんよね?

編集:

私の 3 回目の試みである outer へのバインドではh:panelGroup、JSF ファインダー関数を次に示します。

private List<UIComponent> findTitleComponents(UIComponent node) {
    List<UIComponent> found = new ArrayList<UIComponent>();
    for (UIComponent child : node.getChildren()) {
        if (child.getId().equals("theTitle")) {
            found.add(child);
            log.debug("have found "+child.getClientId());
        } else {
            found.addAll(findTitleComponents(child));
            log.debug("recursion into "+child.getClientId());
        }
    }
    return found;
}

これを で呼び出しています。これは、の周りのnodeバインディングです。(私のライブ アプリケーションはもう少し入れ子構造になっているため、再帰を使用しています) これですべての "theTitle" テキストエリアが得られるので、メッセージを追加したり、属性を好きなように読み取ったりできると思いました。残念ながら、メソッドは1 つの「theTitle」コンポーネントのみを返し、ログ メッセージはその理由を示しています。UIComponenth:panelGroupui:repeat

生成されたページの DOM では、id は "myform:myPanelGroup:0:theTitle" (の反復カウンターを含むui:repeat) のようになりますが、Bean は getClientId() のように見えるmyform:myPanelGroup:theTitleだけで、最後に (私は推測しますか?) 反復。

4

2 に答える 2

2

入力コンポーネントをマップ/配列にバインドしようとして失敗しました。JSF コンポーネント ツリーにはこれらのコンポーネントが複数ではなく、1 つしかないためです。は<ui:repeat>、JSF コンポーネント ツリーを生成するビューのビルド時には実行されません。代わりに、HTML 出力を生成するビューのレンダリング時に実行されます。つまり、<ui:repeat>各繰り返しの HTML 出力を生成する際に、毎回 の子コンポーネントが再利用されます。

特定の例外「Target Unreachable, ''BracketSuffix'' returned null」がスローされました。これは、UI コンポーネント ツリーが構築され、すべての属性が評価さ#{_lang}れる瞬間のビューのビルド時に変数を使用できないためです。ビューのレンダリング時にのみ使用できます。idbinding

<c:forEach>代わりに使用した場合、これらのバインディングの試みは成功したでしょう。ビューのビルド時に実行され、JSF コンポーネント ツリーが生成されます。その後、子コンポーネントの物理的に複数のインスタンスが作成され、複数回再利用されることなく、それぞれ独自の HTML 出力が生成されます。

パネル グループを作成してすべての子を見つけようとしても、前述の理由から明らかにうまくいきません。は<ui:repeat>、コンポーネント ツリーに物理的に複数の JSF コンポーネントを生成しません。代わりに、現在の反復ラウンドの状態に応じて、同じコンポーネントを再利用して HTML 出力を複数回生成します。

by を置き換える<c:forEach>とうまくいくはずです。おそらく、ビューのビルド時に実行され、preRenderView代わりにモデルを準備しているため、タイミングの問題に直面していた可能性があります@PostConstruct

上記のすべては、JSF2 Facelets の JSTL を注意深く読むと理解しやすくなります...理にかなっていますか?


具体的な機能要件に関しては、通常Validator、ジョブに a を使用します。入力コンポーネントに登録すると、反復ラウンドごとに呼び出されます。メソッドの 2 番目の引数として適切な状態の適切な入力コンポーネントをすぐにvalidate()取得し、3 番目の引数として送信/変換された値を取得します。

たとえば、すべての入力について知る必要があるなどの理由で、後で本当にジョブを実行する必要がある場合は、<ui:repeat>自分自身をプログラムで反復処理する必要があります。UIComponent#visitTree()これを使用すると、すべての反復ラウンドの入力コンポーネントの状態を収集できます。

例えば

final FacesContext facesContext = FacesContext.getCurrentInstance();
UIComponent repeat = getItSomehow(); // findComponent, binding, etc.

repeat.visitTree(VisitContext.createVisitContext(facesContext), new VisitCallback() {
    @Override
    public VisitResult visit(VisitContext context, UIComponent target) {
        if (target instanceof UIInput && target.getId().equals("theTitle")) {
            String clientId = target.getClientId(facesContext);
            Object value = ((UIInput) target).getValue();
            // ...
            facesContext.addMessage(clientId, message);                
        }
        return VisitResult.ACCEPT;
    }
});

以下も参照してください。

于 2013-10-02T11:37:42.693 に答える
0

別のオプションがあります。FacesMessages シバン全体を独自に置き換えることです。ブラックジャックで。と...

いずれにせよ、 Johannes Brodwallとの話し合いに基づいて、visitTree 全体の混乱を回避し、独自のメッセージ メカニズムを構築することにしました。これには以下が含まれます:

1) マルチマップのマップを含む ViewScoped Bean:

private Map<Object, Multimap<String, String>> fieldValidationMessages = new HashMap<>();

これはObject、フィールド識別子として を受け取ります (それぞれの Bean 自体、UI コンポーネント、またはString内で実行時に生成されるui:repeatです。その識別子は、String別の任意の数のサブフィールドに任意の数のメッセージを持つことができます。非常に柔軟です。

この Bean には、フィールドとサブフィールドでメッセージを取得および設定するための便利なメソッドや、メッセージが保存されているかどうか (つまり、検証エラーがあったかどうか) をチェックするための便利なメソッドもありました。

2) 特定のフィールドのエラー メッセージを表示する単純な xhtml インクルード。h:messages for...

そして、それはすでにそれです。問題は、これが JSF 独自の検証フェーズではなく、ライフサイクルのアプリケーションおよびレンダリング フェーズで実行されることです。しかし、私たちのプロジェクトでは、ライフサイクル検証の代わりに Bean 検証を行うことを既に決定していたので、これは問題ではありませんでした。

于 2013-12-03T15:14:31.223 に答える