Как создать динамические поля формы JSF
Я нашел несколько похожих вопросов, таких как этот, однако есть так много способов, которыми это можно сделать, и это еще больше смутило меня.
Мы получаем 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
".
2 ответа
Поскольку источником на самом деле является не XML, а 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. Кстати, вы можете использовать JSTL только тогда, когда модель позади
#{bean.fields}
как в приведенном выше фрагменте, никогда не изменяется во время, по крайней мере, области просмотра JSF. Смотрите также JSTL в JSF2 Facelets... имеет смысл? Вместо этого, используяbinding
для свойства боба все еще "плохая практика".Что касается
<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}
находится в более широкой области видимости, чем область запроса (так что вы можете безопасно использовать, например, здесь компонент с областью видимости), и сохраняет компонент свободным отUIComponent
свойства, которые, в свою очередь, позволяют избежать потенциальных проблем сериализации и утечки памяти, когда компонент содержится в качестве свойства сериализуемого компонента.Если вы все еще на JSF 1.x где
<f:event>
недоступен, вместо этого связывайте компонент формы с bean-компонентом области действия запроса (не сеанса!) черезbinding
<h:form id="form" binding="#{bean.form}" />
А потом лениво заселить его в получатель вида
public HtmlForm getForm() { if (form == null) { form = new HtmlForm(); // ... (continue with code as above) } return form; }
Когда используешь
binding
, очень важно понимать, что компоненты пользовательского интерфейса в основном ограничены запросами и не должны назначаться в качестве свойства компонента в более широкой области. Смотрите также Как работает атрибут 'binding' в JSF? Когда и как его следует использовать?Создайте пользовательский компонент с пользовательским средством визуализации. Я не собираюсь публиковать полные примеры, так как это большой код, который, в конце концов, будет очень тесно связан и связан с конкретным приложением.
Плюсы и минусы каждого варианта должны быть понятны. Он переходит от наиболее простого и лучшего в обслуживании к наиболее сложному и наименее обслуживаемому, а впоследствии также от наименее повторного к лучшему повторному использованию. Вы можете выбрать все, что лучше всего соответствует вашим функциональным требованиям и текущей ситуации.
Следует отметить, что нет абсолютно ничего, что возможно только в 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.
Чтобы сделать отображение между формой и управляемым бином, просто используйте Map<String, Object>
, Если вы называете поля ввода примерно так
<h:inputText value="#{bean.map.field1}" />
<h:inputText value="#{bean.map.field2}" />
<h:inputText value="#{bean.map.field3}" />
...
Представленные значения будут доступны Map
ключи field1
, field2
, field3
, так далее.