2010-08-18 34 views
19

Tôi đã tìm thấy một số câu hỏi tương tự như this một, tuy nhiên có rất nhiều cách để làm điều này khiến tôi bối rối hơn.Cách tạo các trường biểu mẫu JSF động

Chúng tôi đang nhận tệp XML mà chúng tôi đang đọc. XML này chứa thông tin về một số trường biểu mẫu cần được trình bày.

Vì vậy, tôi đã tạo ra tùy chỉnh này DynamicField.java có tất cả các thông tin chúng ta cần:

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

Vì vậy, chúng tôi có một List<DynamicField>.

Tôi muốn lặp thông qua danh sách này và cư các trường mẫu để nó trông giống như sau:

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

Các <my:someCustomComponent> sau đó sẽ trả lại thành phần biểu mẫu JSF thích hợp (tức là nhãn, inputText)

Một cách tiếp cận khác là chỉ hiển thị <my:someCustomComponent> và sau đó sẽ trả lại một HtmlDataTable với các phần tử biểu mẫu. (Tôi nghĩ rằng điều này có thể dễ dàng hơn để làm).

Phương pháp nào là tốt nhất? Ai đó có thể chỉ cho tôi một số liên kết hoặc mã nơi nó cho thấy cách tôi có thể tạo ra điều này? Tôi thích các ví dụ mã hoàn chỉnh hơn và không phải là câu trả lời như "Bạn cần một lớp con của javax.faces.component.UIComponent".

Trả lời

51

Kể từ nguồn gốc thực sự không phải là XML, nhưng một JavaBean, và câu trả lời khác không xứng đáng được chỉnh sửa thành một hương vị hoàn toàn khác (nó có thể vẫn hữu ích cho những người khác tham khảo trong tương lai), tôi sẽ thêm một câu trả lời khác dựa trên nguồn gốc Javabean.


tôi thấy về cơ bản ba lựa chọn khi nguồn gốc là một JavaBean.

  1. Make sử dụng JSF rendered thuộc tính hoặc thậm chí JSTL <c:choose>/<c:if> thẻ để có điều kiện làm hoặc xây dựng các thành phần mong muốn (s). Dưới đây là một ví dụ sử dụng rendered thuộc tính:

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

    Một ví dụ về cách tiếp cận JSTL có thể được tìm thấy tại How to make a grid of JSF composite component? Không, JSTL là hoàn toàn không phải là một "thói quen xấu". Huyền thoại này còn sót lại từ thời kỳ JSF 1.x và tiếp tục quá lâu bởi vì những người mới bắt đầu không hiểu rõ vòng đời và sức mạnh của JSTL. Về điểm này, bạn chỉ có thể sử dụng JSTL khi mô hình phía sau #{bean.fields} như trong đoạn mã trên không bao giờ thay đổi trong phạm vi ít nhất là phạm vi xem JSF. Xem thêm JSTL in JSF2 Facelets... makes sense? Thay vào đó, sử dụng binding đối với thuộc tính bean vẫn là "thực hành không tốt".

    Đối với các <ui:repeat><div>, nó thực sự không quan trọng mà lặp lại thành phần mà bạn sử dụng, bạn thậm chí có thể sử dụng <h:dataTable> như trong câu hỏi ban đầu của bạn, hoặc một thư viện thành phần phần iterating cụ thể, chẳng hạn như <p:dataGrid> hoặc <p:dataList>. Refactor if necessary the big chunk of code to an include or tagfile.

    Để thu thập các giá trị đã gửi, #{bean.values} phải trỏ đến Map<String, Object> đã được xử lý trước. A HashMap đủ. Bạn có thể muốn chuẩn bị trước bản đồ trong trường hợp các điều khiển có thể đặt nhiều giá trị. Sau đó, bạn nên chuẩn bị trước nó với giá trị List<Object>. Lưu ý rằng tôi mong đợi Field#getType()enum vì điều đó giúp giảm bớt việc xử lý ở phía mã Java. Sau đó, bạn có thể sử dụng câu lệnh switch thay vì một khối if/else khó chịu.


  2. Tạo các thành phần lập trình trong một listener postAddToView sự kiện:

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

    Với:

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

    (Lưu ý: Không tạo HtmlForm mình sử dụng JSF tạo! một, cái này không bao giờ là null)

    Điều này đảm bảo rằng cây được điền chính xác vào đúng thời điểm và giữ getters không có logic nghiệp vụ và tránh được sự cố "ID thành phần trùng lặp" khi #{bean} nằm trong phạm vi rộng hơn phạm vi yêu cầu (để bạn có thể sử dụng an toàn ví dụmột bean có phạm vi xem ở đây), và giữ cho bean không bị mất các thuộc tính UIComponent mà tránh được sự cố serialization tiềm ẩn và rò rỉ bộ nhớ khi thành phần được giữ như một thuộc tính của một bean có thể tuần tự hóa.

    Nếu bạn vẫn còn trên 1.x JSF nơi <f:event> không có sẵn, thay vì ràng buộc các thành phần hình thức cho một yêu cầu (không phiên!) Scoped đậu qua binding

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

    Và rồi uể oải cư nó trong getter có dạng:

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

    Khi sử dụng binding, nó rất quan trọng để hiểu rằng các thành phần giao diện người dùng về cơ bản yêu cầu những scoped và nên tuyệt đối không được chỉ định như một tài sản của một bean trong một phạm vi rộng lớn hơn. Xem thêm How does the 'binding' attribute work in JSF? When and how should it be used?


  3. Tạo thành phần tùy chỉnh với trình kết xuất tùy chỉnh. Tôi sẽ không đăng ví dụ hoàn chỉnh vì đó là rất nhiều mã mà sau khi tất cả là một mớ hỗn độn rất chặt chẽ và ứng dụng cụ thể.


Ưu điểm và nhược điểm của mỗi phương án phải rõ ràng. Nó đi từ dễ dàng nhất và tốt nhất để duy trì hầu hết các khó khăn và ít nhất là duy trì và sau đó cũng từ ít nhất reuseable tốt nhất reuseable. Đó là vào bạn để chọn bất cứ điều gì phù hợp nhất với yêu cầu chức năng của bạn và tình hình hiện tại.

Ghi nhận nên có là hoàn toàn không có gì đó là chỉ thể trong Java (2 chiều #) và không thể trong XHTML + XML (cách # 1). Mọi thứ đều có thể có trong XHTML + XML tốt như trong Java. Rất nhiều người mới bắt đầu đánh giá thấp XHTML + XML (đặc biệt là <ui:repeat> và JSTL) trong việc tạo động các thành phần và không nghĩ rằng Java sẽ là cách "một và duy nhất", trong khi thường chỉ kết thúc bằng mã dễ vỡ và khó hiểu.

+1

Có giải pháp thay thế thứ 4: Thành phần mở rộng PrimeFaces: DynaForm (http://www.primefaces.org/showcase-ext/views/home.jsf). Điều này có một số hạn chế, nhưng sẽ là đủ cho hầu hết người dùng. –

+0

Xin chào BalusC, tôi là một fan hâm mộ lớn của bạn. Tôi đã học qua câu trả lời của bạn và tôi cần Id thư của bạn để có một số cuộc thảo luận về một vấn đề mà tôi đang phải đối mặt bây giờ. Xin vui lòng gửi cho tôi id của bạn tại [email protected] –

15

Nếu nguồn gốc là XML, tôi đề xuất đi theo một cách tiếp cận hoàn toàn khác: XSL. Facelets dựa trên XHTML. Bạn có thể dễ dàng sử dụng XSL để chuyển từ XML sang XHTML. Điều này là doable với một chút phong nha Filter mà đá trước khi JSF đang làm các công trình.

Đây là ví dụ về khởi động.

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 được ánh xạ trên <servlet-name> của FacesServlet và giả định rằng các FacesServlet bản thân được ánh xạ vào một <url-pattern> của *.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); 
} 

Run bởi http://example.com/context/persons.jsf và bộ lọc này sẽ kick vào và chuyển đổi persons.xml để persons.xhtml sử dụng persons.xsl và cuối cùng đưa persons.xhtml có nơi JSF mong đợi nó được.

Đúng, XSL có một chút đường cong học tập, nhưng IMO là công cụ thích hợp cho công việc vì nguồn là XML và đích là XML dựa trên dạng wel.

Để thực hiện ánh xạ giữa biểu mẫu và bean được quản lý, chỉ cần sử dụng Map<String, Object>.Nếu bạn đặt tên cho các lĩnh vực đầu vào như vậy

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

Các giá trị nộp sẽ có sẵn bằng Map phím field1, field2, field3 vv

+0

Xin chào @BalusC. Cảm ơn câu trả lời mở rộng. Tuy nhiên, tôi không chắc liệu tôi có thể hưởng lợi từ điều này với mô hình hiện tại của chúng tôi hay không. Có, chúng tôi đang nhận được dữ liệu thông qua XML, tuy nhiên nó đã được thông qua Smooks chuyển giao cho một JavaBean (xml2Java). Vì vậy, tôi không chắc chắn tôi có thể làm những gì bạn đề nghị ở đây ... –

+0

Có bắt buộc phải lưu 'person.xml' và' persons.xsl' vào đường dẫn này - '.getRealPath ("/")'? Khi tôi cố chuyển các tệp đó vào '.getRealPath ("/public_resources/xsl_xml ")' (cùng với ''), nó than phiền, 'Nội dung không được phép trong Prolog' - tệp XHTML được tạo không còn được định dạng tốt nữa. – Tiny

+1

@Tiny '' phải đại diện cho cấu trúc XML, không phải đường dẫn nơi tệp XML đang ở. Không thay đổi nó. – BalusC

Các vấn đề liên quan