vendredi 18 mars 2011

Use JQuery Mobile with JSF 2 (powered by Tomcat)


JQuery Mobile is an extension to JQuery library.
The idea behind is simple and very powerful : use web technology to produce portable mobile applications.
Why not using JSF 2 and its powerful new features : templating,custom xhtml tags, CDI, ... to build mobile app running everywhere ?
I can get all the power of JEE stack, use existing code and plug new mobile applications into my information system without a long learning curve. JQuery Mobile handles the complexity for me !
This post is not a JSF 2 tutorial, so have a look on existing tutorials on the Internet if necessary.
You can find the source code here.
Note : some configuration files need a cleaning pass (faces-config.xml, web.xml).

The basics


JSF 2 promotes the XHTML format. JSP is not an XML format, this is bad. XHTML is THE standard to build web applications.
Here is a sample XHTML based on JSF 2 :
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
        xmlns:f="http://java.sun.com/jsf/core"
        xmlns:h="http://java.sun.com/jsf/html"
        xmlns:ui="http://java.sun.com/jsf/facelets">
<h:head>
        <link rel="stylesheet"
                href="resources/mobile/css/jquery.mobile-1.0a3.min.css" />
        <script src="resources/common/js/jquery-1.5.js" />
        <script src="resources/mobile/js/jquery.mobile-1.0a3.min.js" />
</h:head>
<h:body>
        <div data-role="page">
                <div data-role="header" data-theme="b">
                        <h1>Hello World !</h1>
                </div>
                <div data-role="content">
                        <p>Buy my fruits !</p>                      
                        <ul data-role="listview" data-inset="true">
                                <li><a href="apples.html">Apples</a></li>
                                <li><a href="kiwis.html">Kiwis</a></li>
                                <li><a href="mangos.html">Mangos</a></li>
                                <li><a href="peaches.html">Peaches</a></li>
                        </ul>
                </div>
        </div>
</h:body>
</html>
Now, i start Tomcat 6.0.X. : time to test !!!!

The drama


Firefox displays my first page very well but it's the only one !



The page is ugly on Chrome, Safari Mobile and all others mobile browsers. It simply does not work :

Plus : i get an error in Console.
Uncaught Error: INVALID_STATE_ERR: DOM Exception 11
So, what happens ?
When looking closely at my page content type, i can see that Firefox treats the page as text/html;charset=UTF-8, the others asapplication/xhtml+xml;charset=UTF-8.
But my JSF page is exactly the same !!!!
Chrome send the request with an accept header like this :
Accept:application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5
Firefox send it this way :
Accept:text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Tomcat server adjust the response content type accordingly to the accept header.
For Firefox, text/html is used for others it'is application/xhtml+xml.
The XML parser in Chrome (and others) does not found some element when treating the document as an "application/xhtml+xml", this triggers the exception.
It seems that it's a bug of JQuery mobile but many developers have asked for help on JQuery forum without answers. Hey, it's "Alpha" !

Happy ending


You can force the content type header to text/html for *.xhtml requests.
Here is the filter :

import java.io.IOException; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletResponse; public class XHTMLContentTypeFilter implements Filter { public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletResponse httpServletResponse = (HttpServletResponse) response; try { chain.doFilter(request, response); String contentType = httpServletResponse.getContentType(); //System.out.println(contentType); if (contentType != null && contentType.contains("application/xhtml+xml;")) { httpServletResponse.setContentType("text/html;charset=UTF-8"); } } catch (Throwable t) { } } public void init(FilterConfig arg0) throws ServletException { } public void destroy() { } }
Here is the web.xml fragment :
<filter>
        <filter-name>XHTMLContentTypeFilter</filter-name>
        <filter-class>XHTMLContentTypeFilter</filter-class>
</filter>
<filter-mapping>
        <filter-name>XHTMLContentTypeFilter</filter-name>
        <url-pattern>*.xhtml</url-pattern>
</filter-mapping>

<servlet>
       <servlet-name>faces</servlet-name>
       <servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
       <load-on-startup>1</load-on-startup>
</servlet>


<servlet-mapping>
       <servlet-name>faces</servlet-name>
       <url-pattern>*.xhtml</url-pattern>
</servlet-mapping>
I restart Tomcat.
Now, everything is fine !

A powerful duo


Now, i can built new custom XHTML tag to increase productivity.
Why not using a custom tag to build mobile list ?
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
        xmlns:f="http://java.sun.com/jsf/core"
        xmlns:h="http://java.sun.com/jsf/html"
        xmlns:ui="http://java.sun.com/jsf/facelets"
        xmlns:mobile="http://java.sun.com/jsf/composite/mobile">
<h:head>
        <link rel="stylesheet"
                href="resources/mobile/css/jquery.mobile-1.0a3.min.css" />
       <script src="resources/common/js/jquery-1.5.js" />
       <script src="resources/mobile/js/jquery.mobile-1.0a3.min.js" />
</h:head>
<h:body>
      <div data-role="page">
                <div data-role="header" data-theme="b">
                        <h1>Hello World !</h1>
               </div>
               <div data-role="content">
                        <p>Buy my fruits !</p>                      
                        <mobile:list model="#{helloListCtrl.model}"/>
                </div>
        </div>
</h:body>
</html>
As you can see a mobile:list tag is very useful to build mobile list. It takes one model attribute which is an EL bound to a managed bean. The model property is a List object.
public class HelloWorldListCtrl {

        List<Fruit> model = new ArrayList<Fruit>();
        
        public HelloWorldListCtrl(){
                model.add(new Fruit("Apples","apples.xhtml"));
                model.add(new Fruit("Kiwis","kiwis.xhtml"));
                model.add(new Fruit("Mangos","mangos.xhtml"));
                model.add(new Fruit("Peaches","peaches.xhtml"));
        }

        public List<Fruit> getModel() {
                return model;
        }

        public void setModel(List<Fruit> model) {
                this.model = model;
        }
}
Here is my xhtml tag :
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
        xmlns:h="http://java.sun.com/jsf/html"
        xmlns:f="http://java.sun.com/jsf/core"
        xmlns:ui="http://java.sun.com/jsf/facelets"
        xmlns:composite="http://java.sun.com/jsf/composite"
        >
<h:head>
</h:head>
<h:body>
        <composite:interface>
        </composite:interface>

        <composite:implementation>
                <ul data-role="listview" data-inset="true">
                        <li jsfc="ui:repeat" value="#{cc.attrs.model}" var="current">
                                <a href="#{current.link}">#{current.name}</a>
                        </li>
                </ul>
        </composite:implementation>
</h:body>
</html>
Now i have a generic list tag i can use !
JSF 2 has many powerful features, so enjoy !




Contrat Creative Commons

the jee architect cookbook by Olivier SCHMITT est mis à disposition selon les termes de la licence Creative Commons Paternité - Pas d'Utilisation Commerciale - Pas de Modification 3.0 Unported.