我发现了一些像this这样的类似问题,但是有很多方法可以做到这让我更加困惑。
我们正在获取正在阅读的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
的子类”这样的答案。
由于原点实际上不是XML,而是Javabean,而另一个答案不值得编辑成完全不同的风格(它可能对其他人未来的引用仍然有用),我将添加另一个基于a的答案JavaBean的由来。
当原点是Javabean时,我基本上看到三个选项。
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>
可以在How to make a grid of JSF composite component?找到JSTL方法的一个例子。不,JSTL绝对不是一个“坏习惯”。这个神话是JSF 1.x时代的遗留问题,并且持续时间太长,因为初学者并没有清楚地理解JSTL的生命周期和权力。到目前为止,只有当#{bean.fields}
背后的模型在上面的代码片段中至少在JSF视图范围内没有改变时,才可以使用JSTL。另请参阅JSTL in JSF2 Facelets... makes sense?相反,将binding
用于bean属性仍然是一种“不好的做法”。
至于<ui:repeat><div>
,使用哪个迭代组件并不重要,你甚至可以在初始问题中使用<h:dataTable>
,或者使用组件库特定的迭代组件,例如<p:dataGrid>
或<p:dataList>
。 Refactor if necessary the big chunk of code to an include or tagfile。
至于收集提交的值,#{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
)
这保证了树在恰当的时刻填充,并保持getter没有业务逻辑,并且当#{bean}
在比请求范围更广的范围内时避免潜在的“重复组件ID”问题(因此您可以安全地使用例如视图这里的scoped bean),并保持bean没有UIComponent
属性,这反过来避免了当组件被保存为可序列化bean的属性时潜在的序列化问题和内存泄漏。
如果你仍然在JSF 1.x上<f:event>
不可用,而是通过binding
将表单组件绑定到请求(而不是session!)作用域bean
<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的属性。另见How does the 'binding' attribute work in JSF? When and how should it be used?每个选项的利弊都应该清楚。它从最简单和最好的可维护到最硬和最不可维护,从而也从最不可重复使用到最佳可重复使用。您可以选择最适合您的功能要求和当前情况的任何东西。
值得注意的是,绝对没有任何东西只能在Java(方式#2)中实现,而在XHTML + XML(方式#1)中是不可能的。 XHTML + XML中的一切都可以和Java一样好。许多初学者在动态创建组件时低估了XHTML + XML(特别是<ui:repeat>
和JSTL),并错误地认为Java将是“唯一的”方式,而这通常只会导致脆弱和混乱的代码。
如果原点是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
将persons.xhtml
转换为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}" />
...
提交的值将由Map
keys field1
,field2
,field3
等提供。