Saturday, June 16, 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 as per OmniFaces 1.1.

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:

22 comments:

Daniel said...

This looks really great!!!

Comitizer said...

Will this work for data attributes?

Things like data-*?

http://ejohn.org/blog/html-5-data-attributes/

BalusC said...

@Comitizer: no, the data attributes are not taken into account. Principally, those attribtues are supported on every single plain HTML element and it would be relatively expensive to check every single JSF component for this. Maybe I can add support for them on UIInput components only.

Dietrich Schulten said...

Your post comes exactly at the right time.

Please support output as well, to enable integration with e.g. knockoutjs.

Steve said...

This is awesome and exactly what I was looking for when I searched for a solution to the same problem. Only issue is, we're on JSF 1.2 (I know, Lame right?) so I fell at the first hurdle as RenderKitWrapper is a jsf 2.0 class :( Any way you know of that I can adapt this idea for JSF 1.2?

Unknown said...

Lots of Good information in your post, I favorite your blog post so I can visit again in the future, Thanks.
Kazol

Cody said...

If an f:ajax submit (with Mojarra) is triggered any inputs types such as number or date are silently ignored. Any workarounds?

Unknown said...

Great! Omnifaces rocks ;) Thanks a lot

Unknown said...

Works great! I needed placeholder and "here we are"! Would be greater if it worked with h:inputSecret also. Could you consider this BalusC? Thanks :-)

BalusC said...

@Robert: that was already reported for OmniFaces and included in current 1.4 snapshot.

Unknown said...
This comment has been removed by the author.
Unknown said...

Even after adding Omnifaces 1.3 JAR and adding REnder Kit Factory to faces-cofig.xml, placeholder attribute is not being supported by h:inputText tag. I think I am doing some mistake. What can be possible reason for 'placeholder' not being supported? Thanks :)

Pedro Caruny said...

Really useful post, man!
That came in handy in the right time!!!

Thank you very much!

Pedro Caruny said...
This comment has been removed by the author.
Unknown said...

Great work!!!

Although, I'm having an "issue" with RichFaces Calendar component. Calendar component renderer calls the startElement method several times in order to render a complex popup calendar (div, span, img, etc). This ends up in this elements having the attributes set too. Any workaround?

Thanx

Unknown said...

Hi,

Balus - thanks for posting this, it saved me a lot of work.

I'd like to mention that I;'m having a similar issue to what Yamek Hdez is having with Richfaces calendar with the Primefaces calendar. The "pattern" attribute on the calendar (yyy/mm/dd format) is being passed all the way down to the underlying HTML5 attribute, which doesn't know what tod o with it.

Do you know of any workaround for this?

Chuck said...

Hi BalusC,

Can't we able to produce HTML5 code using JSF1.2, RichFaces 3.3 ?

Please let me know. Thanks.

joerg said...

I have the same question as Chuck - is it possible to get this done with JSF 1.2. ???

Unknown said...

Thanks BalusC ... I wrote this last year just before you posted this blog.
We actually discussed it.

The biggest component needed is for text-types.

If those are covered it will hit most HTML-5 additions.

import java.io.IOException;

import javax.faces.component.UIComponent;
import javax.faces.context.*;

import com.sun.faces.renderkit.html_basic.TextRenderer;

public class InputRender extends TextRenderer
{

// Put all of the attributes you want to render here...
private static final String[] ATTRIBUTES = { "required", "placeholder" };

@Override
protected void getEndTextToRender(FacesContext context, UIComponent component, String currentValue) throws IOException
{
final ResponseWriter originalResponseWriter = context.getResponseWriter();
context.setResponseWriter(new ResponseWriterWrapper()
{

@Override
// As of JSF 1.2 this method is now public.
public ResponseWriter getWrapped()
{
return originalResponseWriter;
}

@Override
public void startElement(String name, UIComponent component) throws IOException
{
super.startElement(name, component);
if ("input".equals(name))
{
for (String attribute : ATTRIBUTES)
{
Object value = component.getAttributes().get(attribute);
if (value != null)
{
super.writeAttribute(attribute, value, attribute);
}
}
}
}
});

super.getEndTextToRender(context, component, currentValue);
context.setResponseWriter(originalResponseWriter); // Restore original.
}

}

Kasor said...

Hi BalusC, thanks for all your hints ...
I Have seen your proposition to use passThrough for html5 attributes in JSF 2.2. I am concerned with data-* attributes used in bootstrap ...

Is this actually the way when using JSF 2.2, or can we just apply the passthrough like p:data-xxxx="value" when using JSF 2.2 ?

Thanks for your confirmation ...

Anonymous said...

Thanks for the tips on FancyBox and Paging Navigation (wordpress-like). Well-explained to make it work also on my blog.


html5

kalyan said...

Hi BalusC,

Could you please let me know how to write JSF custom tags in JSF2.0 enviornment with JSP pages instead of XHTML pages.

My requirement says: i should only use JSP pages along with JSF2.0 environment.

Please help me this is very urgent.