跳至主要内容

JSF 2.2: HTML5 Support

JSF 2.2: HTML 5 Support

No doubt HTML 5 is the hottest topic in web development in these years.
Besides a series of new tags are added, the form element support is also improved. The type attribute of input element can be text, email, number, range, url, date etc. Ideally, a browser could provide native validation for these input tags.(but this feature support is very different between browsers).

JSF 2.0

In JSF 2.0, some attributes of HTML 5 form elements can not be rendered correctly in the standard JSF input components. For example, <input type="email" can not be rendered by <h:inputText type="email".
omnifaces fixes the issue, and adds basic HTML5 form elements support by a extened Html5RenderKitFactory.
What you need to do is declare a Html5RenderKitFactory in the faces-config.xml.
<factory>
    <render-kit-factory>org.omnifaces.renderkit.Html5RenderKitFactory</render-kit-factory>
</factory>
Now, the <h:inputText type="email" can be rendered as expected.
But unfortunately, the omnifaces solution does not support custom data- attributes in the HTML 5 form input tags.

HTML5 support in JSF 2.2

In Java EE 7, HTML 5 is the first class citizen. But refactoring all JSF components to add extra input attributes is not a smart decision. JSF 2.2 provided a compromise solution to support HTML 5 input tags.
They are called Passthrough Attributes and Passthrough Elements respectively.

Passthrough Attributes

JSF 2.2 provides a new facelets taglib namespace to process Passthrough Attributes.
xmlns:p="http://xmlns.jcp.org/jsf/passthrough"
For example, you want to add placeholder to input element, just added a p:placeholder attribute.
<h:inputText p:placeholder="Type text here..."/>
This tell JSF HTMLRenderKit keep back the attribute placeholder directly, not like JSF 2.0, any none support attributes will be ate by HTML RenderKit.
The core taglib also added a new tag to support passthrough feature.
<f:passThroughAttribute name="placeholder" value="Type text here..."/>
This is equivalent to the above version.
You can add more than one attributes at the same time.
 <h:inputText id="number" p:type="number" p:min="1" p:max="10"  value="..."/>
Or use multi f:passThroughAttribute nested in the inputText component.
<h:inputText id="text" value="#{html5Bean.text}" required="true">  
    <f:passThroughAttribute name="placeholder" value="Type text here..."/>
    <f:passThroughAttribute name="required" value="true"/>
</h:inputText>
Or use a f:passThroughAttributes nested in the inputText component, it can accept a Map.
<f:passThroughAttributes value="#{html5Bean.attrs}"/>
In backend bean, a Map is declared.
private Map<String, String> attrs = new HashMap<String, String>();

@PostConstruct
public void init() {
    log.info(" call init@");
    this.attrs.put("type", "range");
    this.attrs.put("min", "1");
    this.attrs.put("max", "10");
    this.attrs.put("step", "2");
}
You can also use a EL 3.0 Map expression as value. EL 3.0 is extracted from JSF specifcation as a standalone specification now, we will discuss it in further post.
<f:passThroughAttributes value="#{{'type':'range', 'min':'0', 'max':'10', 'step':'2'}}"/>
A complete example of Passthrough Attributes.
<h:form>
    <h:messages showDetail="false" showSummary="true"/>
    Text: <h:inputText id="text" value="#{html5Bean.text}" required="true">  
        <f:passThroughAttribute name="placeholder" value="Type text here..."/>
        <f:passThroughAttribute name="required" value="true"/>
    </h:inputText>
    <br/>
    Url: <h:inputText id="url" value="#{html5Bean.url}">
        <f:passThroughAttribute name="type" value="url"/>
    </h:inputText><br/>
    Email: <h:inputText id="email" p:type="email" value="#{html5Bean.email}" /><br/>
    Number: <h:inputText id="number" p:type="number" p:min="1" p:max="10"  value="#{html5Bean.number}" >
        <f:convertNumber minFractionDigits="0"/>
    </h:inputText> <br/>
    Range: <h:inputText id="range" value="#{html5Bean.range}">
        <!-- f:passThroughAttributes value="#{html5Bean.attrs}"/ -->
        <f:passThroughAttributes value="#{{'type':'range', 'min':'0', 'max':'10', 'step':'2'}}"/>
        <f:convertNumber minFractionDigits="0"/>
    </h:inputText><br/>
    Date: <h:inputText id="date" p:type="date" value="#{html5Bean.date}" >
        <f:convertDateTime pattern="yyyy-MM-dd"/>
    </h:inputText><br/>
    <h:commandButton value="Save" action="#{html5Bean.submit()}">
        <f:ajax execute="@form" render="@all"/>
    </h:commandButton>
</h:form>

<h:panelGroup id="out">
    Text:  <h:outputText value="#{html5Bean.text}"/><br/>
    Url:  <h:outputText value="#{html5Bean.url}"/><br/>
    Email:  <h:outputText value="#{html5Bean.email}"/><br/>
    Number:  <h:outputText value="#{html5Bean.number}"/><br/>
    Range:  <h:outputText value="#{html5Bean.range}"/><br/>
    Date:  <h:outputText value="#{html5Bean.date}"/><br/>
</h:panelGroup>
Through the passthrough feature in JSF 2.2, you can add any custom attribute to input element, such as the data-XXX attribute in Bootstrap framework.
The Passthrough Attributes way is very friendly when you migrate the existed JSF application to JSF 2.2 and make it compatible with HTML 5.

Passthrough Elements

JSF 2.2 provides another facelets taglib namespace to support Passthrough Elements feature.
xmlns:jsf="http://xmlns.jcp.org/jsf"
All plain HTML element will be treated as equivalent facelets components.
For example.
<input jsf:id="text" type="text" jsf:value="#{html5Bean.text}" required="required" placeholder="Type text here..."/>
is equivalent to the <h:inputText. All standard attributes of h:inputText in the input tag can be treated automatically. The extra placeholder attributes will be keep as it is after it is rendered.
You can find the complete comparison table between plain HTML tags and facelets components from Oracle Java EE tutorial.
As described in the Java EE tutorial, you have to mark at least one of the attributes with jsf prefix as a maker to make the input tag to be processed as a JSF component.
NOTE: In the above example, I found I have to add "jsf:" to the value attribute to make it work, or it will throw an exception at runtime, even I added a jsf:id in the the input tag already.
It is simple and stupid, and it is very friendly when you work together with a web designer.
An complete example to demonstrate Passthrough Elements feature.
<h:form prependId="false">
    <h:messages showDetail="false" showSummary="true"/>
    Text: <input jsf:id="text" type="text" jsf:value="#{html5Bean.text}" required="required" placeholder="Type text here..."/>  
    <br/>
    Url: <input jsf:id="url" type="url"  jsf:value="#{html5Bean.url}"/><br/>

    Email: <input type="email" jsf:id="email" jsf:value="#{html5Bean.email}" /><br/>
    Number: <input jsf:id="number" type="number" min="1" max="10"  jsf:value="#{html5Bean.number}" >
        <f:convertNumber minFractionDigits="0"/>
    </input> <br/>
    Range: <input jsf:id="range"  type="range" min="1" max="10"  jsf:value="#{html5Bean.range}">
        <f:convertNumber minFractionDigits="0"/>
    </input><br/>

    Date: <input jsf:id="date" type="date" jsf:value="#{html5Bean.date}" >
        <f:convertDateTime pattern="yyyy-MM-dd"/>
    </input><br/>
    <input type="submit" jsf:id="submit" value="Save" jsf:action="#{html5Bean.submit()}">            
    </input>
</h:form>
NOTE: In fact, this is really not a new feature. In the old facelets(before JSF 2.0), it supports a jsfcid attribute in plain HTML tags to identify a JSF component. What make me excited is it becomes standard now.

The pain of HTML 5 support in browsers

Currently only Opera supports all HTML 5 elements in this post.
The following is the result of HTML 5 required attribute validation.
the required true displayed in opera
This is the the whole form displayed in Opera.
the required true displayed in opera
In Firefox, the range, number, date are not rendered as expected, they are displayed as plain text input instead.
HTML 5 looks beautiful, but the browser support is a little disappointed now.
NOTE: If f:ajax is added in the *h:commandButton and the form is submit in ajax way, I found the browser native validation does not work.*

Sample codes

Check out the complete codes from my Github.com, and play it yourself.
https://github.com/hantsy/ee7-sandbox

评论

此博客中的热门博文

AngularJS CakePHP Sample codes

Introduction This sample is a Blog application which has the same features with the official CakePHP Blog tutorial, the difference is AngularJS was used as frontend solution, and CakePHP was only use for building backend RESR API. Technologies AngularJS   is a popular JS framework in these days, brought by Google. In this example application, AngularJS and Bootstrap are used to implement the frontend pages. CakePHP   is one of the most popular PHP frameworks in the world. CakePHP is used as the backend REST API producer. MySQL   is used as the database in this sample application. A PHP runtime environment is also required, I was using   WAMP   under Windows system. Post links I assume you have some experience of PHP and CakePHP before, and know well about Apache server. Else you could read the official PHP introduction( php.net ) and browse the official CakePHP Blog tutorial to have basic knowledge about CakePHP. In these posts, I tried to follow the steps describ

JPA 2.1: Attribute Converter

JPA 2.1: Attribute Converter If you are using Hibernate, and want a customized type is supported in your Entity class, you could have to write a custom Hibernate Type. JPA 2.1 brings a new feature named attribute converter, which can help you convert your custom class type to JPA supported type. Create an Entity Reuse the   Post   entity class as example. @Entity @Table(name="POSTS") public class Post implements Serializable { private static final long serialVersionUID = 1L; @Id @GeneratedValue(strategy = GenerationType.AUTO) @Column(name="ID") private Long id; @Column(name="TITLE") private String title; @Column(name="BODY") private String body; @Temporal(javax.persistence.TemporalType.DATE) @Column(name="CREATED") private Date created; @Column(name="TAGS") private List<String> tags=new ArrayList<>(); } Create an attribute convert

Auditing with Hibernate Envers

Auditing with Hibernate Envers The approaches provided in JPA lifecyle hook and Spring Data auditing only track the creation and last modification info of an Entity, but all the modification history are not tracked. Hibernate Envers fills the blank table. Since Hibernate 3.5, Envers is part of Hibernate core project. Configuration Configure Hibernate Envers in your project is very simple, just need to add   hibernate-envers   as project dependency. <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-envers</artifactId> </dependency> Done. No need extra Event listeners configuration as the early version. Basic Usage Hibernate Envers provides a simple   @Audited   annotation, you can place it on an Entity class or property of an Entity. @Audited private String description; If   @Audited   annotation is placed on a property, this property can be tracked. @Entity @Audited public class Signup implements Serializa