jsf - validar - formulario dinamico primefaces
Cómo crear campos de formulario JSF dinámicos (2)
Si el origen es XML, sugiero adoptar un enfoque completamente diferente: XSL . Facelets está basado en XHTML. Puede usar XSL fácilmente para ir de XML a XHTML. Esto es factible con un Filter
decente que entra en acción antes de que JSF haga los trabajos.
Aquí hay un ejemplo de inicio.
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
que está mapeado en <servlet-name>
del FacesServlet
y asume que el propio FacesServlet
está mapeado en un <url-pattern>
de *.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);
}
Ejecutar por http://example.com/context/persons.jsf y este filtro se iniciará y transformará persons.xml
en persons.xhtml
utilizando persons.xsl
y finalmente pondrá persons.xhtml
allí donde JSF espera que esté.
Es cierto que XSL tiene un poco de curva de aprendizaje, pero IMO es la herramienta adecuada para el trabajo, ya que la fuente es XML y el destino también está basado en XML.
Para hacer la asignación entre el formulario y el bean administrado, simplemente use un Map<String, Object>
. Si nombra los campos de entrada como tal
<h:inputText value="#{bean.map.field1}" />
<h:inputText value="#{bean.map.field2}" />
<h:inputText value="#{bean.map.field3}" />
...
Los valores enviados estarán disponibles mediante las teclas del Map
field1
, field2
, field3
, etc.
He encontrado algunas preguntas similares a esta , sin embargo, hay muchas maneras en que se puede hacer esto que me confundió más.
Estamos obteniendo un archivo XML
que estamos leyendo. Este XML
contiene información sobre algunos campos de formulario que debe presentarse.
Así que creé este DynamicField.java
personalizado que tiene toda la información que necesitamos:
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.
}
Entonces tenemos una List<DynamicField>
.
Quiero iterar a través de esta lista y rellenar los campos del formulario para que se vea así:
<h:dataTable value="#{dynamicFields}" var="field">
<my:someCustomComponent value="#{field}" />
</h:dataTable>
El <my:someCustomComponent>
devolvería los componentes de formulario JSF apropiados (es decir, label, inputText)
Otro enfoque sería mostrar el <my:someCustomComponent>
y luego eso devolvería una HtmlDataTable
con los elementos del formulario. (Creo que esto es quizás más fácil de hacer).
¿Qué enfoque es el mejor? ¿Puede alguien mostrarme algunos enlaces o códigos donde se muestra cómo puedo crear esto? Prefiero ejemplos completos de código y no respuestas como "Necesita una subclase de javax.faces.component.UIComponent
".
Dado que el origen no es en realidad XML, sino Javabean, y la otra respuesta no merece editarse en un sabor totalmente diferente (aún puede ser útil para futuras referencias de otros), agregaré otra respuesta basada en un Javabean-origen.
Veo básicamente tres opciones cuando el origen es Javabean.
Utilice el atributo
rendered
JSF o incluso las etiquetas JSTL<c:choose>
/<c:if>
para representar o construir condicionalmente los componentes deseados. A continuación se muestra un ejemplo con el atributorendered
:<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>
Un ejemplo del enfoque JSTL se puede encontrar en Cómo hacer una grilla del componente compuesto JSF? No, JSTL definitivamente no es una "mala práctica". Este mito es un remanente de la era JSF 1.x y continúa demasiado tiempo porque los principiantes no entendieron claramente el ciclo de vida y los poderes de JSTL. Hasta el punto, puede usar JSTL solo cuando el modelo detrás de
#{bean.fields}
como en el fragmento de arriba no cambia durante al menos el alcance de la vista JSF. Ver también JSTL en JSF2 Facelets ... tiene sentido? En cambio, utilizar elbinding
a una propiedad de frijol sigue siendo una "mala práctica".En cuanto a
<ui:repeat><div>
, realmente no importa qué componente de iteración usa, incluso puede usar<h:dataTable>
como en su pregunta inicial, o un componente iterativo específico de una biblioteca de componentes, como<p:dataGrid>
o<p:dataList>
. Refactor, si es necesario, la gran parte del código a un include o tagfile .En cuanto a la recopilación de los valores enviados, el
#{bean.values}
debe apuntar a unMap<String, Object>
que ya está previamente creado. UnHashMap
suficiente. Es posible que desee rellenar previamente el mapa en el caso de controles que pueden establecer múltiples valores. Debería rellenarlo conList<Object>
como valor. Tenga en cuenta que espero que elField#getType()
sea unaenum
ya que eso facilita el procesamiento en el lado del código Java. A continuación, puede usar una instrucciónswitch
lugar de un desagradable bloqueif/else
.Cree los componentes mediante programación en un
postAddToView
eventospostAddToView
:<h:form id="form"> <f:event type="postAddToView" listener="#{bean.populateForm}" /> </h:form>
Con:
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... } } }
(Nota: ¡NO cree
HtmlForm
usted mismo! Utilice el creado por JSF, este nunca esnull
)Esto garantiza que el árbol esté completo exactamente en el momento correcto, mantiene los buscadores libres de lógica comercial y evita posibles problemas de "identificación de componentes duplicados" cuando
#{bean}
tiene un alcance más amplio que el alcance de la solicitud (para que pueda usar de forma segura por ejemplo, una vista de bean con ámbito aquí), y mantiene el bean libre de propiedades deUIComponent
que a su vez evita problemas potenciales de serialización y pérdida de memoria cuando el componente se mantiene como propiedad de un bean serializable.Si todavía está en JSF 1.x donde
<f:event>
no está disponible, en lugar de ello, vincule el componente de formulario a un bean con ámbito de solicitud (¡no sesión!) A través de unbinding
<h:form id="form" binding="#{bean.form}" />
Y luego poblarlo perezoso en el getter de la forma:
public HtmlForm getForm() { if (form == null) { form = new HtmlForm(); // ... (continue with code as above) } return form; }
Cuando se utiliza el
binding
, es muy importante comprender que los componentes de la interfaz de usuario son básicamente de ámbito de solicitud y no deben asignarse como una propiedad de un bean en un ámbito más amplio. Ver también ¿Cómo funciona el atributo ''vinculante'' en JSF? ¿Cuándo y cómo se debe usar?Crea un componente personalizado con un renderizador personalizado. No voy a publicar ejemplos completos, ya que es un montón de código que, después de todo, sería un desastre muy ajustado y específico de la aplicación.
Los pros y los contras de cada opción deben ser claros. Va desde lo más fácil y lo más fácil de mantener hasta lo más difícil y lo menos sostenible, y también desde lo menos reutilizable hasta lo mejor reutilizable. Depende de usted elegir lo que mejor se adapte a sus necesidades funcionales y su situación actual.
Notado debe ser que no hay absolutamente nada que solo sea posible en Java (modo # 2) e imposible en XHTML + XML (modo # 1). Todo es posible tanto en XHTML + XML como en Java. Muchos de los principiantes subestiman XHTML + XML (particularmente <ui:repeat>
y JSTL) en la creación dinámica de componentes y piensan erróneamente que Java sería el "único" camino, mientras que generalmente solo termina en un código quebradizo y confuso.