validar formulario dinamico jsf components facelets dynamic-forms

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.

  1. 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 atributo 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>

    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 el binding 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 un Map<String, Object> que ya está previamente creado. Un HashMap suficiente. Es posible que desee rellenar previamente el mapa en el caso de controles que pueden establecer múltiples valores. Debería rellenarlo con List<Object> como valor. Tenga en cuenta que espero que el Field#getType() sea ​​una enum ya que eso facilita el procesamiento en el lado del código Java. A continuación, puede usar una instrucción switch lugar de un desagradable bloque if/else .

  2. Cree los componentes mediante programación en un postAddToView eventos postAddToView :

    <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 es null )

    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 de UIComponent 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 un binding

    <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?

  3. 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.