Archetype: RichFaces 4.1.0.Final + Spring 3.1.0.RELEASE + Hibernate 3.3.0

This maven2 archetype contains a little sample web application with this Frameworks:

  • Spring 3.1.0.RELEASE Framework
  • Servlet-API 2.5
  • JSF 2.0 (mojarra 2.1.2)
  • RichFaces 4.1.0.Final
  • ploinFaces 1.6
  • ploinMailFactory 1.3.1
  • Hibernate 3.3.0
  • TestNG 5.8
  • Log4J 1.2.15
  • HSQLDB 1.8.0.7

The configuration is annotation-driven. It is deployed on the PLOIN Repository-Server

http://www.ploin-m2.de/nexus/content/groups/public/

you can create a project from the archetype with the following command:

mvn archetype:generate -DarchetypeGroupId=org.ploin.archetype -DarchetypeArtifactId=demoSpringRichHibernate-archetype -DarchetypeVersion=1.2 -DarchetypeRepository=http://www.ploin-m2.de/nexus/content/groups/public/ -DgroupId=com.versioneye -DartifactId=myNewWebApp

The created project is a very simple web-application with a login mask and 2 xhtml-sites. I have tested the app on a tomcat 6.0.35 and Java 1.6 on Mac OS X Lion.

On the second page there is a small demonstration of the JSF 2.0 AJAX feature. Every time you type something in into the first input field, there is a whole server roundtrip. The ManagedBean gets updated immediately.

On the same page there is a small demo for the RichFaces calendar.

After the creation the app is running with the HypersoniceSQL DBMS. But it is very easy to switch to MySQL or Oracle. I put the drivers for MySQL and Oracle as a comment in the pom.xml. So you just need to comment in the right lines in the pom.xml.

Archetype: RichFaces 4.0.0.M1 + Spring 3.0.5.RELEASE + Hibernate 3.3.0

This maven2 archetype contains a little sample web application with this Frameworks:

  • Spring 3.0.5.RELEASE Framework
  • Servlet-API 2.5
  • JSF 2.0 (mojarra 2.1.2)
  • RichFaces 4.0.0.M1
  • ploinFaces 1.6
  • ploinMailFactory 1.3.1
  • Hibernate 3.3.0
  • TestNG 5.8
  • Log4J 1.2.15
  • HSQLDB 1.8.0.7

The configuration is annotation-driven. It is deployed on the PLOIN Repository-Serve

http://www.ploin-m2.de/nexus/content/groups/public/

you can create a project from the archetype with the following command:

mvn archetype:generate -DarchetypeGroupId=org.ploin.archetype -DarchetypeArtifactId=tempSpringRichHibernate -DarchetypeVersion=1.7.2 -DarchetypeRepository=http://www.ploin-m2.de/nexus/content/groups/public/ -DgroupId=org.ploin -DartifactId=demoSpringRichHibernate

The created project is a very simple web-application with a login mask and 2 xhtml-sites. I have tested the app on a tomcat 6.0.20 and Java 1.6 on Mac OS X Snow Leopard.

rich-faces-login
rich-faces-login

You can login with the username “admin” and the password “admin”.

After the creation the app is running with the HypersoniceSQL DBMS. But it is very easy to switch to MySQL or Oracle. I put the drivers for MySQL and Oracle as a comment in the pom.xml. So you just need to comment in the right lines in the pom.xml.

Probleme mit nodeSelectListener in rich:tree

In der Baumkomponente von RichFaces ist es möglich einen “nodeSelectListener” zu registrieren.

<rich:tree switchType="ajax"
           binding="#{managedBean[binding]}"
           componentState="#{managedBean[state]}"
           ajaxSubmitSelection="true"
           nodeSelectListener="#{managedBean[nodeListener]}"  >
  <rich:recursiveTreeNodesAdaptor id="adapter"
                                  roots="${managedBean[treeModel]}"
                                  var="item"
                                  nodes="#{item.childs}" >
    <rich:treeNode id="node"  >
      <h:outputText value="#{item.name}"  />
    </rich:treeNode>
  </rich:recursiveTreeNodesAdaptor>
</rich:tree>

Der Listener wird gefeuert, wenn ein Element des Baumes selektiert bzw. angeklickt wird. Jetzt habe ich festgestellt, dass die registrierte Methode nur beim ersten Klick auf das Baumelement aufgerufen wird. Bei jedem weiteren Klick auf das selektierte Element wird zwar ein Request abgeschickt und der Lifecycle durchlaufen, jedoch wird die Methode nicht aufgerufen.

Das ist ja mal wieder ganz toll. So wie ich das sehe ist der Name “nodeSelectListener” falsch gewählt. Dem verhalten nach zu urteilen ist das eher ein “changeNodeSelecttionListener”.

Ein möglicher Workarround besteht dadrin, auf den “nodeSelectListener” zu verzichten und in der treeNode einen commandLink zu plazieren.

<rich:tree switchType="ajax"
           binding="#{managedBean[binding]}"
           componentState="#{managedBean[state]}"
           ajaxSubmitSelection="true"  >
  <rich:recursiveTreeNodesAdaptor id="adapter"
                                  roots="${managedBean[treeModel]}"
                                  var="item"
                                  nodes="#{item.childs}" >
    <rich:treeNode id="node"  >
      <h:commandLink action="#{treeBean.doSelectNode}"  value="#{item.name}"  />
    </rich:treeNode>
  </rich:recursiveTreeNodesAdaptor>
</rich:tree>

NodeSelectListener and dynamic expand with rich:tree

Dieser Blogeintrag zeigt wie ein Baum in RichFaces aufgebaut wird. Auf dem Baum wird ein nodeSelectListener registriert um auf die Selektion von Baumelementen reagieren zu können. Außerdem wird der Baum, von einem HTML Button aus, an einem bestimmten Knoten aufgeklappt.

Das Datenmodell für den Baum ist eine User-Klasse, die wiederum eine Liste von Usern hält.

public List<User> getTree(){
  User node1 = new User("Mike Rene");
  User node2 = new User("Hans Wurst");
  User node3 = new User("Paul Paul");
  User node4 = new User("Johannes Mueller");
  User node5 = new User("Andreas Schmidt");
  User node6 = new User("Julia Meier");
  User node7 = new User("Anette Nette");
  User node8 = new User("Rosa Linde");

  node6.getChilds().add(node7);
  node6.getChilds().add(node8);

  node4.getChilds().add(node5);

  node1.getChilds().add(node2);
  node1.getChilds().add(node3);
  node1.getChilds().add(node4);

  List<User> elements = new ArrayList<User>();
  elements.add(node1);
  elements.add(node6);

  return elements;
}

Um den Baum zu rendern, wird folgender Code in der XHTML/JSP Seite benötigt:

<rich:tree id="tree"
           nodeSelectListener="#{treeBean.doSelect}"
           ajaxSubmitSelection="true"
           reRender="selectedName"
           switchType="ajax"  >
  <rich:recursiveTreeNodesAdaptor id="adapter"
                                  roots="#{treeBean.tree}"
                                  var="item"
                                  nodes="#{item.childs}" >
      <rich:treeNode id="node" >
          #{item.name}
      </rich:treeNode>
  </rich:recursiveTreeNodesAdaptor>
</rich:tree>

Es reicht nicht aus, einfach den “nodeSelectListener” auf dem Baum zu registrieren! Es muss zusätzlich noch die Property “ajaxSubmitSelection” auf “true” gesetzt sein, damit der Listener aufgerufen wird.

In anderen JSF-Bibliotheken reicht es ja aus, einfach einen Listener zu registrieren. Aber ich habe mich mitlerweile dran gewöhnt, dass man RichFaces alles zweimal sagen muß, bevor es anfängt seine Arbeit zu tun.

Wenn alles geklappt hat, kann die Seite wie folgt aussehen:

Der entsprechende Listener in der ManagedBean sieht wie folgt aus:

public void doSelect(NodeSelectedEvent event){
    HtmlTree tree = ((HtmlTree) event.getComponent());
    rowkey = tree.getRowKey();
    System.out.println("RowKey in doSelect: " + rowkey);

    if (rowkey == null)
        return ;

    User rowData = (User)tree.getRowData(rowkey);
    System.out.println("RowData in doSelect: " + rowData.getName());
    selectedName = rowData.getName();
}

us dem Event wird die Komponente geholt. Von dieser wiederum wird der selektierte RowKey geholt, um diesen im nächsten Aufruf wieder an RichFaces zu übergeben, damit man das aktuelle User-Objekt erhält. Also einfach ist anders. Eine Methode “getSelectedRowData()” hätte es vielleicht auch getan.

Um den Baum an einer ganz bestimmten Stelle programatisch auf zu klappen, bedarf es gleich zwei Komponenten-Bindings.

private HtmlTree htmlTree;
private TreeState treeState;

Auf der XHTML Seite muss rich:tree um folgende zwei Propertys erweiter werden:

binding="#{treeBean.htmlTree}"
componentState="#{treeBean.treeState}"

Um von eimen “h:commandButton” aus den dritten Knoten auf der ersten Ebene auf zu klappen, muss folgende Action aufgerufen werden:

public String doExpandTree(){
    try{
        UITree uiTree = (UITree)htmlTree;
        StackingTreeModelKey<Integer> segment0 = new StackingTreeModelKey<Integer>("adapter", 0);
        StackingTreeModelKey<Integer> segment2 = new StackingTreeModelKey<Integer>("adapter", 2);
        List<StackingTreeModelKey> list = new ArrayList<StackingTreeModelKey>();
        list.add(segment0);
        list.add(segment2);
        TreeRowKey<StackingTreeModelKey> key = new ListRowKey<StackingTreeModelKey>(list);
        treeState.expandNode(uiTree, key);
    } catch (Exception ex){
        ex.printStackTrace();
    }
    return null;
}

Es ist in RichFaces nicht vorgesehen von “ausserhalb” einen Baum aufzuklappen. Mein erster Gedanke war, dass ich dem treeState einfach die uiTree und die entsprechende ID aus der HTML Seite übergeben. Aber so einfach ist das nicht! RichFaces erwartet als Key eine TreeRowKey Klasse. Ich habe mich einige Zeit lang durch den Quellcode von RichFaces wühlen müssen, bis ich rausgefunden habe wie die Klasse aufgebaut werden muß, damit sie das gewollte Verhalten an den Tag legt.

Die Klasse “StackingTreeModelKey” ist laut JavaDoc nur für den internen Gebrauch gedacht. Sie taucht nirgendwo in der offiziellen RichFaces Dokumentation auf. Das Lustige an der ganzen Geschichte ist, die “toString()” Methode von “ListRowKey” gibt genau die ID zurück die in der HTML Seite an dem Konten dran steht.

Nach dem klicken auf den Button sollte die Seite wie folgt aussehen:

Hier ist nochmal der vollständige Quellcode der xhtml Seite:

<f:view
 xmlns="http://www.w3.org/1999/xhtml"
 xmlns:ui="http://java.sun.com/jsf/facelets"
 xmlns:h="http://java.sun.com/jsf/html"
 xmlns:f="http://java.sun.com/jsf/core"
 xmlns:a4j="http://richfaces.org/a4j"
 xmlns:rich="http://richfaces.org/rich" >

<meta http-equiv="Content-Type"
 content="text/html; charset=UTF-8" >
</meta>

<html>
<head>
 <title>dynamic expand rich:tree</title>
</head>
<body>

 <h:form id="form"  >
 <br/>
 <br/>
 <h:commandButton action="#{treeBean.doExpandTree}"  value="doExpandTree"  />
 <br/>
 <br/>
 <rich:tree id="tree"
            binding="#{treeBean.htmlTree}"
            componentState="#{treeBean.treeState}"
            nodeSelectListener="#{treeBean.doSelect}"
            ajaxSubmitSelection="true"
            reRender="selectedName"
            switchType="ajax"  >
   <rich:recursiveTreeNodesAdaptor id="adapter"
                                   roots="#{treeBean.tree}"
                                   var="item"
                                   nodes="#{item.childs}" >
     <rich:treeNode id="node" >
       #{item.name}
     </rich:treeNode>
   </rich:recursiveTreeNodesAdaptor>
 </rich:tree>
 <br/>
 <br/>
 <a4j:outputPanel ajaxRendered="true" >
   <h:outputText id="selectedName" value="#{treeBean.selectedName}"  />
 </a4j:outputPanel>
 </h:form>

</body>
</html>
</f:view>

und hier, die vollständige Klasse:

package org.ploin.tsm.gui.bean;

import org.ploin.web.faces.BaseBean;
import org.richfaces.component.UITree;
import org.richfaces.component.html.HtmlTree;
import org.richfaces.component.state.TreeState;

import org.richfaces.event.NodeSelectedEvent;
import org.richfaces.model.ListRowKey;
import org.richfaces.model.StackingTreeModelKey;
import org.richfaces.model.TreeRowKey;

import java.util.ArrayList;
import java.util.List;

public class TreeBean extends BaseBean{

private HtmlTree htmlTree;
private TreeState treeState;

private Object rowkey;
private String selectedName = "nothing selected";

// ********* ACTIONS AND ACTIONLISTENER ****************

  public String doExpandTree(){
    try{
      UITree uiTree = (UITree)htmlTree;
      StackingTreeModelKey<Integer> segment0 = new StackingTreeModelKey<Integer>("adapter", 0);
      StackingTreeModelKey<Integer> segment2 = new StackingTreeModelKey<Integer>("adapter", 2);
      List<StackingTreeModelKey> list = new ArrayList<StackingTreeModelKey>();
      list.add(segment0);
      list.add(segment2);
      TreeRowKey<StackingTreeModelKey> key = new ListRowKey<StackingTreeModelKey>(list);
      treeState.expandNode(uiTree, key);
    } catch (Exception ex){
      ex.printStackTrace();
    }
    return null;
 }

  public void doSelect(NodeSelectedEvent event){
    HtmlTree tree = ((HtmlTree) event.getComponent());
    rowkey = tree.getRowKey();
    System.out.println("RowKey in doSelect: " + rowkey);

    if (rowkey == null)
      return ;

    User rowData = (User)tree.getRowData(rowkey);
    System.out.println("RowData in doSelect: " + rowData.getName());
    selectedName = rowData.getName();
  }

// ********* GETTER AND SETTER ****************

  public List<User> getTree(){
    User node1 = new User("Mike Rene");
    User node2 = new User("Hans Wurst");
    User node3 = new User("Paul Paul");
    User node4 = new User("Johannes Mueller");
    User node5 = new User("Andreas Schmidt");
    User node6 = new User("Julia Meier");
    User node7 = new User("Anette Nette");
    User node8 = new User("Rosa Linde");

    node6.getChilds().add(node7);
    node6.getChilds().add(node8);

    node4.getChilds().add(node5);

    node1.getChilds().add(node2);
    node1.getChilds().add(node3);
    node1.getChilds().add(node4);

    List<User> elements = new ArrayList<User>();
    elements.add(node1);
    elements.add(node6);

    return elements;
  }

  public HtmlTree getHtmlTree() {
    return htmlTree;
  }

  public void setHtmlTree(HtmlTree htmlTree) {
    this.htmlTree = htmlTree;
  }

  public TreeState getTreeState() {
    return treeState;
  }

  public void setTreeState(TreeState treeState) {
    this.treeState = treeState;
  }

  public String getSelectedName() {
    return selectedName;
  }

  public void setSelectedName(String selectedName) {
    this.selectedName = selectedName;
  }
}

Also, ist eigentlich ganz einfach. Man muß nur ein paar Bindings anbringen, ganz viel Code schreiben, ein paar Stunden im Quellcode von RichFaces rumwühlen und schon läuft die Geschichte.

Ich finde das ist alles viel komplizierter als es sein müsste. Wer das ganze mit weniger Quellcode eleganter lösen will, der sollte sich einfach an IceFaces halten.

RichFaces mit Maven2

Um RichFaces als aktuelle Maven2 Abhängigkeit zu verwenden, ist es erforderlich die JBoss M2 Repositories in der setting.xml (~/.m2/setting.xml) von Maven2 zu registrieren. Bei mir sieht die Datei wie folgt aus:

<?xml version="1.0" ?>
 <settings>
   <mirrors>

     <mirror>
        <id>ibiblio.org</id>
        <name>ibiblio Mirror of http://repo1.maven.org/maven2/</name>
        <url>http://mirrors.ibiblio.org/pub/mirrors/maven2</url>
        <mirrorOf>central</mirrorOf>
     </mirror>

   </mirrors>

   <activeProfiles>
      <activeProfile>RichFaces</activeProfile>
   </activeProfiles>

 <profiles>
   <profile>
     <id>RichFaces</id>
     <repositories>
       <repository>
         <releases>
           <enabled>true</enabled>
         </releases>
         <snapshots>
           <enabled>false</enabled>
           <updatePolicy>never</updatePolicy>
         </snapshots>
         <id>repository.jboss.com</id>
         <name>Jboss Repository for Maven</name>
         <url>http://repository.jboss.com/maven2/</url>
         <layout>default</layout>
       </repository>
       <repository>
         <releases>
           <enabled>false</enabled>
         </releases>
         <snapshots>
           <enabled>true</enabled>
           <updatePolicy>always</updatePolicy>
         </snapshots>
         <id>maven2-snapshots.jboss.com</id>
         <name>Jboss Repository for Maven Snapshots</name>
         <url>http://snapshots.jboss.org/maven2</url>
         <layout>default</layout>
       </repository>
    </repositories>

  <pluginRepositories>
     <pluginRepository>
       <id>maven2-snapshots.jboss.com</id>
       <name>Jboss Repository for Maven Snapshots</name>
       <url>http://snapshots.jboss.org/maven2</url>
       <releases>
         <enabled>false</enabled>
       </releases>
       <snapshots>
         <enabled>true</enabled>
         <updatePolicy>always</updatePolicy>
       </snapshots>
     </pluginRepository>
     <pluginRepository>
       <releases>
          <enabled>true</enabled>
       </releases>
       <snapshots>
         <enabled>false</enabled>
         <updatePolicy>never</updatePolicy>
       </snapshots>
       <id>repository.jboss.com</id>
       <name>Jboss Repository for Maven</name>
       <url>http://repository.jboss.com/maven2/</url>
       <layout>default</layout>
     </pluginRepository>
   </pluginRepositories>
 </profile>
</profiles>

</settings>

Wenn das erledigt ist, muss nur noch die entsprechende pom.xml um die folgende Abhängigkeit erweitert werden:

<dependency>
   <groupId>org.richfaces.ui</groupId>
   <artifactId>richfaces-ui</artifactId>
   <version>3.3.1.GA</version>
</dependency>

Wenn man noch Facelets statt den olen JSPs verwenden will, dann sollte zusätzlich noch folgender Eintrag in der pom.xml stehen:

 <dependency>
     <groupId>com.sun.facelets</groupId>
     <artifactId>jsf-facelets</artifactId>
     <version>1.1.14</version>
 </dependency>

Nach dem folgenden Aufruf:

mvn -DdownloadSources eclipse:eclipse

RichFaces 4.0.0.ALPHA1 + Spring 3.0.0.RELEASE + Hibernate 3.3.0

This maven2 archetype contains a little sample web application with this Frameworks:

  • Spring 3.0.0.RELEASE Framework
  • Servlet-API 2.5
  • JSF 2.0 (mojarra 2.0.2)
  • RichFaces 4.0.0.ALPHA1
  • ploinFaces 1.6
  • ploinMailFactory 1.3.1
  • Hibernate 3.3.0
  • TestNG 5.8
  • Log4J 1.2.15
  • HSQLDB 1.8.0.7

The configuration is annotation-driven. It is deployed on the PLOIN Repository-Serve

http://www.ploin-m2.de/nexus/content/groups/public/

you can create a project from the archetype with the following command:

mvn archetype:generate -DarchetypeGroupId=org.ploin.archetype -DarchetypeArtifactId=tempSpringRichHibernate -DarchetypeVersion=1.7 -DarchetypeRepository=http://www.ploin-m2.de/nexus/content/groups/public/ -DgroupId=org.ploin -DartifactId=demoSpringRichHibernate

The created project is a very simple web-application with a login mask and 2 xhtml-sites. I have tested the app on a tomcat 6.0.20 and Java 1.6 on Mac OS X Snow Leopard.

You can login with the username “admin” and the password “admin”.

After the creation the app is running with the HypersoniceSQL DBMS. But it is very easy to switch to MySQL or Oracle. I put the drivers for MySQL and Oracle as a comment in the pom.xml. So you just need to comment in the right lines in the pom.xml.