jakub holý

building the right thing, building it right, fast

Seam Tutorial 1.2: RichFaces and paged table (datascroller)

2009-03-23Portlets

In this two-part tutorial you will learn how to get started with the development of Seam applications with RichFaces using Eclipse with JBoss Tools. In the 1st part we've set up our environment, created, and run an empty shell Seam application. In this 2nd part we will create a simple web page with a table presenting data on multiple pages using Ajax (a RichFaces component) and its model stored as a POJO Component in the Seam Conversation scope. I assume that you already have some basic knowledge of Seam and JSF, for instance that you know what a Component or the Conversation scope are. I'll present my path to this goal with all the mistakes so that you too can learn from them.

My aim in this tutorial series is to create a Seam portlet displaying data in a paged (and ideally also sortable and filterable, but lets be realistic) table running in the Liferay portal.

Attempt 1: The straightforward version

First we will create a new Seam project in exactly the same way as in part 1.1, the only difference being that we will call it seamTutorial12. Or you can reuse the existing project. I will not repeat the steps here, you can refer to the previous post.

TIP: Cleaning of the jboss deploy folder doesn't work very well (event with the Clean button in the JBoss Server View). You may need to help it by undeploying all projects and manually deleting contents of deploy/ in the JBoss folder, which is, for a server named JBoss_4.2.3, <eclipse workspace>/.metadata/.plugins/org.jboss.ide.eclipse.as.core/JBoss_4.2.3/. An alternative is to create a new server.

Note: When working offline you may get exceptions due to www.w3.org being unreachable, which will prevent the deployment of jboss-portal.sar/portal-wsrp.sar/portal-wsrp.war/. This is nothing to worry about.

Create a component and a page

Assuming that you have the project seamTutorial12 created and running, we will add a new component and page to it for our experiments.

File > New > Seam Action
  • Type the Seam component name richTable and accept the defaults (it would be better to apply a reasonable naming standard but let's keep it simple).
  • Finish.
  • Note: We could also select Seam Form, which would create a page with a form and button.


This will create two files:

1. src/hot/org/domain/seamtutorial12/session/RichTable.java

package org.domain.seamtutorial12.session;

import org.jboss.seam.annotations.Name; import org.jboss.seam.annotations.In; import org.jboss.seam.annotations.Logger; import org.jboss.seam.log.Log; import org.jboss.seam.faces.FacesMessages;

@Name("richTable") public class RichTable { @Logger private Log log; @In FacesMessages facesMessages;

public void richTable() { //implement your business logic here log.info("richTable.richTable() action called"); facesMessages.add("richTable"); }

//add additional action methods }
2. WebContent/richTable.xhtml .

<!DOCTYPE composition PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<ui:composition xmlns="http://www.w3.org/1999/xhtml" xmlns:s="http://jboss.com/products/seam/taglib" xmlns:ui="http://java.sun.com/jsf/facelets" xmlns:f="http://java.sun.com/jsf/core" xmlns:h="http://java.sun.com/jsf/html" xmlns:rich="http://richfaces.org/rich" xmlns:a="http://richfaces.org/a4j" template="layout/template.xhtml">

<ui:define name="body">

<h:messages globalOnly="true" styleClass="message" />

<rich:panel> <f:facet name="header">richTable</f:facet> <h:form id="richTableForm"> <h:commandButton id="richTable" value="richTable!" action="#{richTable.richTable}" /> </h:form> </rich:panel>

</ui:define>

</ui:composition>

Opening the page in Eclipse will show an editor with a text and visual views of the page (notice that the selected tag – h:form – is emphasized in the visual view – the blue line):



Go to http://localhost:8080/seamTutorial12/richTable.seam to see it:



Clicking the button RichTable! should add a message “RichTable” to the page.

Define a table data model

To use a table we need the data to display in it. JSF requires data to be provided as an instance of DataModel however we will use a simple List and let Seam to wrap it into a DataModel. We will add the following snippet to RichTable.java (plus the necessary imports) to define the data model property/component rowList and to initialize in the class' constructor:
@DataModel
List<String> rowList = new LinkedList<String>();

public RichTable() { final Date now = new Date(); rowList.add("row 1 created on " + now); rowList.add("row 2 created on " + now); rowList.add("row 3 created on " + now); }

Create a RichFaces table

Finally we will create the rich table to display the data.
  1. Double click on richTable.xhtml in Eclipse to open it in the JBoss Tools HTML Editor.
  2. In the source view click inside the h:form and in the JBoss Tools Palette click on JBoss RichFaces – extendedDataTable. I've selected the extendedDataTable and not the basic rich:dataTable because the extended one has sorting and filtering built-in. (You can also add these features to the standard table using other components.) The table will be inserted at the cursor's location.

  3. You could continue with adding columns etc. in this manner but I leave this up to your playful nature and just provide the final code that the ones in a hurry can copy&paste.
The final code:

<h:form>

<rich:extendedDataTable width="483" id="richTable" rows="2" columnClasses="col" value="#{rowList}" var="row">

<f:facet name="header">

<rich:columnGroup> <h:column> <h:outputText styleClass="headerText" value="A Column" /> </h:column> </rich:columnGroup>

</f:facet>

<h:column><h:outputText value="#{row}" /></h:column>

</rich:extendedDataTable>

<rich:spacer height="30" />

<rich:datascroller id="richTableScroller" align="left" for="richTable" maxPages="20" />

</h:form>

Notice that I've removed the command button (h:commandButton), which we don't need, to make the code cleaner.

That's it! Notes:



  • We use a datascroller to have paging of the table via Ajax. The table is set to display at most 2 rows via its attribute rows=2 thus we should see the 3rd element of our data model on the next page of the table.

  • RichFaces (Ajax) components must be within a form because its function is based on submitting the closest enclosing form. (See the RichFaces Developer Guide for 3.1.4.GA, part 5.4.3. Data Processing Options ).

Run the application

Now we can run it. In the JBoss Server View right-click on seamTutorial12 > Full Publish (though incremental publish or waiting for JBoss tools to publish the change automatically should also work).

When you access http://localhost:8080/seamTutorial12/richTable.seam you will be surprised to see a Facelets Error page reading:



/richTable.xhtml @22,46 <rich:extendedDataTable> Tag Library supports namespace: http://richfaces.org/rich, but no tag was defined for name: extendedDataTable.

Catch #1: JBoss Tools Palette uses newer version of RichFaces than Seam itself and let you add a component not present in the runtime version.

The error message above indicates that Facelets can't find the tag extendedDataTable in the taglib rich. Indeed if you look in Eclipse in the Package Explorer into seamTutorial12 > Web App Libraries > richfaces-ui.jar/META-INF/rich.tld you will not find it there either. A look inside richfaces-ui.jar/META-INF/MANIFEST.MF reveals that this version of RichFaces is 3.1.4 GA and not the latest (as of 2/2009) 3.3.0.



Intermezzo: Get RichFaces demo 3.1.4 GA running

The RichFaces demo is the best way to learn what its components can do, how to do it, and to copy the code for that. The online demo is of the latest version but for Seam 2.0.2 SP1 you would need RichFaces demo 3.1.4 GA.
  1. Download richfaces-demo-3.1.4.GA-tomcat6.war
  2. Download Tomcat 6.0.18.
  3. Install it to the Tomcat. I've imported the .war into Eclipse (File > Import) as JSF Project From *.war, defined a Tomcat server (right click in JBoss Server View > New > Server > ...) , and added the project to the server (JBoss Server View > [your Tomcat server] > Add and Remove Projects...).
  4. Perhaps change the ports Tomcat uses so that you can have it running in parallel with JBoss: double click on the newly created Tomcat server in the JBoss Server View, change Ports e.g. by prepending 2 (-> 28080 etc.).
  5. Start Tomcat.
  6. Go to http://localhost:28080/Richfaces-demo-3.1.4.GA-tomcat6/richfaces/dataTable.jsf?c=dataTable (or :8080 if you haven't changed the port) and you shall see the demo .
I haven't managed to get the demo running under JBoss but this solution is fine.

Attempt 2: Page valid for the actual RichFaces version

Create a RichFaces table, version 2 – a basic rich:dataTable

Since RichFaces 3.1.4 doesn't have the lovely extendedDataTable we will give up on sorting and filtering and use the basic rich:dataTable. The change to the code is trivial, just replace extendedDataTable with dataTable.

<h:form>

<rich:dataTable width="483" id="richTable" rows="2" columnClasses="col" value="#{rowList}" var="row"> ...

</rich:dataTable>

...

</h:form>

Modify the .xhtml and save it, JBoss should pick the change up automatically. (Or do force its publication.) Reload http://localhost:8080/seamTutorial12/richTable.seam in your browser. You may need to wait for some time.



Certainly you wonder why the table is empty instead of showing our 3 rows (or actually the first two ones) . The answer is that at the time the table was being rendered, the data model component rowList was undefined.
Issue: Undefined data source during startup
When deploying and undeploying projects it may (will) happen that JBoss Tools get confused and forget to deploy the project's data source. Normally when you are adding a project, like seamTutorial12, to a server, you should be able and should do select also its data source, like /../seamTutorial12-ds.xml. If you haven't the choice or forget it you will see an error like the following one during the Seam application startup following its deployment:

ERROR [DatasourceConnectionProvider] Could not find datasource: java:/seamTutorial12Datasource

javax.naming.NameNotFoundException: seamTutorial12Datasource not bound

The solution is to deploy the data source manually:
  1. In the Package Explorer, right-click on /seamTutorial12/resources/seamTutorial12-ds.xml and select Make Deployable from the context menu, then select the proper target server from the window that pops up (JBoss_4.2.3)
  2. In the JBoss Server View. you should now see /seamTutorial12/resources/seamTutorial12-ds.xml below the server name and the server's status changed to Started, Republish (if it was started).
  3. After republishing or restarting (which may be safer) the server the error should have been gone. In the log you should see: INFO [ConnectionFactoryBindingService] Bound ConnectionManager 'jboss.jca:service=DataSourceBinding,name=seamTutorial12Datasource' to JNDI name 'java:seamTutorial12Datasource'
Notice: On the screenshot there is “Make Undeployable” instead of Deployable because it's been taken after the step #3, i.e. when already deployed.

Lesson learned 1: DataModel needs a Factory method unless the parent component is also used on the page

You may have a component that outjects another component as a data model via the annotation @DataModel on a property. However if the owning component (richTable) isn't used on a page where the data model is used, it won't be instantiated and therefore also there will be nobody to create and outject the data model component (rowList) itself. Seam is not clever or active enough to understand that to have the component rowList it must first instantiate richTable.

The solution is to provide a method annotated with @Factory("rowList") to tell Seam that to instantiate the rowList component, it must call this method (which will likely also require instantiation of its parent component, richTable). See Seam Reference 2.0.2 SP1, chapter 4.8. Factory and manager components.

Attempt 3: Tell Seam how to get an instance of rowList via a @Factory method

As explained above, we will provide a new method to tell Seam what to do when #{rowList} is referenced on a page and it doesn't exist yet. We will move here the initiation code from the parent component's constructor.

@Factory("rowList")

public void initRowList() {

rowList = new LinkedList<String>();

final Date now = new Date();

rowList.add("row1 created on " + now);

rowList.add("row2 created on " + now);

rowList.add("row3 created on " + now);

}

Publishing the change to the server and accessing http://localhost:8080/seamTutorial12/richTable.seam again will finally show the paged table (nearly) as expected. On the following screenshot you can see both the 1st and the 2nd page of the table, the red arrowhead emphasizing the current one:



Have you noticed anything strange? Not? Look carefully at the dates on both pages. You will see that on the first page there is “row 1 created on Sat Mar 21 09:04:27 GMT+01:00 2009” while on the second one “row 3 created on Sat Mar 21 09:06:22 GMT+01:00 2009”. The times differ even though all the rows were created at once in the initRowList method!

The explanation is obvious: When we move to the 2nd page of the table, it triggers a new request to the server and since the data model only lives during a request, a new one was created with a new creation time. Coming back to page 1 would show yet later time. The solution is of course to keep the list in a longer-living scope and since we've Seam, the best candidate is the Conversation scope.

Attempt 4: Moving the data model into the Conversation scope to survive across request

We want to create the rowList data model only once and then simply access this instance when a user moves through the table's pages. The motivation is clear – in a normal, non-tutorial scenario we'd load data from a database and wouldn't want to repeat this operation over and over again.

You've been nice readers and thus I'll tell you two important things right away without teasing you.



Lesson learned 2: POJO Components are in the Event (Request) scope by default

As opposed to EJB components, plain old Java object components are stored in the Event scope unless specified otherwise. Don't trust you friends, don't be lazy, and check the defaults by yourself :-)

If you're not sure what scope a component is in, you can check it:
  • In Eclipse in the Seam Component view (Window > Show View > Other > Seam > Seam Components)

  • In the server log during startup of a Seam web application, Seam prints all components it has found together with their scope and class.
To make the component conversation-scoped, we will modify RichTable.java as follows:

@Name("richTable") @Scope(ScopeType.CONVERSATION) public class RichTable {



Lesson learned 3: Conversation scope doesn't exist and behaves as Event scope unless you start a conversation explicitly.

It may surprise you (it did surprise me!) but marking a component as a conversation-scoped is not enough. That's because no conversation scope actually exists until created explicitly. Normally Seam creates a new conversation for each request and destroys it when the request ends, in the very same way as with the Event scope. To get a truly long-lived conversation you must start it explicitly.

You can start a conversation in several ways, usually you do it when an action method is called. But we've no such method, we need a conversation started as soon as somebody lands on the richTable.seam page. This is something that can be set in the page configuration file. You may have a global pages.xml but we will create a separate configuration file /seamTutorial12/WebContent/richTable.page.xml next to the page file itself (thanks to the matching names Seam will understand it's for richTable.xhtml):
<?xml version="1.0" encoding="UTF-8"?>
<page xmlns="http://jboss.com/products/seam/pages"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://jboss.com/products/seam/pages http://jboss.com/products/seam/pages-2.0.xsd"
      login-required="false"
      >

<begin-conversation join="true"/> </page>
The only important line is <begin-conversation join="true"/> that tells Seam to start a new or join an existing long-lived conversation when the page is accessed.

Summary

  1. We marked the component as belonging to the Conversation scope applying the annotation @Scope(ScopeType.CONVERSATION) in the .java file
  2. We requested that a real conversation is started whenever a users enters the page richTable.xhtml (.seam) by specifying begin-conversation join="true" in a new page configuration file richTable.page.xml.
Now you can once again go to http://localhost:8080/seamTutorial12/richTable.seam and you will see that the dates within rows do not change anymore no matter how you switch the table pages.

The final version

RichTable.java:

package org.domain.seamtutorial12.session;

import java.util.Date; import java.util.LinkedList; import java.util.List;

import org.jboss.seam.ScopeType; import org.jboss.seam.annotations.Factory; import org.jboss.seam.annotations.In; import org.jboss.seam.annotations.Logger; import org.jboss.seam.annotations.Name; import org.jboss.seam.annotations.Scope; import org.jboss.seam.annotations.datamodel.DataModel; import org.jboss.seam.faces.FacesMessages; import org.jboss.seam.log.Log;

@Name("richTable") @Scope(ScopeType.CONVERSATION) public class RichTable {

@Logger private Log log;

@In FacesMessages facesMessages;

@DataModel List&lt;String&gt; rowList;

/** * We must provide this factory method to init the rowList instead of * initiating it e.g. in a constructor because otherwise it would be * uninitialized until this component itself - tichTable - is also * used by the page using rowList. * &lt;p&gt; * See Seam Reference 2.0.2 SP1, chapter 4.8. Factory and manager components. */ @Factory("rowList") public void initRowList() { rowList = new LinkedList&lt;String&gt;(); final Date now = new Date(); rowList.add("row 1 created on " + now); rowList.add("row 2 created on " + now); rowList.add("row 3 created on " + now);

log.info("initRowList called at " + now); }

}


richTable.xhtml:


<!DOCTYPE composition PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
                             "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
                xmlns:s="http://jboss.com/products/seam/taglib"
                xmlns:ui="http://java.sun.com/jsf/facelets"
                xmlns:f="http://java.sun.com/jsf/core"
                xmlns:h="http://java.sun.com/jsf/html"
                xmlns:rich="http://richfaces.org/rich"
                xmlns:a="http://richfaces.org/a4j"
                template="layout/template.xhtml">

<ui:define name="body">

<h:messages globalOnly="true" styleClass="message"/>

<rich:panel> <f:facet name="header">richTable</f:facet>

<h:form> <rich:dataTable width="483" id="myRichTable" rows="2" columnClasses="col" value="#{rowList}" var="row">

<f:facet name="header"> <rich:columnGroup> <h:column> <h:outputText styleClass="headerText" value="A Column" /> </h:column> </rich:columnGroup> </f:facet>

<h:column> <h:outputText value="#{row}" /> </h:column>

</rich:dataTable>

<rich:spacer height="30" />

<rich:datascroller id="richTableScroller" align="left" for="myRichTable" maxPages="20" /> </h:form>

</rich:panel>

</ui:define>

</ui:composition>


richTable.page.xml


<?xml version="1.0" encoding="UTF-8"?>
<page xmlns="http://jboss.com/products/seam/pages"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://jboss.com/products/seam/pages http://jboss.com/products/seam/pages-2.0.xsd"
      login-required="false"
      >

<begin-conversation join="true"/> </page>

Troubleshooting tips

RichFaces monitoring and troubleshooting

There are two tools you can use for monitoring Ajax requests and RichFaces behavior. The first one is the RichFaces tag a:log that will display detailed log about what RichFaces is doing, once it actually starts doing something:

<a:log level="ALL" popup="false" width="400" height="200"/>

I've found this tag useful but not sufficient. I also haven't got popup=true working (likely a mistake on my part).

With the addition of the Firefox extension FireBug, a JavaScript/CSS/... debugger and monitor and one of the best free web development tools, you'll have all you need. Notice that you will fist need to enable FireBug for the site (localhost) to monitor. Below you can see FireBug in action observing Post parameters of an Ajax request (tab “XHR”):

Tracking Seam conversations

How do you find out whether an action triggered a new conversation or remained in an existing conversation? By checking whether the request parameter cid has changed it's number or not or wasn't a part of the request at all. This parameter carries a numerical ID of the current conversation and is always present and unchanged if a user action is within an existing conversation. (But notice above that the Ajax request for switching a table page captured by FireBug doesn't have any such attribute – perhaps some Seam-RichFaces magic.)

Summary

We have created a conversation scoped Seam component holding a list of items that are displayed in a paged RichFaces table. We have also learned what the correct version of RichFaces is and how to get its demo running and about all the steps required to really get a component into a persistent conversation scope. Additionally we've found out what to do when the project's data source isn't deployed correctly to the server and got to know some troubleshooting tools.

Update 2009-04-21: Hotdeploy Seam app with Maven

There is an interesting Maven plugin (under active development) that can perform a hot-deploy of a Seam application to JBoss: Seam Hotdeploy Maven Plugin.

Resources

You can download the complete seamTutorial12 Eclipse projects, namely the project itself and its companion Seam-generated *-test project. Notice that the login, home, etc. pages have been generated by Seam and aren't actually needed for the richTable stuff though their removal would require some modifications.

PS: Please excuse the terrible formatting :-(