我发现了类似的问题,但是可以通过多种方式完成,这使我更加困惑。

我们正在获取一个正在阅读的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表单组件(即label,inputText)

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

哪种方法最好?有人可以向我展示一些链接或代码,以显示如何创建此链接或代码吗?我喜欢完整的代码示例,而不喜欢“您需要javax.faces.component.UIComponent的子类”之类的答案。

#1 楼

由于起源实际上不是XML,而是Javabean,并且不应该将另一个答案编辑成完全不同的形式(它可能仍然对其他人将来的引用很有用),因此我将基于Javabean起源。


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



使用JSF rendered属性甚至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的生命周期和功能。到目前为止,仅在至少JSF视图范围内,#{bean.fields}后面的模型(如上述代码片段)没有发生变化时,才可以使用JSTL。另请参见JSF2 Facelets中的JSTL ...有意义吗?相反,将binding用作bean属性仍然是一种“坏习惯”。关于<ui:repeat><div>,实际上使用的是哪个迭代组件都没有关系,甚至可以像在初始时那样使用<h:dataTable>问题或特定于组件库的迭代组件,例如<p:dataGrid><p:dataList>。如有必要,可将大量代码重构为包含或标记文件。关于收集提交的值,#{bean.values}应指向已预先创建的Map<String, Object>。一个HashMap就足够了。如果控件可以设置多个值,则可能需要预填充地图。然后,应使用List<Object>作为值预填充它。请注意,我希望Field#getType()enum,因为这样可以简化Java代码端的处理。然后,您可以使用switch语句而不是讨厌的if/else块。





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,永远不要这样)

这保证了树是当#{bean}的作用域比请求作用域更广时,在恰好合适的时机进行填充,使getter不受业务逻辑的影响,并避免潜在的“重复组件ID”麻烦(因此,您可以在此处安全地使用例如view作用域的Bean),并且使bean不具有UIComponent属性,当组件作为可序列化bean的属性保存时,依次避免了潜在的序列化麻烦和内存泄漏。不可用,而是通过<f:event>将表单组件绑定到请求(不是会话!)范围内的bean。

 q 4312079q 


,然后以以下形式的吸气剂懒惰地填充它:

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


使用binding时,了解UI组件基本上是请求范围的,绝对不应在更广泛的范围内将其分配为bean的属性。另请参见“绑定”属性在JSF中如何工作?







使用自定义渲染器创建自定义组件。我不打算发布完整的示例,因为其中有很多代码,毕竟它们都是紧密耦合且特定于应用程序的混乱。




每个选项的优缺点都应该清楚。它从最容易和最佳可维护性到最困难和最不可维护性,随后也从最少可重用性到最佳可重用性。由您选择最适合您的功能要求和当前状况的内容。

应当指出的是,绝对没有什么只有在Java中(方法2)和在XHTML + XML(方法1)中不可能的。 XHTML + XML和Java一样,一切皆有可能。许多新手在动态创建组件时低估了XHTML + XML(尤其是<h:form id="form" binding="#{bean.form}" /> 和JSTL),并错误地认为Java是“唯一的”方法,而通常只能使代码变得脆弱和混乱。

评论


有第四个选择:PrimeFaces扩展组件:DynaForm(primefaces.org/showcase-ext/views/home.jsf)。这有一些限制,但对于大多数用户而言已足够。

– Dimitri Dewaele
2014年10月2日在10:25



嗨BalusC,我是你的忠实粉丝。我一直在学习您的答案,我需要您的电子邮件ID以对我现在面临的问题进行一些讨论。请将您的身份证件邮寄给我,邮箱:qadir.hussain99@gmail.com

–卡迪尔·侯赛因(Qadir Hussain)
2015年5月14日14:47

#2 楼

如果源是XML,则建议采用完全不同的方法:XSL。 Facelets基于XHTML。您可以轻松地使用XSL从XML到XHTML。这可以通过在JSF开始工作之前启动的Filter来实现。

这是一个启动示例。 class =“ lang-xml prettyprint-override”> 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>

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,此过滤器将启动,并使用*.jsfpersons.xml转换为persons.xhtml,最后将persons.xsl放在JSF期望的位置。有点学习曲线,但这是IMO合适的工具,因为源是XML,目标是基于XML的。

要在表单和托管bean之间进行映射,只需使用一个persons.xhtml。如果您这样命名输入字段

 Map<String, Object> 


提交的值将由<h:inputText value="#{bean.map.field1}" /> <h:inputText value="#{bean.map.field2}" /> <h:inputText value="#{bean.map.field3}" /> ... 提供键Mapfield1field2

评论


嗨@BalusC。感谢您的广泛回答。但是,我不确定是否可以从当前模型中受益。是的,我们通过XML获取数据,但是已经通过Smooks传输到JavaBean(xml2Java)。所以我不确定我可以按照您的建议去做...

–谢尔文·阿斯加里(Shervin Asgari)
2010年8月19日在8:37



是否必须将person.xml和person.xsl存储到此路径-.getRealPath(“ /”)?当我尝试将这些文件移动到.getRealPath(“ / public_resources / xsl_xml”)(以及)时,它抱怨说,Prolog中不允许内容-生成的XHTML文件的格式不再正确。

–小
2014年6月25日在21:15



@Tiny 必须表示XML结构,而不是XML文件所在的路径。请勿更改它。

– BalusC
2014年6月26日下午5:17