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.

2 thoughts on “NodeSelectListener and dynamic expand with rich:tree

  1. Hallo Robert,

    ich bin leider momentan auf RichFaces angewiesen und benötige genau die Funktion des externen Expands des Trees. Ich habe meinen Tree nicht über den RekursivNodeAdapter realisiert, sondern in meiner Bean ein TreeNode root, mit dem ich dynamisch meinen Tree aufbaue. Ich sehe leider in deiner doExpandTree()-Methode onicht ganz durch, da du ja auch statische Daten hast. Weißt du, wie ich von einem TreeNode seinen speziellen TreeRowKey erhalte?

    Vielen Dank für deine Hilfe!

    Mit freundlichen Grüßen

    Florian

  2. Hi Florian,

    zunächst mal mein Beileid, das du zur Zeit auf RichFaces angewiesen bist 🙂
    Ich habe seit über einem Jahr nichts mehr mit RichFaces zu tun. Ich kann dir auch nicht auf anhieb sagen wie du an die TreeRowKey ran kommst. Soweit ich weis kannst du den dynamisch zusammenbauen.
    Ich habe damals einfach im Sourcecode von RichFaces nachgeschaut. Das würde ich dir auch empfehlen. Es gibt keine bessere Referenz als den Sourcecode 🙂

    Viele Grüße,
    Rob

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s