Create new RichFaces Documentation Jira issue

This will launch the RichFaces Jira page - to complete your feedback please login if needed, and submit the Jira.

JBoss.orgCommunity Documentation

4.3. Navigation tree

The <rich:tree> component takes one of the main places in the Photo Album and is tightly bound with the application logic. It helps to represent and implement inherently the "Shelves - Albums" hierarchy. Shelf is the highest possible level in the tree hierarchy, that is used to group thematic albums and may contain as many albums as needed.

There are two types of navigation tree in the application: for a registered user and for a guest. The difference between them is that the first one has a context menu and drag-and-drop possibility.

Navigation tree for a guest is represented as a simple <rich:tree> component.

There are several ways to implement the <rich:tree> on a page. In the current application the <rich:tree> is designed using a model tag <rich:treeNodesAdaptor>.

The <rich:treeNodesAdaptor> component has a "nodes" attribute that accepts a collection of elements, so <rich:treeNodesAdaptor> iterates over the collection and renders a hierarchical tree structure on a page.

According to the "Shelves - Albums" hierarchy we need two nested <rich:treeNodesAdaptor> components. The first one iterates over the Shelves collection that is returned from the getPredefinedShelves() method of ShelfManager.java class:

...

public List<Shelf> getPredefinedShelves() {
        if (shelves == null) {
            shelves = shelfAction.getPredefinedShelves();
        }
        return shelves;
    }
} 
...

The second <rich:treeNodesAdaptor> component iterates over the Albums collection of the current Shelf which is available via "var" attribute. The "var" attribute is used to get access to the data object of the current collection element Shelf, so it is possible to output any necessary data. Let's see the src/main/webapp/includes/index/tree.xhtml file:


...
<rich:tree adviseNodeOpened="#{treeManager.adviseNodeSelected}"
            adviseNodeSelected="#{treeManager.adviseNodeSelected}"
            ajaxSubmitSelection="false" id="PreDefinedTree"
            treeNodeVar="treeNode" switchType="client"
            iconCollapsed="/img/shell/tree_icon_plus.png"
            iconExpanded="/img/shell/tree_icon_minus.png"
            showConnectingLines="false">
      <rich:treeNodesAdaptor nodes="#{shelfManager.getPredefinedShelves()}" var="shelf">
            <rich:treeNode style="cursor:pointer" reRender="treePanel,mainArea" selectedClass="tree-selected-node">
                  <f:facet name="icon">
                        <h:graphicImage style="border: none" value="/img/shell/tree_icon_shelf.png">
                              <a4j:support reRender="treePanel, mainArea" event="onclick" actionListener="#{controller.showShelf(shelf)}" similarityGroupingId="sel" />
                        </h:graphicImage>
                  </f:facet>
                  <a4j:outputPanel  >
                        <h:outputText style="cursor:pointer" value="#{shelf.name}" />
                        <h:outputText value=" :: " />
                        <strong>#{shelf.unvisitedImages.size()}</strong> new
                        <a4j:support reRender="treePanel, mainArea" event="onclick" actionListener="#{controller.showShelf(shelf)}" similarityGroupingId="sel" />
                  </a4j:outputPanel>
            </rich:treeNode>
            
            <rich:treeNodesAdaptor var="album" nodes="#{shelf.albums}">
                  <rich:treeNode style="cursor:pointer" reRender="treePanel,mainArea" selectedClass="tree-selected-node" icon="img/shell/tree_icon_album.png">
                        <f:facet name="iconLeaf">
                              <h:graphicImage style="border: none" value="img/shell/tree_icon_album.png">
                                    <a4j:support reRender="treePanel, mainArea" event="onclick" actionListener="#{controller.showAlbum(album)}" similarityGroupingId="sel" />
                              </h:graphicImage>
                        </f:facet>
                        <a4j:outputPanel>
                              <h:outputText style="cursor:pointer" value="#{album.name}" />
                              <h:outputText value=" :: " />
                              <strong>#{album.unvisitedImages.size()}</strong> new
                              <a4j:support reRender="treePanel, mainArea" event="onclick" actionListener="#{controller.showAlbum(album)}" similarityGroupingId="sel" />
                        </a4j:outputPanel>
                  </rich:treeNode>
            </rich:treeNodesAdaptor>
      </rich:treeNodesAdaptor>
</rich:tree>
...

The image below shows how the navigation tree for a guest is rendered on the page.


As it was mentioned before a navigation tree for a registered user has two main features: drag-and-drop and context menu. Context menu is described in the "Context menu" chapter.

Drag-and-drop feature supported in the Photo Album application is not so complicated as it may seem from the first view. In this application we can mark out two types of drag-and-drop: one type takes place only inside the tree (between tree nodes) and another one - between the watching area and the tree. The difference is not considerable enough to describe two types separately, but also not at all insignificant to be omitted here.

The tree related components (<rich:tree> and <rich:treeNode>) have their own attributes that provide drag-and-drop functionality. These attributes can be divided into two groups: those which provide drag (dragValue, dragListener, dragIndicator, dragType attributes) and those which provide drop operations (dropValue, dropListener, acceptedTypes, typeMapping).

Note:

Due to "Shelves - Albums - Photos" hierarchy we can say that photos could be moved between albums, albums could be moved between shelves. To avoid a mishmash, it's not allowed to place photos directly in shelves as well as nesting shelves inside shelves or albums inside albums.

Let's explore how drag-and-drop works for albums.

All albums, that are represented as TreeNodes, must be marked somehow for dragging. For this purpose we use previously mentioned "dragValue", "dragType" attributes. Let's have a look at the src/main/webapp/includes/index/tree.xhtml file:


<rich:treeNodesAdaptor var="album" nodes="#{shelf.albums}">
      <rich:treeNode style="cursor:pointer" 
                        reRender="mainArea, treePanel" 
                        dragType="album" 
                        dragValue="#{album}" 
                        dropValue="#{album}" 
                        acceptedTypes="image" 
                        selectedClass="tree-selected-node" 
                        icon="img/shell/tree_icon_album.png">
            ...
            <rich:dndParam name="label" type="drag" value="#{album.name}" />
            ...
      </rich:treeNode>
</rich:treeNodesAdaptor>

To provide drop functionality for the marked albums, we should mark Shelves as drop zones in the application code too. For this purpose we add the "dropValue" and "acceptedTypes" attributes to the "Shelf" node in the same src/main/webapp/includes/index/tree.xhtml file:


<rich:treeNodesAdaptor nodes="#{shelfManager.getUserShelves()}" var="shelf">
      <rich:treeNode style="cursor:pointer" acceptedTypes="album" dropValue="#{shelf}" reRender="mainArea, treePanel" selectedClass="tree-selected-node">
            ...
      </rich:treeNode>
</rich:treeNodeAdaptor>

The "acceptedTypes" attribute tells the "Shelf" node what types of dragged zones (albums in this case) it can accept. We have specified "Album" node "dragType" as "album", so the "Shelf" node can accept it.

Finally in order to process drop on the server side we need to specify a listener for the <rich:tree> in the "dropListener" attribute (src/main/webapp/includes/index/tree.xhtml file):


<rich:tree adviseNodeOpened="#{treeManager.adviseNodeSelected}"
                  adviseNodeSelected="#{treeManager.adviseNodeSelected}"
                  ajaxSubmitSelection="false" dragIndicator="dragIndicator" 
                  treeNodeVar="treeNode" switchType="client"
                  iconCollapsed="/img/shell/tree_icon_plus.png"
                  iconExpanded="/img/shell/tree_icon_minus.png"
                  dropListener="#{dndManager.processDrop}"
                  showConnectingLines="false">
            ...
</tree>

The code for the <rich:dragIndicator> looks like the following:


<rich:dragIndicator id="dragIndicator" />

The processDrop() method of DnDManager.java class is shown in the listing below:

...

public void processDrop(DropEvent dropEvent) {
      Dropzone dropzone = (Dropzone) dropEvent.getComponent();
      Object dragValue = dropEvent.getDragValue();
      Object dropValue = dropzone.getDropValue();
      if(dragValue instanceof Image){
            if(!((Album)dropValue).getOwner().getLogin().equals(user.getLogin())){
                  Events.instance().raiseEvent(Constants.ADD_ERROR_EVENT, Constants.DND_PHOTO_ERROR);
                  return;
            }
            handleImage((Image)dragValue, (Album)dropValue);    
      }else if(dragValue instanceof Album){
            if(!((Shelf)dropValue).getOwner().getLogin().equals(user.getLogin())){
                  Events.instance().raiseEvent(Constants.ADD_ERROR_EVENT, Constants.DND_ALBUM_ERROR);
                  return;
            }
            handleAlbum((Album)dragValue, (Shelf)dropValue);
      }
}
...

Here is the whole example of the "Navigation tree for a registered user":


<h:panelGroup id="tree" rendered="#{identity.hasRole('admin')}" layout="block">
        <a4j:commandLink actionListener="#{controller.selectShelves()}" reRender="mainArea, treePanel"><h2><h:outputText  value="My shelves:"/></h2></a4j:commandLink><br/>
    <rich:dragIndicator
            id="dragIndicator" />
        <rich:tree 
            adviseNodeOpened="#{treeManager.adviseNodeSelected}"
            adviseNodeSelected="#{treeManager.adviseNodeSelected}"
            ajaxSubmitSelection="false" dragIndicator="dragIndicator" 
            treeNodeVar="treeNode" switchType="client"
            iconCollapsed="/img/shell/tree_icon_plus.png"
            iconExpanded="/img/shell/tree_icon_minus.png"
            dropListener="#{dndManager.processDrop}"
            showConnectingLines="false">
             
                <f:facet name="icon">
                    <h:graphicImage style="border: none" value="/img/shell/tree_icon_shelf.png">
                            <a4j:support reRender="treePanel, mainArea" event="onclick" actionListener="#{controller.showShelf(shelf)}" similarityGroupingId="sel" />
                    </h:graphicImage>
                </f:facet>
                <ui:include src="/includes/contextMenu/CMForShelf.xhtml" >
                    <ui:param name="shelf" value="#{shelf}" />
                </ui:include>
                <a4j:outputPanel>
                    <h:outputText style="cursor:pointer" value="#{shelf.name}" />
                    <h:outputText value=" :: " />
                    <strong>#{shelf.unvisitedImages.size()}</strong> new
                    <a4j:support reRender="treePanel, mainArea" event="onclick" actionListener="#{controller.showShelf(shelf)}" similarityGroupingId="sel" />
                </a4j:outputPanel>
                </rich:treeNode>

                <rich:treeNodesAdaptor var="album"
                    nodes="#{shelf.albums}">
                    <rich:treeNode style="cursor:pointer" reRender="mainArea, treePanel" dragType="album"
                        dragValue="#{album}" dropValue="#{album}"
                        acceptedTypes="image"
                        selectedClass="tree-selected-node"
                        icon="img/shell/tree_icon_album.png">
                        <f:facet name="iconLeaf">
                            <h:graphicImage style="border: none" value="img/shell/tree_icon_album.png">
                                    <a4j:support reRender="treePanel, mainArea" event="onclick" actionListener="#{controller.showAlbum(album)}" similarityGroupingId="sel" />
                            </h:graphicImage>
                        </f:facet>
                        <ui:include src="/includes/contextMenu/CMForAlbum.xhtml" >
                        <ui:param name="album" value="#{album}" />
                        </ui:include>
                        <rich:dndParam name="label" type="drag" value="#{album.name}" />
                        <a4j:outputPanel >
                            <h:outputText style="cursor:pointer" value="#{album.name}" />
                            <h:outputText value=" :: " />
                                <strong>#{album.unvisitedImages.size()}</strong> new
                        <a4j:support reRender="treePanel, mainArea" event="onclick" actionListener="#{controller.showAlbum(album)}" similarityGroupingId="sel" />
                        </a4j:outputPanel>
                </rich:treeNode>

                </rich:treeNodesAdaptor>
            </rich:treeNodesAdaptor>

        </rich:tree>
        </h:panelGroup>

The image below shows how the described above drag-and-drop features are rendered in the Photo Album.


Visit following pages at RichFaces Live Demo for more information, examples and sources on the components used in the application and described in this chapter: