24

このような同様の質問をいくつか見つけましたが、これを行う方法が非常に多いため、さらに混乱しました。

XML読み取り中のファイルを取得しています。これXMLには、表示する必要のあるいくつかのフォーム フィールドに関する情報が含まれています。

そこでDynamicField.java、必要なすべての情報を含むこのカスタムを作成しました。

public class DynamicField {
  private String label; // label of the field
  private String fieldKey; // some key to identify the field
  private String fieldValue; // the value of field
  private String type; // can be input,radio,selectbox etc

  // Getters + setters.
}

したがって、 がありList<DynamicField>ます。

このリストを繰り返し処理し、フォーム フィールドにデータを入力して、次のようにします。

<h:dataTable value="#{dynamicFields}" var="field">
    <my:someCustomComponent value="#{field}" />
</h:dataTable>

次に<my:someCustomComponent>、適切な JSF フォーム コンポーネント (つまり、ラベル、inputText) を返します。

もう 1 つの方法は、 を表示するだけで、フォーム要素を持つ<my:someCustomComponent>を返すことです。HtmlDataTable(これはおそらくやりやすいと思います)。

どのアプローチが最適ですか? これを作成する方法を示すリンクまたはコードを誰かに見せてもらえますか? 「のサブクラスが必要です」のような答えではなく、完全なコード例を好みますjavax.faces.component.UIComponent

4

2 に答える 2

56

オリジンは実際にはXMLではなくJavabeanであり、他の回答はまったく異なるフレーバーに編集する価値がないため(他の人による将来の参照にはまだ役立つかもしれません)、に基づいて別の回答を追加します. Javabean 由来。


オリジンが Javabean の場合、基本的に 3 つのオプションが表示されます。

  1. JSFrendered属性または JSTL <c:choose>/<c:if>タグを使用して、目的のコンポーネントを条件付きでレンダリングまたはビルドします。以下は、rendered属性を使用した例です。

    <ui:repeat value="#{bean.fields}" var="field">
        <div class="field">
            <h:inputText value="#{bean.values[field.name]}" rendered="#{field.type == 'TEXT'}" />
            <h:inputSecret value="#{bean.values[field.name]}" rendered="#{field.type == 'SECRET'}" />
            <h:inputTextarea value="#{bean.values[field.name]}" rendered="#{field.type == 'TEXTAREA'}" />
            <h:selectOneRadio value="#{bean.values[field.name]}" rendered="#{field.type == 'RADIO'}">
                <f:selectItems value="#{field.options}" />
            </h:selectOneRadio>
            <h:selectOneMenu value="#{bean.values[field.name]}" rendered="#{field.type == 'SELECTONE'}">
                <f:selectItems value="#{field.options}" />
            </h:selectOneMenu>
            <h:selectManyMenu value="#{bean.values[field.name]}" rendered="#{field.type == 'SELECTMANY'}">
                <f:selectItems value="#{field.options}" />
            </h:selectManyMenu>
            <h:selectBooleanCheckbox value="#{bean.values[field.name]}" rendered="#{field.type == 'CHECKONE'}" />
            <h:selectManyCheckbox value="#{bean.values[field.name]}" rendered="#{field.type == 'CHECKMANY'}">
                <f:selectItems value="#{field.options}" />
            </h:selectManyCheckbox>
        </div>
    </ui:repeat>
    

    JSTL アプローチの例は、JSF 複合コンポーネントのグリッドを作成する方法で見つけることができます。いいえ、JSTL は絶対に「悪い習慣」ではありません。この神話は JSF 1.x 時代の名残であり、初心者が JSTL のライフサイクルと機能を明確に理解していなかったため、長く続いています。要するに、JSTL を使用できるのは#{bean.fields}、上記のスニペットの背後にあるモデルが、少なくとも JSF ビュー スコープで変更されない場合のみです。JSF2 FaceletsのJSTLも参照してください...理にかなっていますか? 代わりにbinding、Bean プロパティを使用することは依然として「悪い習慣」です。

    に関しては<ui:repeat><div>、使用する反復コンポーネントは実際には問題ではありません。最初の質問のように使用することも、 または などのコンポーネント ライブラリ固有の反復コンポーネントを使用することもでき<h:dataTable>ます。必要に応じて、大量のコードを include または tagfile にリファクタリングします。<p:dataGrid><p:dataList>

    送信された値の収集に関しては、はすでに作成済み#{bean.values}の を指す必要があります。Map<String, Object>AHashMapで十分です。複数の値を設定できるコントロールの場合は、マップを事前に設定することをお勧めします。List<Object>次に、 as 値を事前に入力する必要があります。Java コード側での処理が容易Field#getType()になるため、 がであると予想していることに注意してください。その後、厄介なブロックの代わりにステートメントenumを使用できます。switchif/else


  2. postAddToViewイベント リスナーでプログラムによってコンポーネントを作成します。

    <h:form id="form">
        <f:event type="postAddToView" listener="#{bean.populateForm}" />
    </h:form>
    

    と:

    public void populateForm(ComponentSystemEvent event) {
        HtmlForm form = (HtmlForm) event.getComponent();
        for (Field field : fields) {
            switch (field.getType()) { // It's easiest if it's an enum.
                case TEXT:
                    UIInput input = new HtmlInputText();
                    input.setId(field.getName()); // Must be unique!
                    input.setValueExpression("value", createValueExpression("#{bean.values['" + field.getName() + "']}", String.class));
                    form.getChildren().add(input);
                    break;
                case SECRET:
                    UIInput input = new HtmlInputSecret();
                    // etc...
            }
        }
    }
    

    (注: 自分で作成しないでHtmlFormください! JSF で作成したものを使用してください。これは決して ではありませんnull)

    これにより、ツリーが正確に適切なタイミングで生成され、getter がビジネス ロジックから解放#{bean}され、リクエスト スコープよりも広いスコープにある場合に「コンポーネント ID が重複する」という潜在的な問題が回避されます (したがって、たとえばビュー スコープを安全に使用できます)。これによりUIComponent、コンポーネントがシリアライズ可能な Bean のプロパティとして保持されている場合に、シリアライゼーションの潜在的な問題やメモリ リークが回避されます。

    利用できないJSF 1.x をまだ<f:event>使用している場合は、代わりに、フォーム コンポーネントをリクエスト (セッションではなく) スコープ Bean にバインドします。binding

    <h:form id="form" binding="#{bean.form}" />
    

    そして、フォームのゲッターに遅延入力します。

    public HtmlForm getForm() {
        if (form == null) {
            form = new HtmlForm();
            // ... (continue with code as above)
        }
        return form;
    }
    

    を使用する場合binding、UI コンポーネントは基本的にリクエスト スコープであり、より広いスコープで Bean のプロパティとして割り当てるべきではないことを理解することが非常に重要です。「バインディング」属性は JSF でどのように機能しますか?も参照してください。いつ、どのように使用する必要がありますか?


  3. カスタム レンダラーを使用してカスタム コンポーネントを作成します。完全な例を投稿するつもりはありません。これは非常に密結合でアプリケーション固有の混乱を招く大量のコードだからです。


各オプションの長所と短所を明確にする必要があります。それは、最も簡単で保守しやすいものから、最も困難で保守性が最も低いものへと変化し、その後、再利用可能性が最も低いものから再利用可能性が最も高いものへと変化します。機能要件と現在の状況に最も適したものを選択するのは、あなた次第です。

Javaでのみ可能 (方法 2) で、XHTML+XML で不可能 (方法 1)は絶対にないことに注意してください。Java と同様に、XHTML+XML でもすべてが可能です。多くのスターターは、コンポーネントを動的に作成する際に XHTML+XML (特に JSTL) を過小評価し、Java が「唯一無二の」方法であると誤って考えています。<ui:repeat>

于 2010-08-19T13:56:37.257 に答える
18

オリジンがXMLの場合は、まったく異なるアプローチであるXSLを使用することをお勧めします。FaceletsはXHTMLベースです。XSLを使用してXMLからXHTMLに簡単に移行できます。Filterこれは、JSFが作業を行う前に開始する、少しまともな方法で実行できます。

これがキックオフの例です。

persons.xml

<?xml version="1.0" encoding="UTF-8"?>
<persons>
    <person>
        <name>one</name>
        <age>1</age>
    </person>
    <person>
        <name>two</name>
        <age>2</age>
    </person>
    <person>
        <name>three</name>
        <age>3</age>
    </person>
</persons>

persons.xsl

<?xml version="1.0" encoding="UTF-8"?>

<xsl:stylesheet 
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0"
    xmlns:f="http://java.sun.com/jsf/core"
    xmlns:h="http://java.sun.com/jsf/html">

    <xsl:output method="xml"
        doctype-public="-//W3C//DTD XHTML 1.0 Transitional//EN"
        doctype-system="http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"/>

    <xsl:template match="persons">
        <html>
        <f:view>
            <head><title>Persons</title></head>
            <body>
                <h:panelGrid columns="2">
                    <xsl:for-each select="person">
                        <xsl:variable name="name"><xsl:value-of select="name" /></xsl:variable>
                        <xsl:variable name="age"><xsl:value-of select="age" /></xsl:variable>
                        <h:outputText value="{$name}" />
                        <h:outputText value="{$age}" />
                    </xsl:for-each>
                </h:panelGrid>
            </body>
        </f:view>
        </html>
    </xsl:template>
</xsl:stylesheet>

JsfXmlFilterこれはのにマップされ、それ自体がのにマップされて<servlet-name>いることFacesServletを前提としています。FacesServlet<url-pattern>*.jsf

public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
    throws IOException, ServletException
{
    HttpServletRequest r = (HttpServletRequest) request;
    String rootPath = r.getSession().getServletContext().getRealPath("/");
    String uri = r.getRequestURI();
    String xhtmlFileName = uri.substring(uri.lastIndexOf("/")).replaceAll("jsf$", "xhtml"); // Change this if FacesServlet is not mapped on `*.jsf`.
    File xhtmlFile = new File(rootPath, xhtmlFileName);

    if (!xhtmlFile.exists()) { // Do your caching job.
        String xmlFileName = xhtmlFileName.replaceAll("xhtml$", "xml");
        String xslFileName = xhtmlFileName.replaceAll("xhtml$", "xsl");
        File xmlFile = new File(rootPath, xmlFileName);
        File xslFile = new File(rootPath, xslFileName);
        Source xmlSource = new StreamSource(xmlFile);
        Source xslSource = new StreamSource(xslFile);
        Result xhtmlResult = new StreamResult(xhtmlFile);

        try {
            Transformer transformer = TransformerFactory.newInstance().newTransformer(xslSource);
            transformer.transform(xmlSource, xhtmlResult);
        } catch (TransformerException e) {
            throw new RuntimeException("Transforming failed.", e);
        }
    }

    chain.doFilter(request, response);
}

http://example.com/context/persons.jsfによって実行され、このフィルターが起動して使用に変換persons.xmlされ、最終的にJSFが期待する場所に配置されます。persons.xhtmlpersons.xslpersons.xhtml

確かに、XSLには少し学習曲線がありますが、ソースはXMLであり、宛先はwelとしてXMLベースであるため、IMOはこの仕事に適したツールです。

フォームとマネージドBeanの間のマッピングを行うには、を使用しMap<String, Object>ます。入力フィールドにそのように名前を付けると

<h:inputText value="#{bean.map.field1}" />
<h:inputText value="#{bean.map.field2}" />
<h:inputText value="#{bean.map.field3}" />
...

送信された値は、キーMap、、、field1などで利用できます。field2field3

于 2010-08-18T11:58:56.590 に答える