Seam Tutorial 1.2: RichFaces and paged table (datascroller)
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.- Double click on richTable.xhtml in Eclipse to open it in the JBoss Tools HTML Editor.
- 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.
- 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.
<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" />
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.- Download richfaces-demo-3.1.4.GA-tomcat6.war
- Download Tomcat 6.0.18.
- 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...).
- 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.).
- Start Tomcat.
- 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 .
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:
|
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.
@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" >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.
<begin-conversation join="true"/> </page>
Summary
- We marked the component as belonging to the Conversation scope applying the annotation @Scope(ScopeType.CONVERSATION) in the .java file
- 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.
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<String> 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.
* <p>
* See Seam Reference 2.0.2 SP1, chapter 4.8. Factory and manager components.
*/
@Factory("rowList")
public void initRowList() {
rowList = new LinkedList<String>();
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 :-(