2010-08-18 61 views
19

He encontrado algunas preguntas similares, como this, pero hay muchas maneras en que se puede hacer esto que me hizo más confundido.Cómo crear campos de formulario JSF dinámicos

Estamos obteniendo un archivo XML que estamos leyendo. Este XML contiene información sobre algunos campos de formulario que deben presentarse.

Así que creé esta costumbre DynamicField.java 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. 
} 

así que tenemos una List<DynamicField>.

Quiero recorrer esta lista y rellenar los campos del formulario por lo que se ve algo como esto:

<h:dataTable value="#{dynamicFields}" var="field"> 
    <my:someCustomComponent value="#{field}" /> 
</h:dataTable> 

El <my:someCustomComponent> sería entonces devolver los componentes de la forma de JSF apropiada (es decir, la etiqueta, inputText)

Otro enfoque sería mostrar el <my:someCustomComponent> y luego devolver un HtmlDataTable con elementos de 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".

Respuesta

51

Desde el origen es en realidad no XML, pero un JavaBean, y la otra respuesta no merece ser editada con un sabor totalmente diferente (aún puede ser útil para referencias futuras de otros), agregaré otra respuesta basada en un origen Javabean.


Veo básicamente tres opciones cuando el origen es un JavaBean.

  1. Hacer uso de JSF rendered atributo o incluso JSTL <c:choose>/<c:if> etiquetas para rendir condicional o construir el componente (s) deseada. A continuación se muestra un ejemplo usando rendered atributo:

    <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 de enfoque JSTL se puede encontrar en How to make a grid of JSF composite component? No, JSTL no es absolutamente 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. Consulte también JSTL in JSF2 Facelets... makes sense? En su lugar, usar binding en una propiedad de frijol sigue siendo una "mala práctica".

    En cuanto a la <ui:repeat><div>, realmente no importa lo que la iteración componente que utiliza, incluso se puede utilizar como <h:dataTable> en su pregunta inicial, o un componente de la iteración específica biblioteca de componentes, tales como <p:dataGrid> o <p:dataList>. Refactor if necessary the big chunk of code to an include or 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 preparado. Un HashMap es suficiente. Es posible que desee rellenar previamente el mapa en el caso de controles que pueden establecer múltiples valores. Debería rellenarlo con un valor de List<Object>. Tenga en cuenta que espero que el Field#getType() sea un enum, ya que eso facilita el procesamiento en el lado del código Java. A continuación, puede utilizar una instrucción switch en lugar de un desagradable bloque if/else.


  2. Crear los componentes mediante programación en un oyente postAddToView evento:

    <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 se crea el HtmlForm mismo utilizar el JSF-creado! uno, este nunca es null)

    Esto garantiza que el árbol se llena exactamente en el momento correcto y mantiene los getters libres de lógica comercial, y evita posibles problemas de "identificación de componentes duplicados" cuando el alcance #{bean} es más amplio que el alcance de la solicitud p.ejun view scoped bean aquí), y mantiene el bean libre de UIComponent propiedades que a su vez evita posibles problemas de serialización y pérdidas de memoria cuando el componente se mantiene como propiedad de un bean serializable.

    Si todavía estás en 1.x JSF donde <f:event> no está disponible, en lugar de enlazar el componente formulario para una solicitud (no sesión!) De ámbito de frijol a través de binding

    <h:form id="form" binding="#{bean.form}" /> 
    

    Y luego perezosamente en poblarlo el captador de la forma:

    public HtmlForm getForm() { 
        if (form == null) { 
         form = new HtmlForm(); 
         // ... (continue with code as above) 
        } 
        return form; 
    } 
    

    al utilizar binding, es muy importante entender que los componentes de interfaz de usuario son básicamente ámbito de petición y deben ser asignados en absoluto como una propiedad de un grano en un ámbito más amplio. Ver también How does the 'binding' attribute work in JSF? When and how should it be used?


  3. Crear un componente personalizado con un intérprete 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.


pros y los contras de cada opción debe ser clara. 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.

anotados deben ser que no hay absolutamente nada que es única posible en Java (Camino # 2) e imposible en XHTML + XML (Camino # 1). Todo es posible tanto en XHTML + XML como en Java. Muchos 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 la única forma, mientras que generalmente solo termina en un código frágil y confuso.

+1

Hay una cuarta alternativa: componente de extensión PrimeFaces: DynaForm (http://www.primefaces.org/showcase-ext/views/home.jsf). Esto tiene algunas limitaciones, pero será suficiente para la mayoría de los usuarios. –

+0

Hola BalusC, soy un gran admirador de tu. He estado aprendiendo a través de sus respuestas y necesito su identificación de correo para tener un poco de discusión sobre un problema que estoy enfrentando ahora. Por favor envíeme su identificación a [email protected] –

15

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 poco decente Filter que se activa 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 la que se asigna el <servlet-name> del FacesServlet y asume que el FacesServlet sí se mapea en una <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); 
} 

Dirigido por http://example.com/context/persons.jsf y este filtro entrará en funcionamiento y transformar persons.xml a persons.xhtml usando persons.xsl y finalmente pusieron persons.xhtml allí donde JSF es esperar.

Cierto, 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, solo use un Map<String, Object>.Si nombra a los campos de entrada como de manera

<h:inputText value="#{bean.map.field1}" /> 
<h:inputText value="#{bean.map.field2}" /> 
<h:inputText value="#{bean.map.field3}" /> 
... 

Los valores presentados estarán disponibles por Map teclas field1, field2, field3, etc.

+0

Hola @BalusC. Gracias por una respuesta extensa. Sin embargo, no estoy seguro si puedo beneficiarme de esto con nuestro modelo actual. Sí, estamos obteniendo los datos a través de XML, sin embargo, ya es a través de Smooks transferidos a un JavaBean (xml2Java). Así que no estoy seguro de poder hacer lo que sugiere aquí ... –

+0

¿Es obligatorio almacenar 'persons.xml' y' persons.xsl' en esta ruta - '.getRealPath ("/")'? Cuando intento mover esos archivos a '.getRealPath ("/public_resources/xsl_xml ")' (junto con ''), se queja: 'El contenido no está permitido en Prolog': el archivo XHTML generado ya no está bien formateado. – Tiny

+1

@Tiny '' debe representar la estructura XML, no la ruta donde está el archivo XML. No lo cambie. – BalusC

Cuestiones relacionadas