2013-03-22 21 views
6

Khi cố gắng triển khai thuộc tính html5 đơn giản 'tự động lấy nét' trong ứng dụng web JSF/Primefaces của tôi, tôi được cảnh báo rằng các thành phần không vượt qua tất cả các thuộc tính không xác định vào đánh dấu cuối cùng. Tôi có thể hiểu lý do cho điều này, vì các thành phần có thể là sự kết hợp phức tạp của đánh dấu html và nó sẽ không rõ ràng nơi để đặt các thuộc tính nếu chúng chưa được xác định rõ bởi thành phần. Tuy nhiên, giải pháp tốt nhất cho tôi là có hỗ trợ lấy nét tự động (và bất kỳ loại thuộc tính có thể nào khác mà tôi có thể muốn hỗ trợ trong ứng dụng của tôi mà các nguyên tố chưa được xác định). Các tính năng chínhThêm hỗ trợ thuộc tính tùy chỉnh (HTML5) vào Primefaces (3.4)

Tôi đã xem Adding custom attribute (HTML5) support to JSF 2.0 UIInput component, nhưng điều đó dường như áp dụng cho các thành phần JSF cơ bản và không hoạt động đối với các thành phần PrimeFaces.

Làm cách nào để mở rộng thành phần/hiển thị của Primefaces để hỗ trợ điều này?

Trả lời

10

Thay vì homegrowing một renderer tùy chỉnh cho mỗi thành phần cá nhân duy nhất, bạn cũng có thể chỉ cần tạo một đĩa đơn RenderKit trong đó bạn cung cấp một tùy chỉnh ResponseWriter trong đó phương pháp startElement() là overriden để kiểm tra tên phần tử và/hoặc trường hợp thành phần và sau đó viết thêm các thuộc tính cho phù hợp.

Dưới đây là một ví dụ Kickoff của bộ HTML5 render:

public class Html5RenderKit extends RenderKitWrapper { 

    private RenderKit wrapped; 

    public Html5RenderKit(RenderKit wrapped) { 
     this.wrapped = wrapped; 
    } 

    @Override 
    public ResponseWriter createResponseWriter(Writer writer, String contentTypeList, String characterEncoding) { 
     return new Html5ResponseWriter(super.createResponseWriter(writer, contentTypeList, characterEncoding)); 
    } 

    @Override 
    public RenderKit getWrapped() { 
     return wrapped; 
    } 

} 

HTML5 nhà văn phản ứng:

public class Html5ResponseWriter extends ResponseWriterWrapper { 

    private static final String[] HTML5_INPUT_ATTRIBUTES = { "autofocus" }; 

    private ResponseWriter wrapped; 

    public Html5ResponseWriter(ResponseWriter wrapped) { 
     this.wrapped = wrapped; 
    } 

    @Override 
    public ResponseWriter cloneWithWriter(Writer writer) { 
     return new Html5ResponseWriter(super.cloneWithWriter(writer)); 
    } 

    @Override 
    public void startElement(String name, UIComponent component) throws IOException { 
     super.startElement(name, component); 

     if ("input".equals(name)) { 
      for (String attributeName : HTML5_INPUT_ATTRIBUTES) { 
       String attributeValue = component.getAttributes().get(attributeName); 

       if (attributeValue != null) { 
        super.writeAttribute(attributeName, attributeValue, null); 
       } 
      } 
     } 
    } 

    @Override 
    public ResponseWriter getWrapped() { 
     return wrapped; 
    } 

} 

Để làm cho nó chạy, tạo HTML5 làm cho nhà máy này kit:

public class Html5RenderKitFactory extends RenderKitFactory { 

    private RenderKitFactory wrapped; 

    public Html5RenderKitFactory(RenderKitFactory wrapped) { 
     this.wrapped = wrapped; 
    } 

    @Override 
    public void addRenderKit(String renderKitId, RenderKit renderKit) { 
     wrapped.addRenderKit(renderKitId, renderKit); 
    } 

    @Override 
    public RenderKit getRenderKit(FacesContext context, String renderKitId) { 
     RenderKit renderKit = wrapped.getRenderKit(context, renderKitId); 
     return (HTML_BASIC_RENDER_KIT.equals(renderKitId)) ? new Html5RenderKit(renderKit) : renderKit; 
    } 

    @Override 
    public Iterator<String> getRenderKitIds() { 
     return wrapped.getRenderKitIds(); 
    } 

} 

Và đăng ký như sau trong faces-config.xml:

<factory> 
    <render-kit-factory>com.example.Html5RenderKitFactory</render-kit-factory> 
</factory> 

Tiện ích JSF thư viện OmniFaces cũng có một bộ làm như vậy, Html5RenderKit (source code here) mà nên về mặt lý thuyết cũng làm việc tốt trên PrimeFaces thành phần. Tuy nhiên, câu hỏi này buộc tôi phải nhìn lại lần nữa và tôi cảm thấy xấu hổ khi thấy rằng đối số component trong ResponseWriter#startElement()null trong <p:inputText> (xem line 74 of InputTextRenderer, thay vào đó phải là writer.startElement("input", inputText)). Tôi không chắc chắn nếu điều này là cố ý hoặc giám sát trong việc thiết kế các renderer PrimeFaces hay không, nhưng bạn có thể sử dụng UIComponent#getCurrentComponent() thay vào đó để có được nó.


Cập nhật: đây là cố định trong OmniFaces 1.5.


Ghi nhận nên rằng JSF sắp tới 2.2 sẽ hỗ trợ việc xác định thuộc tính tuỳ chỉnh trong giao diện thông qua passthrough namespace mới hoặc <f:passThroughAttribute> thẻ. Xem thêm What's new in JSF 2.2? - HTML5 Pass-through attributes.

Do đó, vì vậy:

<html ... xmlns:p="http://java.sun.com/jsf/passthrough"> 
... 
<h:inputText ... p:autofocus="true" /> 

(bạn có thể muốn sử dụng a thay vì p như tiền tố namespace để tránh đụng độ với không gian tên mặc định PrimeFaces')

Hoặc:

<h:inputText ...> 
    <f:passThroughAttribute name="autofocus" value="true" /> 
</h:inputText> 
+0

Tuyệt vời, điều này trông đẹp hơn nhiều so với giải pháp cho mỗi thành phần. Tôi chắc chắn các trường hợp sử dụng cụ thể có thể tạo ra các đối số theo cách nào đó. Nếu tôi nhận được thời gian tôi sẽ cố gắng này ra vì có vẻ như nó sẽ là nhanh hơn nhiều so với homegrowing tất cả các thành phần. – Rich

+0

UIComponent # getCurrentComponent (FacesContext) có vẻ là chữ ký, vì vậy trong ngữ cảnh của phương thức startElement không có sẵn. Tôi thấy trong Omnifaces của bạn cam kết bạn sử dụng Components.getCurrentCompnent() mà không yêu cầu bối cảnh, mặc dù đó là một lớp OmniFaces mà không có sẵn chỉ trong PrimeFaces. Tại thời điểm đó Nó sẽ nguy hiểm hơn đối với tôi để cũng phải ghi đè lên bất kỳ thành phần nào có thể truyền null đến startElement. Bạn có đề nghị tôi kéo OmniFaces hoặc có cách nào khác không? – Rich

+0

Mẫu 'Components # getCurrentComponent()' Các OmniFaces chỉ đại biểu cho 'UIComponent # getCurrentComponent()'. Bạn không rõ ràng nếu bạn đã thử nghiệm nó hay không, nhưng nó làm việc cho tôi. – BalusC

3

Giải pháp mà tôi tìm thấy là mở rộng và triển khai lại các phương thức encodeMarkup cho trình kết xuất đồ họa. Tôi muốn có một giải pháp tổng quát hơn, nhưng sau khi nhìn vào mã nguồn của Primefaces, tôi không thấy bất kỳ móc chung nào cho các trình kết xuất thành phần để thêm các thuộc tính tùy chỉnh. Đánh dấu được viết ra theo phương pháp encodeMarkup(FacesContext context, InputText inputText) của trình kết xuất đồ họa. Nó gọi hệ thống phân cấp lớp thành renderPassThruAttributes(FacesContext context, UIComponent component, String[] attributes) nhưng nó chỉ cấp dữ liệu trong các chuỗi String [] cuối cùng tĩnh từ org.primefaces.util.HTML.

Trong trường hợp của tôi, tôi muốn hỗ trợ cho thuộc tính 'tự động lấy nét' trên các thành phần InputMask, InputText, InputTextarea và Password. Hơn nữa, việc triển khai thực hiện tương tự cho từng thành phần, vì vậy tôi sẽ hướng dẫn thực hiện 'lấy nét tự động' trên thành phần InputText, nhưng rõ ràng nó có thể được mở rộng như thế nào để hỗ trợ nhiều thuộc tính hơn và nhiều thành phần hơn.

Để mở rộng/ghi đè trình kết xuất, bạn sẽ cần có sẵn nguồn Primefaces và tìm phương thức encodeMarkup và sao chép nội dung của nó. Dưới đây là ví dụ cho InputTextRenderer:

protected void encodeMarkup(FacesContext context, InputText inputText) throws IOException { 
    ResponseWriter writer = context.getResponseWriter(); 
    String clientId = inputText.getClientId(context); 

    writer.startElement("input", null); 
    writer.writeAttribute("id", clientId, null); 
    writer.writeAttribute("name", clientId, null); 
    writer.writeAttribute("type", inputText.getType(), null); 

    String valueToRender = ComponentUtils.getValueToRender(context, inputText); 
    if(valueToRender != null) { 
     writer.writeAttribute("value", valueToRender , null); 
    } 

    renderPassThruAttributes(context, inputText, HTML.INPUT_TEXT_ATTRS); 

    if(inputText.isDisabled()) writer.writeAttribute("disabled", "disabled", null); 
    if(inputText.isReadonly()) writer.writeAttribute("readonly", "readonly", null); 
    if(inputText.getStyle() != null) writer.writeAttribute("style", inputText.getStyle(), null); 

    writer.writeAttribute("class", createStyleClass(inputText), "styleClass"); 

    writer.endElement("input"); 
} 

Mở rộng/Trọng renderer với riêng bạn (xem ý kiến ​​cho mã quan trọng):

public class HTML5InputTextRenderer extends InputTextRenderer { 

    Logger log = Logger.getLogger(HTML5InputTextRenderer.class); 

    //Define your attributes to support here 
    private static final String[] html5_attributes = { "autofocus" }; 

    protected void encodeMarkup(FacesContext context, InputText inputText) throws IOException { 
    ResponseWriter writer = context.getResponseWriter(); 
    String clientId = inputText.getClientId(context); 

    writer.startElement("input", null); 
    writer.writeAttribute("id", clientId, null); 
    writer.writeAttribute("name", clientId, null); 
    writer.writeAttribute("type", inputText.getType(), null); 

    String valueToRender = ComponentUtils.getValueToRender(context, inputText); 
    if (valueToRender != null) { 
     writer.writeAttribute("value", valueToRender, null); 
    } 

    renderPassThruAttributes(context, inputText, HTML.INPUT_TEXT_ATTRS); 

    //Make an extra call to renderPassThruAttributes with your own attributes array 
    renderPassThruAttributes(context, inputText, html5_attributes); 

    if (inputText.isDisabled()) 
     writer.writeAttribute("disabled", "disabled", null); 
    if (inputText.isReadonly()) 
     writer.writeAttribute("readonly", "readonly", null); 
    if (inputText.getStyle() != null) 
     writer.writeAttribute("style", inputText.getStyle(), null); 

    writer.writeAttribute("class", createStyleClass(inputText), "styleClass"); 

    writer.endElement("input"); 
    } 
} 

Cấu hình ghi đè đang hiển thị trong faces-config.xml

<?xml version='1.0' encoding='UTF-8'?> 
<faces-config xmlns="http://java.sun.com/xml/ns/javaee" 
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
     xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facesconfig_2_0.xsd" 
     version="2.0"> 

    <!-- snip... --> 

    <render-kit> 
     <renderer> 
      <component-family>org.primefaces.component</component-family> 
      <renderer-type>org.primefaces.component.InputTextRenderer</renderer-type> 
      <renderer-class>com.mycompany.HTML5InputTextRenderer</renderer-class> 
     </renderer> 
    </render-kit> 

    <!-- snip... --> 
</faces-config> 

và chỉ trong trường hợp nếu bạn không có cấu hình khuôn mặt được định cấu hình trong web.xml của mình thêm:

<context-param> 
     <param-name>javax.faces.CONFIG_FILES</param-name> 
     <param-value> 
      /WEB-INF/faces-config.xml, /faces-config.xml 
     </param-value> 
    </context-param> 

Sau đó sử dụng điều này trong một số lập trình:

<p:inputText id="activateUserName" value="${someBean.userName}" 
    autofocus="on"> 
</p:inputText> 

Lưu ý: JSF là không hài lòng với các thuộc tính mà không có giá trị. Trong khi tự động lấy nét trong HTML5 không sử dụng một giá trị, JSF sẽ ném một lỗi nếu không được cung cấp, vì vậy hãy chắc chắn xác định một số giá trị ném đi khi thêm các thuộc tính đó.

+0

Cho mình một câu trả lời trong vòng 2 phút? Công việc tốt! –

+2

SO cung cấp cho bạn tùy chọn để thực hiện câu hỏi về kiểu Hỏi & Đáp khi bạn trả lời câu hỏi ngay từ đầu. Tôi đã không tìm thấy bất kỳ câu trả lời cụ thể cho điều này khi tôi đã cố gắng để giải quyết nó vì vậy tôi nghĩ rằng tôi sẽ đóng góp nó cho cộng đồng. – Rich

+0

@Rich có vẻ đầy hứa hẹn. Tôi sẽ kiểm tra nó vào cuối tuần. –

2

JSF 2.2 cũng cung cấp tính năng thông qua thuộc tính được thiết kế cho HTML5 và hơn thế nữa khi PrimeF aces chính thức hỗ trợ JSF 2.2, bạn có thể chuyển bất kỳ thuộc tính nào từ các thành phần sang phần tử html.

+0

Tuyệt vời, tôi sẽ theo dõi điều đó. – Rich

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