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 表单组件(即标签、输入文本)

另一种方法是只显示<my:someCustomComponent>然后会返回一个HtmlDataTable带有表单元素的元素。(我认为这可能更容易做到)。

哪种方法最好?有人可以向我展示一些链接或代码,其中显示了我如何创建它吗?我更喜欢完整的代码示例,而不是像“你需要一个子类javax.faces.component.UIComponent”这样的答案。

4

2 回答 2

56

由于起源实际上不是 XML,而是 Javabean,并且另一个答案不值得编辑成完全不同的风格(它可能仍然对其他人的未来参考有用),我将添加另一个基于 a 的答案Javabean 起源。


当源是 Javabean 时,我基本上看到了三个选项。

  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>
    

    可以在如何制作 JSF 复合组件网格中找到 JSTL 方法的示例?不,JSTL 绝对不是“坏习惯”。这个神话是 JSF 1.x 时代遗留下来的,并且持续了太久,因为初学者没有清楚地了解 JSTL 的生命周期和功能。就这一点而言,只有当#{bean.fields}上面代码片段中的模型至少在 JSF 视图范围内没有改变时,才能使用 JSTL。另请参阅JSF2 Facelets 中的 JSTL... 有意义吗?相反,使用bindingbean 属性仍然是“不好的做法”。

    至于 ,<ui:repeat><div>您使用哪个迭代组件实际上并不重要,您甚至可以<h:dataTable>在最初的问题中使用,或者组件库特定的迭代组件,例如<p:dataGrid><p:dataList>如有必要,将大块代码重构为 include 或 tagfile

    至于收集提交的值,#{bean.values}应该指向一个Map<String, Object>已经预先创建的。一个HashMap就够了。如果控件可以设置多个值,您可能需要预填充地图。List<Object>然后,您应该使用as 值预填充它。请注意,我希望它Field#getType()是一个,enum因为它简化了 Java 代码端的处理。然后,您可以使用switch语句而不是讨厌的if/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”问题(因此您可以安全地使用例如视图范围bean 在这里),并保持 bean 没有UIComponent属性,这反过来又避免了当组件作为可序列化 bean 的属性保存时潜在的序列化问题和内存泄漏。

    如果您仍然在<f:event>不可用的 JSF 1.x 上,请将表单组件绑定到请求(不是会话!)范围 bean 通过binding

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

    然后在表单的 getter 中懒惰地填充它:

    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)中是不可能的。在 XHTML+XML 中一切皆有可能与在 Java 中一样好。许多初学者<ui:repeat>在动态创建组件方面低估了 XHTML+XML(尤其是 JSTL),并错误地认为 Java 将是“唯一的”方式,而这通常只会导致脆弱和混乱的代码。

于 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>of 上,FacesServlet并假设FacesServlet自身映射在<url-pattern>of 上*.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.xmlpersons.xhtmlusing persons.xsl,最后放置persons.xhtml在 JSF 期望的位置。

诚然,XSL 有一点学习曲线,但它是 IMO 适合这项工作的工具,因为源是 XML,而目标也是基于 XML 的。

要在表单和托管 bean 之间进行映射,只需使用Map<String, Object>. 如果您像这样命名输入字段

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

提交的值将通过Mapfield1, field2,field3等可用。

于 2010-08-18T11:58:56.590 回答