我发现了类似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
的子类”这样的答案。
答案 0 :(得分:54)
由于起源实际上不是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方法的一个例子可以在How to make a grid of JSF composite component?找到。不,JSTL绝对不是一个“坏习惯”。这个神话是JSF 1.x时代的遗留问题,并且持续时间太长,因为初学者并没有清楚地理解JSTL的生命周期和权力。到目前为止,只有在上述代码段中#{bean.fields}
后面的模型在至少JSF视图范围内不会发生变化时,才能使用JSTL。另请参阅JSTL in JSF2 Facelets... makes sense?相反,将binding
用于bean属性仍然是一种“不良做法”。
对于<ui:repeat><div>
,使用哪个迭代组件并不重要,您甚至可以在初始问题中使用<h:dataTable>
,或者使用组件库特定的迭代组件,例如{ {1}}或<p:dataGrid>
。 Refactor if necessary the big chunk of code to an include or tagfile
关于收集提交的值,<p:dataList>
应指向已预先创建的#{bean.values}
。 Map<String, Object>
就足够了。如果控件可以设置多个值,您可能需要预先填充地图。然后,您应该使用HashMap
作为值进行预填充。请注意,我希望List<Object>
为Field#getType()
,因为这样可以简化Java代码方面的处理。然后,您可以使用enum
语句而不是讨厌的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...
}
}
}
!使用JSF创建的一个,这个永远不会HtmlForm
)
这保证了树在恰当的时刻填充,并保持getter没有业务逻辑,并且当null
在比请求范围更广的范围内时避免潜在的“重复组件ID”麻烦(所以你可以安全地使用例如这里的视图范围bean),并保持bean没有#{bean}
属性,这反过来避免了当组件被保存为可序列化bean的属性时潜在的序列化问题和内存泄漏。
如果您仍然使用UIComponent
不可用的JSF 1.x,而是通过<f:event>
binding
然后在表格的吸气器中懒洋洋地填充它:
<h:form id="form" binding="#{bean.form}" />
使用public HtmlForm getForm() {
if (form == null) {
form = new HtmlForm();
// ... (continue with code as above)
}
return form;
}
时,了解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(特别是binding
和JSTL),并错误地认为Java将是“唯一的”方式,而这通常只会导致脆弱和混乱的代码。< / p>
答案 1 :(得分:16)
如果原点是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>
的{{1}},并假设FacesServlet
本身映射到FacesServlet
<url-pattern>
。
*.jsf
按http://example.com/context/persons.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);
}
将persons.xml
转换为persons.xhtml
,最后将persons.xsl
放在JSF预期的位置。
是的,XSL有一点学习曲线,但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
等提供。