Tuesday, May 29, 2012

Tapestry - Chenillekit

There are several open source component libraries for Tapestry, like Tynamo, TapX, Chenillekit, etc.

We will use Chenillekit as a demo, and put the greeting inside a Sliding Panel.

Update your pom.xml to contain the Chenillekit dependency, like this:

 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">  
       <modelVersion>4.0.0</modelVersion>  
       <groupId>org.confucius</groupId>  
       <artifactId>HelloWorldTapestry</artifactId>  
       <packaging>war</packaging>  
       <version>0.0.1-SNAPSHOT</version>  
         
       <dependencies>  
           <dependency>  
                <groupId>org.apache.tapestry</groupId>  
                <artifactId>tapestry-core</artifactId>  
                <version>5.3.3</version>  
           </dependency>              
   
           <dependency>  
                <groupId>org.apache.tapestry</groupId>  
                <artifactId>tapestry5-annotations</artifactId>  
                <version>5.3.3</version>  
           </dependency>  
               
           <dependency>  
                <groupId>org.chenillekit</groupId>  
                <artifactId>chenillekit-tapestry</artifactId>  
                <version>1.3.3</version>  
           </dependency>  
         
   </dependencies>  
         
    <build>  
        <finalName>HelloWorldTapestry</finalName>  
    </build>  
         
 </project>  
   

Update Layout.tml to use a Sliding Panel, like this:

 <html xmlns:t="http://tapestry.apache.org/schema/tapestry_5_1_0.xsd">  
   <head>  
     <title>Tapestry</title>  
   </head>  
   <body>  
     <h3>Basic Tutorial Series</h3>  
     
           <t:chenillekit.SlidingPanel t:subject="Chenillekit Greeter">  
             <t:body/>  
           </t:chenillekit.SlidingPanel>      
   
           <p>Visit Us @ www.projectconfucius.org</p>  
             
   </body>  
 </html>  

Now if you rebuild and redeploy HelloWorldTapestry, you will see the greeting inside a Chenillekit Sliding Panel.

Tapestry - Layouts

To create the same look-n-feel across multiple pages, Tapestry provides the concept of Layout. For example, all pages may have the same header and footer,

A Layout looks very similar to any other Page, except that it is placed in the /components folder instead of /pages.

For now, we will not worry about the difference between a component and a page - they are very similar. Both have a foo.tml file and a corresponding foo.java POJO.

Let us build a Layout for our pages.

In /src/main/resources/org/confucius, create a folder 'components'

In  /src/main/resources/org/confucius/components, create a file Layout.tml, like this:

 <html xmlns:t="http://tapestry.apache.org/schema/tapestry_5_1_0.xsd">  
   <head>  
     <title>Tapestry</title>  
   </head>  
   <body>  
     <h3>Basic Tutorial Series</h3>  
     
     <t:body/>  
   
     <p>Visit Us @ www.projectconfucius.org</p>  
             
   </body>  
 </html>  


Note that we have included the Tapestry tag library. The magic here is the t:body tag - this will get automatically replaced by the content of the actual page.

Again, this looks just like any other Tapestry page, but because it is in the /components folder, Tapestry treats it like a 'component'.


In /src/main/java/org/confucius, create a folder 'components'

In  /src/main/java/org/confucius/components, create a class Layout.java, like this:

 package org.confucius.components;  
   
 public class Layout {  
   
 }  
   

This is just an empty POJO, but it is necessary to make it available to Tapestry.

Now, update your Index.tml page to use the Layout, like this:

 <html t:type="layout" xmlns:t="http://tapestry.apache.org/schema/tapestry_5_1_0.xsd">  
      <body>  
           <h2>${greeting}</h2>  
      </body>  
 </html>  
   


The t:type attribute tells Tapestry to use Layout.tml as the layout component.

If you now rebuild and redeploy HelloWorldTapestry, you will see the header and footer rendered in the page.

Note:
If some of your pages needed a different layout, say Layout2 - then you will need to create a Layout2.tml and Layout2.java, and in your pages, you will say t:type="layout2"

Friday, May 25, 2012

Tapestry - Using Beans

Due to Tapestry convention, Index.java is already a Bean, no configuration necessary.

Let us see how to use it in Index.tml.

Update your Index.java to have a property 'greeting', like this:
 package org.confucius.pages;  
 public class Index {  
      private String greeting;  
      public Index() {  
           this.greeting = "Hello World, Tapestry!";  
      }  
      public void setGreeting(String greeting) {  
           this.greeting = greeting;  
      }  
      public String getGreeting() {  
           return greeting;  
      }  
 }  

We can now use this field in Index.tml, like this:
 <html>  
 <body>  
 <h2>${greeting}</h2>  
 </body>  
 </html>  

Due to the one-to-one mapping between pages and Beans, Tapestry will look for the 'greeting' property in Index.java, since it is referred to in Index.tml

Tapestry will automatically call the getGreeting()) method to get the property 'greeting'.

If you rebuild and redeploy HelloWorldTapestry, and point your browser to:
http://localhost:8080/HelloWorldTapestry/

you will see "Hello World, Tapestry!" displayed.

Tapestry Hello World - Setup

Let us setup a Tapestry project using Maven.

In Eclipse, go to File --> New Project --> Maven Project

Use the maven-archetype-webapp

Specify:
Group Id: org.confucius
Artfact Id: HelloWorldTapestry
Package: org.confucius

Once the project is created, we will update the pom.xml to include the Tapestry libraries.

So your pom.xml will now look like this:
 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">  
       <modelVersion>4.0.0</modelVersion>  
       <groupId>org.confucius</groupId>  
       <artifactId>HelloWorldTapestry</artifactId>  
       <packaging>war</packaging>  
       <version>0.0.1-SNAPSHOT</version>  
       <dependencies>  
           <dependency>  
                <groupId>org.apache.tapestry</groupId>  
                <artifactId>tapestry-core</artifactId>  
                <version>5.3.3</version>  
           </dependency>              
           <dependency>  
                <groupId>org.apache.tapestry</groupId>  
                <artifactId>tapestry5-annotations</artifactId>  
                <version>5.3.3</version>  
           </dependency>  
       </dependencies>  
       <build>  
        <finalName>HelloWorldTapestry</finalName>  
       </build>  
 </project>  

Next, we need to update our web.xml to use Tapestry.

Your web.xml will now look like this:
 <!DOCTYPE web-app PUBLIC  
  "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"  
  "http://java.sun.com/dtd/web-app_2_3.dtd" >  
 <web-app>  
   <context-param>  
     <param-name>tapestry.app-package</param-name>  
     <param-value>org.confucius</param-value>  
   </context-param>  
   <filter>  
     <filter-name>Tapestry Filter</filter-name>  
     <filter-class>org.apache.tapestry5.TapestryFilter</filter-class>  
   </filter>  
   <filter-mapping>  
     <filter-name>Tapestry Filter</filter-name>  
     <url-pattern>/*</url-pattern>  
   </filter-mapping>  
 </web-app>  

We direct all URLs to be handled by the Tapestry filter.


The default Welcome page for Tapestry is Index.tml

Under /src/main/resources, create the folder structure:
/src/main/resources/org/confucius/pages

In /src/main/resources/org/confucius/pages, create a file Index.tml, like this:
 <html>  
 <body>  
 <h2>Hello World, Tapestry!</h2>  
 </body>  
 </html>  

Note that an TML file looks just like a HTML file.

Under /src/main, create a Source Folder called 'java'
(R-click on /src/main, select New-->Other, then select Java-->Source Folder)


Under /src/main/java, create the folder structure:
/src/main/java/org/confucius/pages


In /src/main/java/org/confucius/pages, create a class Index.java, like this:
 package org.confucius.pages;  
 public class Index {  
 }  

Note that this is an empty POJO, but it must be available to Tapestry - it is the matching POJO for Index.tml

Our Tapestry project is now setup.

If you build and deploy the project, and browse to
http://localhost:8080/HelloWorldTapestry/

You will see "Hello World, Tapestry!" displayed. 

Tapestry

Tapestry is very similar to JSF.

However, it uses Convention-Over-Configuration to greatly reduce configuration and simplify UI development.

Just like JSF Facelets, Tapestry has its own templating engine. Tapestry template files end in the .tml extension.

The most significant convention is that every page Foo.tml must have a corresponding Foo.java.

Foo.java is a POJO and is automatically a Tapestry Bean, no configuration is necessary.

Secondly, Foo.java can only be accessed from Foo.tml and not from any other template.

Thirdly, any method in Foo.java can return a class (say Bar.class), or a string (say "Bar"). This tells Tapestry that the next page to display is Bar.tml

These simple conventions go a long way in making a Tapestry project very organized.

For example, in JSF, since any page can refer to any Bean, very soon it leads to a criss-cross of references from pages to Beans. If a page is deprecated, it is not clear if any of the Beans need to be deprecated as well. In the absence of convention, pages and Beans become disconnected (eventually, a mess).

Tapestry, because if its one-to-one relation between pages and Beans, makes it very clear how pages can access Beans. If a page is deprecated, the corresponding Bean gets deprecated as well.

This is one example of how Tapestry conventions make things simpler. There are several others.

Another example, since a Bean is associated with exactly one page,  maintaining state is non-ambiguous. In JSF, if a Bean is called from several pages, some of its state may be set by one Page, other by another Page - leading to a chaotic situation.

Finally, building Tapestry components is way easier than building corresponding JSF components.

There are many other optimizations and scalability that Tapestry is able to deliver because of its well thought-out conventions.

Thursday, May 24, 2012

JSF - Dependency Injection

Just like Spring (see this post), JSF provides support for injecting Beans into other Beans.

Let us see how this is done.

We will move the method for creating a List of Greets to a new class, then inject this class into HelloBean.

In your /src/main/java/org/confucius, create a class called GreetDAO.java, like this:

 package org.confucius;  
   
 import java.util.ArrayList;  
 import java.util.List;  
   
 import javax.faces.bean.ManagedBean;  
 import javax.faces.bean.SessionScoped;  
   
 @ManagedBean(name="greetDAO_v1")  
 @SessionScoped  
 public class GreetDAO {  
      public List<Greet> getInternationalGreetsList(){  
           List<Greet> greetList = new ArrayList<Greet>();  
             
           Greet englishGreet = new Greet("English", "Hello World!");  
           greetList.add(englishGreet);  
             
           Greet frenchGreet = new Greet("French", "Bonjour tout le Monde!");  
           greetList.add(frenchGreet);  
             
           Greet italianGreet = new Greet("Italian", "Buongiorno a Tutti!");  
           greetList.add(italianGreet);  
             
           Greet spanishGreet = new Greet("Spanish", "Hola Mundo!");  
           greetList.add(spanishGreet);  
             
           Greet swahiliGreet = new Greet("Swahili", "Jambo!");  
           greetList.add(swahiliGreet);  
             
           return greetList;  
      }  
 }  
   

Note that we have declared this class to be a Bean named "greetDAO_v1"
(Typically, DAO classes connect to a database to get data objects, but here we are create the objects on the fly - for the purpose of demo)

Next, update your HelloBean to make use of this DAO class, like this:

 package org.confucius;  
   
 import java.util.List;  
   
 import javax.faces.bean.ManagedBean;  
 import javax.faces.bean.ManagedProperty;  
 import javax.faces.bean.SessionScoped;  
   
 @ManagedBean(name="helloBean")  
 @SessionScoped  
 public class HelloBean {  
      private String greeting;  
        
      @ManagedProperty(value = "#{greetDAO_v1}")  
      private GreetDAO greetDAO;  
   
      public HelloBean(){  
           this.greeting = "Hello World JSF!";  
      }  
        
      public String getGreeting() {  
           return this.greeting;  
      }  
   
      public void setGreeting(String greeting) {  
           this.greeting = greeting;  
      }  
   
      public GreetDAO getGreetDAO() {  
           return greetDAO;  
      }  
   
      public void setGreetDAO(GreetDAO greetDAO) {  
           this.greetDAO = greetDAO;  
      }  
   
      public String getInternationalGreets() {  
           return "international_greets";  
      }  
   
      public String getAmericanGreets() {  
           return "american_greets";  
      }  
        
      public List<Greet> getInternationalGreetsList(){  
           return greetDAO.getInternationalGreetsList();  
      }  
   
 }  
   

We have injected the GreetDAO bean into this class, and are using its method to return a List of Greet objects.

If you rebuild and redeploy HelloWorldJSF, you will be able to see the data table showing languages and greets.

JSF did what is necessary to provide a GreetDAO class instance to HelloBean.

JSF - Richfaces Datatable

A very common scenario in UI is to show a data table.

Usually a method gets a list of objects from a database, and we need to present them as a table.

Let us use Richfaces to display such a data table.

In your /src/main/java/org/confucius, create a class called Greet.java like this:

 package org.confucius;  
 public class Greet {  
      private String language;  
      private String greeting;  
      public Greet(String language, String greeting) {  
           this.language = language;  
           this.greeting = greeting;  
      }  
      public String getLanguage() {  
           return language;  
      }  
      public void setLanguage(String language) {  
           this.language = language;  
      }  
      public String getGreeting() {  
           return greeting;  
      }  
      public void setGreeting(String greeting) {  
           this.greeting = greeting;  
      }  
 }  

In your HelloBean.java, create a new method which returns a List of Greet objects, like this:

 package org.confucius;  
 import java.util.ArrayList;  
 import java.util.List;  
 import javax.faces.bean.ManagedBean;  
 import javax.faces.bean.SessionScoped;  
 @ManagedBean(name="helloBean")  
 @SessionScoped  
 public class HelloBean {  
      private String greeting;  
      public HelloBean(){  
           this.greeting = "Hello World JSF!";  
      }  
      public String getGreeting() {  
           return this.greeting;  
      }  
      public void setGreeting(String greeting) {  
           this.greeting = greeting;  
      }  
      public String getInternationalGreets() {  
           return "international_greets";  
      }  
      public String getAmericanGreets() {  
           return "american_greets";  
      }  
      public List<Greet> getInternationalGreetsList(){  
           List<Greet> greetList = new ArrayList<Greet>();  
           Greet englishGreet = new Greet("English", "Hello World!");  
           greetList.add(englishGreet);  
           Greet frenchGreet = new Greet("French", "Bonjour tout le Monde!");  
           greetList.add(frenchGreet);  
           Greet italianGreet = new Greet("Italian", "Buongiorno a Tutti!");  
           greetList.add(italianGreet);  
           Greet spanishGreet = new Greet("Spanish", "Hola Mundo!");  
           greetList.add(spanishGreet);  
           Greet swahiliGreet = new Greet("Swahili", "Jambo!");  
           greetList.add(swahiliGreet);  
           return greetList;  
      }  
 }  

Update your international_greets.xhtml to display the Greet objects in a data table, like this:

 <!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:rich="http://richfaces.org/rich">  
       <ui:composition template="/templates/template.xhtml">  
           <ui:define name="title">International Greets</ui:define>  
           <ui:define name="content">  
                <h:form>  
               <rich:dataTable value="#{helloBean.internationalGreetsList}" var="greet" id="greettable">  
                      <rich:column>  
                        <f:facet name="header">  
                          <h:outputText value="Language"/>  
                                  </f:facet>  
                     <h:outputText value="#{greet.language}"/>                  
                      </rich:column>  
                      <rich:column>  
                        <f:facet name="header">  
                          <h:outputText value="Greeting"/>  
                                  </f:facet>  
                     <h:outputText value="#{greet.greeting}"/>                  
                      </rich:column>  
               </rich:dataTable>  
                </h:form>  
           </ui:define>  
   </ui:composition>  
 </html>  

The Richfaces DataTable will automatically iterate through the list, and populate the table.

If you rebuild and redeploy HelloWorldJSF, you will see a DataTable displaying all the greets in different languages.