2010-08-18 18 views
19

Ich habe einige ähnliche Fragen wie this gefunden, aber es gibt so viele Möglichkeiten, dass dies getan werden kann, dass es mich mehr verwirrt.So erstellen Sie dynamische JSF-Formularfelder

Wir bekommen eine XML Datei, die wir lesen. Diese XML enthält Informationen zu einigen Formularfeldern, die dargestellt werden müssen.

Also habe ich diese Gewohnheit DynamicField.java, die alle Informationen hat, die wir brauchen:

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

So haben wir eine List<DynamicField>.

Ich möchte durch diese Liste zu durchlaufen und die Formularfelder zu füllen, so dass es so etwas wie folgt aussieht:

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

Die <my:someCustomComponent> würde dann wieder die entsprechenden JSF Formularkomponenten (dh Etikett, input)

Ein anderer Ansatz wäre, nur die <my:someCustomComponent> anzuzeigen, und dann würde das eine HtmlDataTable mit Formularelementen zurückgeben. (Ich denke, das ist vielleicht einfacher zu tun).

Welcher Ansatz ist am besten? Kann mir jemand Links oder Code zeigen wo es zeigt wie ich das erstellen kann? Ich bevorzuge vollständige Codebeispiele und keine Antworten wie "Sie benötigen eine Unterklasse von javax.faces.component.UIComponent".

Antwort

51

Da der Ursprung tatsächlich nicht XML, sondern ein Javabean, und die andere Antwort verdient es nicht, in eine völlig andere Art und Weise bearbeitet zu werden (sie kann immer noch für zukünftige Referenzen von anderen nützlich sein), ich werde eine weitere Antwort basierend auf einem Javabean-Ursprung hinzufügen.


Ich sehe grundsätzlich drei Möglichkeiten, wenn der Ursprung ein Javabean ist.

  1. Nutzen Sie JSF rendered Attribut oder sogar JSTL <c:choose>/<c:if> Tags bedingt machen oder die gewünschte Komponente (n) bauen. Unten ist ein Beispiel unter Verwendung von rendered Attribute:

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

    Ein Beispiel für JSTL Ansatz kann Nein, JSTL ist absolut keine „schlechte Praxis“ bei How to make a grid of JSF composite component? finden. Dieser Mythos ist ein Überbleibsel aus der JSF 1.x-Ära und dauert zu lange, weil die Starter den Lebenszyklus und die Fähigkeiten von JSTL nicht klar verstanden haben. Zu diesem Punkt können Sie JSTL nur verwenden, wenn sich das Modell hinter #{bean.fields} wie in obigem Snippet während mindestens des JSF-Ansichtsbereichs nicht ändert. Siehe auch JSTL in JSF2 Facelets... makes sense? Stattdessen ist die Verwendung von binding zu einer Bean-Eigenschaft immer noch eine "schlechte Praxis".

    In Bezug auf die <ui:repeat><div>, es spielt keine Rolle, welche Komponente Iterieren Sie verwenden, können Sie auch <h:dataTable> als in Ihrer ersten Frage, oder eine Komponentenbibliothek spezifische Iterieren Komponente, wie <p:dataGrid> oder <p:dataList> verwenden können. Refactor if necessary the big chunk of code to an include or tagfile.

    Um die übergebenen Werte zu sammeln, sollte der #{bean.values} auf einen Map<String, Object> zeigen, der bereits vorproduziert ist. Eine HashMap genügt. Möglicherweise möchten Sie die Map im Falle von Steuerelementen, die mehrere Werte festlegen können, vorbelegen. Sie sollten es dann mit einem List<Object> als Wert vorbefüllen. Beachten Sie, dass ich die Field#getType() als enum erwarte, da dies die Verarbeitung auf der Java-Codepage erleichtert. Sie können dann eine switch Anweisung anstelle eines unangenehmen if/else Blocks verwenden.


  2. Erstellen Sie die Komponenten programmatisch in einem postAddToView Ereignis-Listener:

    <h:form id="form"> 
        <f:event type="postAddToView" listener="#{bean.populateForm}" /> 
    </h:form> 
    

    mit:

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

    (Anmerkung: erzeugen Sie die HtmlForm sich die JSF-created nutzen! eins, das ist nie null)

    Dies garantiert, dass der Baum genau zum richtigen Zeitpunkt gefüllt wird, und hält Getter frei von Geschäftslogik und vermeidet mögliche Probleme mit "doppelten Komponenten-IDs", wenn #{bean} in einem breiteren Umfang als der Anforderungsbereich liegt (damit Sie sicher verwenden können) z.B(hier eine Sichtbereichs-Bean), und hält die Bean frei von Eigenschaften, was wiederum mögliche Serialisierungsstörungen und Speicherlecks vermeidet, wenn die Komponente als eine Eigenschaft einer serialisierbaren Bean gehalten wird.

    Wenn Sie immer noch auf JSF 1.x sind, wo <f:event> nicht verfügbar ist, binden, anstatt die Form Komponente auf eine Anfrage (nicht Session!) Scoped Bohne über binding

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

    Und dann gemächlich bevölkert in der Getter der Form:

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

    Wenn binding verwendet wird, ist es sehr wichtig, dass die UI-Komponenten grundsätzlich anfordern scoped zu verstehen und sollen auf keinen Fall als eine Eigenschaft einer Bean in einem breiteren Bereich zugewiesen werden. Siehe auch How does the 'binding' attribute work in JSF? When and how should it be used?


  3. Erstellen Sie eine benutzerdefinierte Komponente mit einem benutzerdefinierten Renderer. Ich werde keine vollständigen Beispiele veröffentlichen, da das eine Menge Code ist, der schließlich ein sehr eng gekoppeltes und anwendungsspezifisches Chaos ist.


Vor- und Nachteile der einzelnen Optionen sollte klar sein. Es geht von den einfachsten und am besten wartbaren zu den meisten harten und am wenigsten zu wartenden und in der Folge auch von den am wenigsten wiederverwendbaren zu den besten, die wiederverwendbar sind. Es liegt an Ihnen, das zu wählen, was am besten zu Ihren funktionellen Anforderungen und der aktuellen Situation passt.

sollte beachtet werden, dass es absolut nichts ist die nur möglich in Java (Art # 2) und nicht in XHTML + XML (Art und Weise # 1) ist. In XHTML + XML ist alles so gut wie in Java möglich. Viele Starter unterschätzen XHTML + XML (insbesondere <ui:repeat> und JSTL) bei der dynamischen Erstellung von Komponenten und glauben fälschlicherweise, dass Java der "einzige" Weg wäre, während dies im Allgemeinen nur in sprödem und verwirrendem Code endet.

+1

Es gibt eine vierte Alternative: PrimeFaces Erweiterung Komponente: DynaForm (http://www.primefaces.org/showcase-ext/views/home.jsf). Dies hat einige Einschränkungen, wird aber für die meisten Benutzer ausreichen. –

+0

Hallo BalusC, ich bin ein großer Fan von dir. Ich habe durch Ihre Antworten gelernt, und ich brauche Ihre E-Mail-ID, um eine Diskussion über ein Problem zu führen, mit dem ich jetzt konfrontiert bin. Bitte mailen Sie mir Ihre ID an [email protected] –

15

Wenn der Ursprung XML ist, schlage ich vor, einen völlig anderen Ansatz zu wählen: XSL. Facelets basiert auf XHTML. Sie können XSL einfach verwenden, um von XML zu XHTML zu wechseln. Dies ist machbar mit ein wenig anständigen Filter, die beginnt, bevor JSF die Arbeiten macht.

Hier ist ein Kickoff-Beispiel.

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 die auf <servlet-name> des FacesServlet abgebildet wird, und geht davon aus, dass die sich auf einem FacesServlet<url-pattern> von *.jsf abgebildet wird.

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); 
} 

Geführt von http://example.com/context/persons.jsf und dieser Filter treten in und verwandeln persons.xml zu persons.xhtmlpersons.xsl mit und setzen schließlich persons.xhtml dort, wo JSF erwarten ist.

Wahr, XSL hat ein bisschen Lernkurve, aber es ist IMO das richtige Werkzeug für den Job, da die Quelle XML und Ziel ist XML-basiert als auch.

Um die Zuordnung zwischen dem Formular und der verwalteten Bean durchzuführen, verwenden Sie einfach Map<String, Object>.Wenn Sie die Eingabefelder wie Name so

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

Die vorgelegten Werte field1 von Map Tasten zur Verfügung, field2, field3 usw.

+0

Hallo @BalusC. Danke für eine ausführliche Antwort. Ich bin mir jedoch nicht sicher, ob ich mit unserem aktuellen Modell davon profitieren kann. Ja, wir bekommen die Daten über XML, aber es wird bereits über Smooks auf eine JavaBean (xml2Java) übertragen. Also ich bin mir nicht sicher, ob ich das tun kann, was Sie hier vorschlagen ... –

+0

Ist es zwingend notwendig 'personen.xml' und' personen.xsl' in diesem Pfad zu speichern - '.getRealPath ("/")'? Wenn ich versuche, diese Dateien in '.getRealPath ("/public_resources/xsl_xml ")' zu verschieben (''), beschwert es sich: 'Inhalt ist nicht erlaubt in Prolog - die generierte XHTML-Datei ist nicht mehr gut formatiert. – Tiny

+1

@Tiny '' muss die XML-Struktur darstellen, nicht den Pfad, in dem sich die XML-Datei befindet. Ändern Sie sie nicht. – BalusC