Friday, June 15, 2012

Adding HTML5 attributes to standard JSF components

For OmniFaces I've recently looked for the least intrusive way (i.e. no custom component/tag/renderer boilerplate necessary) to add HTML5 specific <form>, <input>, <textarea> and <select> attributes like placeholder, autofocus, type="range", etcetera. You can learn about all of those new HTML5 attributes in html5tutorial.info under the section "Web Form 2.0". The JSF renderers by default ignores all attributes which are not officially supported by the components.

After some thinking, this appears the best to achieve using a custom RenderKit which returns a custom ResponseWriter wherein the startElement() and writeAttribute() methods are been overridden to check for the current component and if the developer has specified any custom HTML5-related attributes. This way the developer can just add them to standard JSF <h:form>, <h:inputText>, <h:inputTextarea> and <h:selectXxx> components.

Html5RenderKit

The result was a new OmniFaces feature: Html5RenderKit (source code here). This will be available in the future OmniFaces 1.1 for which you can download a snapshot release here.

To get it to run, register its factory as follows in faces-config.xml:



<factory>
    <render-kit-factory>org.omnifaces.renderkit.Html5RenderKitFactory</render-kit-factory>
</factory>

Here's a demonstration how all those new HTML5 attributes can be used (note that the <h:form> now also supports the autocomplete attribute, in standard JSF 2.0/2.1 this was so far only supported on input components):

<h:form autocomplete="off">
    <h:panelGrid columns="3">
        <h:outputLabel for="text" value="Normal text" />
        <h:inputText id="text" value="#{bean.text1}" />
        <h:outputText value="Supported in all browsers" />
    
        <h:outputLabel for="placeholder" value="With placeholder" />
        <h:inputText id="placeholder" value="#{bean.text2}" placeholder="type here" />
        <h:outputText value="Since Firefox 4, Safari 4, Chrome 10, Opera 11.10 and IE 10" />
    
        <h:outputLabel for="autofocus" value="With autofocus" />
        <h:inputText id="autofocus" value="#{bean.text3}" autofocus="true" />
        <h:outputText value="Since Firefox 4, Safari 5, Chrome 6, Opera 11 and IE 10" />

        <h:outputLabel for="search" value="Search" />
        <h:inputText id="search" type="search" value="#{bean.search}" />
        <h:outputText value="Since Firefox 4, Safari 5, Chrome 6, Opera 10.6 and IE 9" />
    
        <h:outputLabel for="email" value="Email" />
        <h:inputText id="email" type="email" value="#{bean.email}" />
        <h:outputText value="Since Firefox 4, Chrome 6, Opera 10.6 and IE 10" />
    
        <h:outputLabel for="url" value="URL" />
        <h:inputText id="url" type="url" value="#{bean.url}" />
        <h:outputText value="Since Firefox 4, Safari 5, Chrome 6, Opera 10.6 and IE 10" />
    
        <h:outputLabel for="phone" value="Phone" />
        <h:inputText id="phone" type="tel" value="#{bean.phone}" />
        <h:outputText value="Since Firefox 4, Safari 5, Chrome 6, Opera 10.6 and IE 10" />
    
        <h:outputLabel for="range" value="Range (between 1 and 10)" />
        <h:inputText id="range" type="range" value="#{bean.range}" min="1" max="10" />
        <h:outputText value="Since Safari 4, Chrome 6, Opera 11 and IE 10" />
    
        <h:outputLabel for="number" value="Number (between 7 and 13)" />
        <h:inputText id="number" type="number" value="#{bean.number}" min="7" max="13" />
        <h:outputText value="Since Safari 4, Chrome 9 and Opera 11" />
    
        <h:outputLabel for="date" value="Date" />
        <h:inputText id="date" type="date" value="#{bean.date}"
            converterMessage="Format must be yyyy-MM-dd">
            <f:convertDateTime pattern="yyyy-MM-dd" />
        </h:inputText>
        <h:panelGroup>
            <h:outputText value="Since Opera 10.6" 
                rendered="#{not facesContext.validationFailed}" />
            <h:message for="date" />
        </h:panelGroup>
    
        <h:outputLabel for="textarea1" value="Textarea with maxlength of 20" />
        <h:inputTextarea id="textarea1" value="#{bean.text4}" cols="16" maxlength="20" />
        <h:outputText value="Since Firefox 4, Safari 5, Chrome 6, Opera 11 and IE 10" />
    
        <h:outputLabel for="textarea2" value="Textarea with placeholder" />
        <h:inputTextarea id="textarea2" value="#{bean.text5}" cols="16" placeholder="some text" />
        <h:outputText value="Since Firefox 4, Safari 5, Chrome 10, Opera 11.50 and IE 10" />
    
        <h:panelGroup />
        <h:commandButton value="submit">
            <f:ajax execute="@form" render="@form" />
        </h:commandButton>
        <h:panelGroup>
            <h:outputText value="OK!"
                rendered="#{facesContext.postback and not facesContext.validationFailed}" />
        </h:panelGroup>
    </h:panelGrid>
</h:form>

With this backing bean:

@ManagedBean
@ViewScoped
public class Bean implements Serializable {

    private String text1;
    private String text2;
    private String text3;
    private String search;
    private String email;
    private String url;
    private String phone;
    private Integer range;
    private Integer number;
    private Date date;
    private String text4;
    private String text5;

    // Add/generate getters/setters.
}

Here's a screenshot of how it look like in Chrome 19:

Friday, June 1, 2012

OmniFaces 1.0 is released!

After a long time of collecting and developing helpful utility methods and components, taking benefit of lessons learnt during ages of professional JSF development, reading stackoverflow.com for frequently recurring peculiar JSF problems, finding reuseable solutions for those problems, testing everything on both Mojarra and MyFaces, working through reported issues, we, me and Arjan Tijms, can now proudly announce that OmniFaces 1.0 is ready for release!

omnifaces-1.0.jar

The last major addition is the FacesViews feature, an idea of Arjan Tijms, which allows using extensionless URLs. Long story short, here's a link to the FacesViews guide and the showcase demo.

OmniFaces 1.0 Initial features

Here's an overview of what's all available in OmniFaces 1.0:

  • Conditional comment rendering for IE
  • Highlighting fields that failed validation
  • Executing scripts on load of every (ajax) response
  • OutputLabel automatically setting label of associated input component
  • Include Servlets and JSP pages in Facelets
  • Tree with full custom markup per level (useable for various recursive use cases)
  • Stateless view parameter
  • Automatic conversion of model objects in drop-downs and other select components.
  • Simplified PhaseListener requiring less boiler plate
  • Reset for input fields making them always updateable via ajax
  • Ajax aware exception handler
  • Extensionless URLs without need to register individual pages
  • Proper 404 for FacesFileNotFoundException
  • HttpFilter convenience class as analogy to HttpServlet
  • Collections of EL functions for dealing with arrays, conversion, dates and strings
  • Convenience managed beans for the current time and server startup time
  • Combining separate scripts and stylesheets to reduce HTTP requests
  • Render-time evaluation for f:converter and f:validator attributes
  • Passing a method expression into Facelets tag
  • Collection of Java methods for dealing with components, events, exceptions, messages and JSF in general
  • Checkbox with required attribute that works intuitively
  • Multi-field validators, for "all or none", "all equal", "one or more", "all in order" and "unique" validations

You can find demonstrations of practically everything on the showcase site.

This is not the end. We still have more ideas on our list, which we would like to implement in future versions. However, none of them are really trivial. For example, client+server side double submit prevention which handles responses properly, some sort of a session timeout checker, a component which makes it possible to invoke a bean action before unload, a component which caches the generated HTML output in the specified scope for a specified time, a component which simplifies usage of HTML5 localstorage feature from JSF on, some components which should simplify container managed login/logout in JSF, etcetera.