JSF MyFaces tree2 component usage

There were mutltiple challenges in creating a tree view for the organisation hierarchy that we needed. As we were using JSF-myfaces so the obvious choice of the component was myfaces tree2 component.

Firstly, on the startup time, I needed to load the tree with all the root level of organisations;
secondly, on the clicking on the node, its suborganisations were supposed to be loaded and the page on the right side of separator must also be directed towards “Subordinate Organizations” page;
thirdly, on the click of the suborganizations at the right side of the separator the tree must be properly updated with its children;
fourthly, on second time click of the selected node, its edit profile page must be opened;
lastly, all the cosmetics like bold font for selected node, auto expansion after step 2 returns the response and different icons usage for different types of organisations etc.

Lets see how we met these challenges. NOT to forget that we took 2 honest weeks to finish off this thing:

First Challenge:
Wii needed to define the class TreeUtility with two properties

private TreeModelBase orgUnitHier;
private TreeStateBase treeState;

to keep track of the data part of the myfaces-tree2 component at the server side <these two object types ofcourse belong to the tree2 related classes>. orgUnitHier is the backing bean value part of the tree2 tag and treeState is kept only at the server side and updated in the orgUnitHier where needed. Wii needed to define two more properties to self maintain the extra tree2 state info

private List clickedNodes;
private String selectedNodeId;

as the name suggests, clickedNodes list would add the TreeNodeBase.getIdentifier() of every node that is clicked the first time. selectedNodeId would save/update itself with node.getIdentifier() for the currently clicked node
Ok then to manipulate the tree state wii needed to define two managed beans in faces-config.xml

 <managed-bean> 
  <managed-bean-name>treeState</managed-bean-name> 
  <managed-bean-class>org.apache.myfaces.custom.tree2.TreeStateBase</managed-bean-class> 
  <managed-bean-scope>session</managed-bean-scope> 
 </managed-bean> 
 <managed-bean> 
  <managed-bean-name>treeUtility</managed-bean-name>  <managed-bean-class>com.paybytouch.iboard.web.organization.TreeUtility</managed-bean-class> 
  <managed-bean-scope>session</managed-bean-scope> 
 </managed-bean>

so when the treeUtility object got instantiated by the JSF framework we did initialize our root organizations in its constructor.

    public TreeUtility(){ 
     FacesContext ctx = FacesContext.getCurrentInstance(); 
        Application app = ctx.getApplication(); 
        ValueBinding contactListBind = app.createValueBinding("#{treeState}"); 
        TreeStateBase treeStateBase = (TreeStateBase)contactListBind.getValue(ctx); 
        setTreeState(treeStateBase); 
        setClickedNodes(new ArrayList()); 
 ... 
     initializeTree(); 
    }

initializeTree is a simple method

    private void initializeTree() 
    { 
     HierarchyBean hierarchyBean = (HierarchyBean)sessionManager.getFromFacesContext("hierarchyBean"); 
     TreeNode organizations = new TreeNodeBase("Organizations","Organizations","0",false); 
     hierarchyBean.getRootMerchant(); 
     translateNodeHierarchyToView(hierarchyBean.getLstOfHierarcyBeanNode(), organizations);    

     addToClickedNodes(organizations); 
     setOrgUnitHier(new TreeModelBase(organizations)); 
     }

In the above method hierarchyBean is a JSF managed bean, related to our own domain, with all the organizations’ and their hierarchy in it. Wii are setting a dummy “Organizations” node as the root node that I won’t show at the view. The definition of method translateNodeHierarchyToView() will come later.
So the first challenge is met!
Second challenge:
In the TreeView.jsp within every facet I added

<h:commandLink action="#{treeUtility.treeNodeClicked}" ... 
 <f:param name="nodeId" value="#{node.identifier}" /> 
</h:commandLink>

to invoke the treeNodeClicked() method of TreeUtility. Now here’s the definition of this method

    public String treeNodeClicked() 
    { 
     FacesContext facesCxt = FacesContext.getCurrentInstance(); 
     ExternalContext context= facesCxt.getExternalContext(); 
     Map reqMap = context.getRequestParameterMap(); 
     String strNodeId =(String) reqMap.get("nodeId"); 
     TreeNodeBase node = getNodeByIdentifier(strNodeId,     (TreeNodeBase)orgUnitHier.getNodeById("0")); 
     String pageToBeDisplayed = populate(node, true); 
//     orgUnitHier.getTreeState().setSelected(node.getIdentifier()); 
     selectedNodeId = node.getIdentifier(); 
     addToClickedNodes(node); 
     return pageToBeDisplayed; 
    }

The first four lines are showing how to extract a paramater from request map through facesContext then the exact node is searched based on the strNodeId within the tree starting from root like this

 private TreeNodeBase getNodeByIdentifier(String argID, TreeNodeBase argRootNode){    

  if(argID.equals(argRootNode.getIdentifier())){ 
   return argRootNode; 
  } 
  else{ 
   for(int i=0 ; i<argRootNode.getChildCount() ; i++){ 
    TreeNodeBase node = getNodeByIdentifier(argID,((TreeNodeBase)argRootNode.getChildren().get(i))); 
    if(node != null) 
     return node; 
   } 
  } 
  return null; 
 }

I know there are similarly named methods in the tree2 component related classes but guess what? The brilliance of tree2 component demands that none of those methods should work so wii wrote our own!
As in every assignment there’s always one big hakuna! let me introduce u to our grand daddy populate() method

    private String populate(TreeNodeBase argTreeNodeBase, boolean isTreePop) 
    { 
     String fullNodePathVal = ""; 
     TreeNodeBase child; 
     HierarchyBean hierarchyBean = (HierarchyBean)sessionManager.getFromFacesContext("hierarchyBean");     if(isClickedForSecondTime(argTreeNodeBase)) { 
  ... 
     } 
 ... 
     if(hierarchyBean.getStrSetPageHeading().equals(HierarchyBean.NO_SUBORDINATE_ORGANIZATION)) 
      return NavigationController.HIERARCHY.getTargetPage();    

     translateNodeHierarchyToView(hierarchyBean.getLstOfHierarcyBeanNode(), argTreeNodeBase); 
     //expanding selected node in html tree    

     htmlTree = fetchHtmlTreeComponent(); 
 ...    

     return NavigationController.HIERARCHY.getTargetPage(); 
    }

now comes the definition of translateNodeHierarchyToView()

    private void translateNodeHierarchyToView(ArrayList argHierarchyNodeBeans, TreeNode argTreeNodeBase) 
    { 
     TreeNode orgUnit = null; 
     for(int i = 0 ; i < argHierarchyNodeBeans.size() ; i++ ){ 
      orgUnit = new TreeNodeBase( 
        ((HierarchyNodeBean)argHierarchyNodeBeans.get(i)).getType(), 
        ((HierarchyNodeBean)argHierarchyNodeBeans.get(i)).getName(), 
        (((HierarchyNodeBean)argHierarchyNodeBeans.get(i)).getNodeId()).toString(), 
        false); 
      argTreeNodeBase.getChildren().add(orgUnit); 
     } 
    }

simple thing; it translates domain hierarchyBean structure to tree2 related treeNode state structure. It picks every node from domain object and sets it in the tree nodes children. The return expression NavigationController.HIERARCHY.getTargetPage() returns the string as listed in the faces-config.xml to which the page must be forwarded.
This pretty much completes our challenge#2
Second Challenge met

Third challenge:
I didn’t modify the current flow of control of the already developed pages so I had to plug my tree related flow into the existing flow

    public void hierarchyNodeClicked(String argStrNodeId) 
    { 
     TreeNodeBase node = getNodeByIdentifier(argStrNodeId); 
     populate(node, false); 
    //     orgUnitHier.getTreeState().setSelected(node.getIdentifier()); 
     selectedNodeId = node.getIdentifier(); 
     addToClickedNodes(node);    

    }

Onclick of suborganizations link a controller method is called and that method in turn calls hierarchyNodeClicked() method. The parameter of populate() method isTreePop is useless for our tree2 usage understanding.
Fourth challenge:
To meet this challenge, Wii needed to maintain the array of clicked nodes. Heres the method addToClickedNodes()

 private void addToClickedNodes(TreeNode argTreeNode){ 
  for(int i = 0 ; i < clickedNodes.size() ; i++) 
   if(argTreeNode.getIdentifier().equals(clickedNodes.get(i))) 
    return ; 
  clickedNodes.add(argTreeNode.getIdentifier()); 
 }

and heres the code of first part of the populate method that I skipped in its initial definition

    private String populate(TreeNodeBase argTreeNodeBase, boolean isTreePop) 
    { 
     String fullNodePathVal = ""; 
     TreeNodeBase child; 
     HierarchyBean hierarchyBean = (HierarchyBean)sessionManager.getFromFacesContext("hierarchyBean"); 
     if(isClickedForSecondTime(argTreeNodeBase)) { 
      if(isAlreadySelected(argTreeNodeBase)) { 
       RetailController retailController = (RetailController)sessionManager.getFromFacesContext("retailController"); 
          if(argTreeNodeBase.getType().equals(RetailOrgType.MERCHANT.getName())) 
           return retailController.loadMerchant(); 
          if(argTreeNodeBase.getType().equals(RetailOrgType.GROUP.getName())) 
           return retailController.loadGroup(); 
          if(argTreeNodeBase.getType().equals(RetailOrgType.STORE.getName())) 
           return retailController.loadStore(); 
      }    

        hierarchyBean.onClickOfOrganizationLinkInTreeView(argTreeNodeBase.getIdentifier()); 
        return NavigationController.HIERARCHY.getTargetPage(); 
     } 
 ... 
}

and heres the definition of two conditional methods

 private boolean isClickedForSecondTime(TreeNodeBase argNode){ 
  ArrayList clickedNodes = (ArrayList)getClickedNode(); 
  for(int i = 0 ; i < clickedNodes.size() ; i++) 
   if(((String)clickedNodes.get(i)).equals(argNode.getIdentifier())) 
    return true; 
  return false; 
 }    

 private boolean isAlreadySelected(TreeNode argClickedNode){ 
  return selectedNodeId.equals(argClickedNode.getIdentifier()); 
  //return getOrgUnitHier().getTreeState().isSelected(argClickedNode.getIdentifier()); 
 }

Fourth & Last Challenges met
Last Challenge:
The cosmetics were also time consuming
Auto-expansion of selected node and heres the code of second part of the populate method that I skipped in its initial definition

    private String populate(TreeNodeBase argTreeNodeBase, boolean isTreePop) 
    { 
 ... 
     //expanding selected node in html tree    

     htmlTree = fetchHtmlTreeComponent(); 
     fullNodePathVal = "0"; 
     fullNodePathVal = getNodePathByIdentifier(argTreeNodeBase.getIdentifier(), 
           (TreeNodeBase)orgUnitHier.getNodeById("0"), fullNodePathVal);    

     htmlTree.expandPath(htmlTree.getPathInformation(fullNodePathVal));    

     return NavigationController.HIERARCHY.getTargetPage(); 
    }

Now I need to explain the HTML node expansion mechanism. By default onclick of every node, which is a link, after the loading of the nodes children the node doesn’t expand and the plus sign on the nodes left needs to be clicked. For that purpose HtmlTree needs to be given a proper node path starting from the root like 0:0:1 means root, root’s first child, root’s first child’s second child. Let me give the definition of two notable methods here fetchHtmlTreeComponent() and getNodePathByIdentifier()

 private String getNodePathByIdentifier(String argID, TreeNodeBase argRootNode, String nodePath) 
 { 
  String nodePathTemp = "";    

  if(argID.equals(argRootNode.getIdentifier())) { 
   return nodePath; 
  } 
  else{ 
   for(int i=0 ; i<argRootNode.getChildCount() ; i++){ 
    nodePathTemp = (!"".equals(nodePath)) ? nodePath + TreeModel.SEPARATOR + i: Integer.toString(i); 
    nodePathTemp = getNodePathByIdentifier(argID, 
          ((TreeNodeBase)argRootNode.getChildren().get(i)), nodePathTemp); 
    if(nodePathTemp != null) 
     return nodePathTemp; 
   } 
  } 
  return null; 
 } 
 private HtmlTree fetchHtmlTreeComponent() 
 { 
  FacesContext context = FacesContext.getCurrentInstance(); 
     UIComponent component = context.getViewRoot(); 
     HtmlTree tree; 
     String id = "tree_subview:serverTree"; 
     tree = (HtmlTree) findComponent(component, id, context, false);    

     return tree; 
 }    

 private UIComponent findComponent(UIComponent component, String id, 
            FacesContext context, boolean exactMatch) { 
        String componentId = component.getClientId(context); 
        if(exactMatch) { 
         if (componentId.equals(id)) return component; 
        } else { 
         if (componentId.contains(id)) return component; 
        }    

        Iterator kids = component.getChildren().iterator(); 
        while (kids.hasNext()) { 
            UIComponent kid = (UIComponent) kids.next(); 
            UIComponent found = findComponent(kid, id, context, exactMatch); 
            if (found != null) return found; 
        } 
        return null; 
    } 
The method findComponent() is taken from JSF Core book.

Different icons for different types of nodes

  <f:subview id="tree_subview"> 
    <t:tree2 id="serverTree" value="#{treeUtility.orgUnitHier}" var="node" varNodeToggler="t" clientSideToggle="false" showRootNode="false"> 
    ... 
           </f:facet> 
     <f:facet name="Merchant"> 
               <h:panelGroup> 
                   <t:graphicImage value="../images_/yellow-folder-open.png" rendered="#{t.nodeExpanded}" border="0"/> 
                   <t:graphicImage value="../images_/yellow-folder-closed.png" rendered="#{!t.nodeExpanded}" border="0"/> 
                <h:commandLink action="#{treeUtility.treeNodeClicked}" value="#{node.description}" ... > 
 ... 
        </h:commandLink> 
                   <h:outputText value=" (#{node.childCount})" styleClass="childCount" rendered="#{!empty node.children}"/> 
               </h:panelGroup> 
           </f:facet> 
           <f:facet name="Store"> 
               <h:panelGroup> 
           </f:facet> 
           <f:facet name="Group"> 
               <h:panelGroup> 
           </f:facet>

The tag <t:graphicImage value=”../images_/yellow-folder-open.png” … in every facet is used to set different icons for different node types.
Remember the <f:facet name=”Merchant”> name field must match the type set in the TreeNodeBase of every node of the tree. To set the bold font of the selected node I needed to do

                <h:commandLink action="#{treeUtility.treeNodeClicked}" value="#{node.description}" immediate="true"  actionListener="#{t.setNodeSelected}" styleClass="#{t.nodeSelected ? 'documentSelected':'document'}">

To mention the number of children at the right side of every node wii did

<h:commandLink ... > 
 ... 
        </h:commandLink> 
<h:outputText value=" (#{node.childCount})" styleClass="childCount" rendered="#{!empty node.children}"/>

next to every node’s commandLink.
I did most of the work in third, fourth and last challenge for others a colleague helped. I believe I’ve done a lot to deserve praise but thats the last thing I’ll be looking for. All in all working in the JSF-myfaces was a very dirty experience. There’s a lot of funcionality available but no docs and I love to read docs.

Here’s the complete code of the jsp file and the java file that are specific to JSF myfaces tree2 functionality. Ignore the commented code if u can. View these files to get a complete picture of the code.

TreeView.jsp

TreeUtility.java

Advertisements

4 Comments

  1. This makes no sense to me. I am not sure if that is because it’s written poorly, or that the semantics (at least with regard to the JSF) are not in sync with those that I a familiar with.

    I am working with extending tree2 (standard) as well, and will keep posted.

    Eric

  2. Yes, you are right Eric in both accounts: the example is presented poorly too, putting application specific login within and the myfaces tree2 semantics are sh*tty at best.

    However, I believe extending it would help just like extending a badly written API doesn’t improve its usability. In fact a clean implementation of some kind like tree3 needs to be provided. But, this all shows the weakness of myfaces with regard to UI component usability designing.

  3. I need some help about this item…. any of you can help me?

  4. I’m frequently looking for new posts in the WWW about this matter. Thanks.


Comments RSS TrackBack Identifier URI

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