我们正在获取一个正在阅读的
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是“唯一的”方法,而通常只能使代码变得脆弱和混乱。#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
pre >
<?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,此过滤器将启动,并使用*.jsf
将persons.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}" /> ...
提供键Map
,field1
,field2
等评论
嗨@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
评论
有第四个选择: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