2

アプリケーションでナビゲートし、さまざまな facelet ページを動的に含めたいとします。次のような commandLink があります。

<h:commandLink value="Link" action="#{navigation.goTo('someTest')}">
    <f:ajax render=":content" />
</h:commandLink>

ここに facelet を含めます。

<h:form id="content">
    <ui:include src="#{navigation.includePath}" />
</h:form>

ナビゲーション クラス:

public class Navigation {
    private String viewName;

    public void goTo(String viewName) {
        this.viewName = viewName;
    }

    public String getIncludePath() {
        return resolvePath(viewName);
    }
}

同様の例を見たことがありますが、これはもちろん機能しません。タグ ハンドラーと同様ui:includeに、ナビゲーション リスナーが呼び出されるずっと前にインクルードが発生します。新しい facelet ではなく、古い facelet が含まれています。これまでのところ、私はそれを理解しています。

頭痛の部分に移りましょう: actionListener に基づいて facelet を動的に含めるにはどうすればよいでしょうか? facelet を preRender イベントに含めようとし、RENDER_RESPONSE の前に phaseListener を含めようとしました。どちらも機能しますが、イベント リスナーでは、他の preRender イベントを含む facelet を含めることはできません。また、phaseListener では、含まれている facelet で数回クリックした後に重複した ID を取得します。ただし、コンポーネント ツリーを調べると、重複するコンポーネントはまったくないことがわかります。たぶん、これらの2つのアイデアはまったく良くありませんでした..

ui:includeを含むページ、または facelet を含む Java クラスが、含まれるページや正確なパスを知る必要がないソリューションが必要です。以前にこの問題を解決した人はいますか? どうすればいいですか?


JSF 2.1 と Mojarra 2.1.15 を使用しています


問題を再現するために必要なのは、次の Bean だけです。

@Named
public class Some implements Serializable {
    private static final long serialVersionUID = 1L;
    private final List<String> values = new ArrayList<String>();

    public Some() {
        values.add("test");
    }

    public void setInclude(String include) {
    }
    public List<String> getValues() {
        return values;
    }
}

これはあなたのインデックスファイルにあります:

<h:head>
    <h:outputScript library="javax.faces" name="jsf.js" />
</h:head>

<h:body>
    <h:form id="topform">
        <h:panelGroup id="container">
            <my:include src="/test.xhtml" />
        </h:panelGroup>
    </h:form>
</h:body>

そして、これはtext.xhtmlにあります

<ui:repeat value="#{some.values}" var="val">
    <h:commandLink value="#{val}" action="#{some.setInclude(val)}">
        <f:ajax render=":topform:container" />
    </h:commandLink>
</ui:repeat>

次のようなエラーを生成するのに十分です。

javax.faces.FacesException: Cannot add the same component twice: topform:j_id-549384541_7e08d92c
4

1 に答える 1

3

OmniFacesについては、メソッドで aを実行する aの代わりに<o:include>asを作成して、これを実験したこともあります。このようにして、含まれている適切な facelet はビューの復元段階で記憶され、含まれているコンポーネント ツリーは応答のレンダリング段階でのみ変更されます。UIComponentTagHandlerFaceletContext#includeFacelet()encodeChildren()

基本的なキックオフの例を次に示します。

@FacesComponent("com.example.Include")
public class Include extends UIComponentBase {

    @Override
    public String getFamily() {
        return "com.example.Include";
    }

    @Override
    public boolean getRendersChildren() {
        return true;
    }

    @Override
    public void encodeChildren(FacesContext context) throws IOException {
        getChildren().clear();
        ((FaceletContext) context.getAttributes().get(FaceletContext.FACELET_CONTEXT_KEY)).includeFacelet(this, getSrc());
        super.encodeChildren(context);
    }

    public String getSrc() {
        return (String) getStateHelper().eval("src");
    }

    public void setSrc(String src) {
        getStateHelper().put("src", src);
    }

}

次のように登録され.taglib.xmlています。

<tag>
    <tag-name>include</tag-name>
    <component>
        <component-type>com.example.Include</component-type>
    </component>
    <attribute>
        <name>src</name>
        <required>true</required>
        <type>java.lang.String</type>
    </attribute>
</tag>

これは、次のビューで正常に機能します。

<h:outputScript name="fixViewState.js" />

<h:form>
    <ui:repeat value="#{includeBean.includes}" var="include">
        <h:commandButton value="Include #{include}" action="#{includeBean.setInclude(include)}">
            <f:ajax render=":include" />
        </h:commandButton>
    </ui:repeat>
</h:form>

<h:panelGroup id="include">
    <my:include src="#{includeBean.include}.xhtml" />
</h:panelGroup>

そして、次のバッキング Bean:

@ManagedBean
@ViewScoped
public class IncludeBean implements Serializable {

    private List<String> includes = Arrays.asList("include1", "include2", "include3");
    private String include = includes.get(0);

    private List<String> getIncludes() {
        return includes;
    }

    public void setInclude(String include) {
        return this.include = include;
    }

    public String getInclude() { 
        return include;
    }

}

(この例では、インクルード ファイルが想定include1.xhtmlされており、メイン ファイルinclude2.xhtmlinclude3.xhtml同じベース フォルダーにあります)

このfixViewState.js回答で見つけることができます: h:commandButton/h:commandLink does not work on first click, works only on second click . このスクリプトは、互いの親を更新する複数の ajax フォームがある場合にビュー ステートが失われるJSF 問題 790を修正するために必須です。

また、この方法では、必要に応じて各インクルード ファイルに独自のファイルを含めることができる<h:form>ため、必ずしもインクルードの前後に配置する必要はありません。

このアプローチは、Mojarra では、インクルード内のフォームからのポストバック リクエストでも問題なく機能しますが、MyFaces では、最初のリクエスト中に次の例外が発生して失敗します。

java.lang.NullPointerException
    at org.apache.myfaces.view.facelets.impl.FaceletCompositionContextImpl.generateUniqueId(FaceletCompositionContextImpl.java:910)
    at org.apache.myfaces.view.facelets.impl.DefaultFaceletContext.generateUniqueId(DefaultFaceletContext.java:321)
    at org.apache.myfaces.view.facelets.compiler.UIInstructionHandler.apply(UIInstructionHandler.java:87)
    at javax.faces.view.facelets.CompositeFaceletHandler.apply(CompositeFaceletHandler.java:49)
    at org.apache.myfaces.view.facelets.tag.ui.CompositionHandler.apply(CompositionHandler.java:158)
    at org.apache.myfaces.view.facelets.compiler.NamespaceHandler.apply(NamespaceHandler.java:57)
    at org.apache.myfaces.view.facelets.compiler.EncodingHandler.apply(EncodingHandler.java:48)
    at org.apache.myfaces.view.facelets.impl.DefaultFacelet.include(DefaultFacelet.java:394)
    at org.apache.myfaces.view.facelets.impl.DefaultFacelet.include(DefaultFacelet.java:448)
    at org.apache.myfaces.view.facelets.impl.DefaultFacelet.include(DefaultFacelet.java:426)
    at org.apache.myfaces.view.facelets.impl.DefaultFaceletContext.includeFacelet(DefaultFaceletContext.java:244)
    at com.example.Include.encodeChildren(Include.java:54)

MyFaces は基本的に、ビューのビルド時間の最後に Facelet コンテキストを解放し、ビューのレンダリング時に使用できなくなり、内部状態に null 化されたプロパティがいくつかあるため、NPE が発生します。ただし、レンダリング時に Facelet ファイルの代わりに個々のコンポーネントを追加することは可能です。これが私のせいなのか、MyFaces のせいなのかを調べる時間はありませんでした。それが、まだ OmniFaces に収まっていない理由でもあります。

とにかく Mojarra を使用している場合は、自由に使用してください。ただし、考えられるすべてのユースケースを同じページで徹底的にテストすることを強くお勧めします。Mojarra には、このコンストラクトを使用すると失敗する可能性のある状態保存関連の癖がいくつかあります。

于 2012-12-05T12:55:48.393 に答える