I'm using the Myfaces tree
component as a menu in an extensive application for showing/editing company
data. The tree nodes are initialized with company data as a root node.
Then children -> departments -> workplaces -> employees etc.
Each of the nodes link to specific JSP
datapages within the company. I'm quite new to implementing a complete JSF
solution and have several problems with the one I'm
currently working on.
Can anybody show me how
to use a JSF tree component to achieve these goals:
-The tree is going to
behave like a menu, where every node points to a page.
-The tree should not
lose it's current open/close state when loading a new page
-When clicking one
of the nodes in the tree, it should be able to pass request parameters to the
page that is loading.
JSF
MAINPAGE
The tree is implemented in a JSF page "mainpage.jsp"
like this:
<body>
<f:view>
<table
align="center"
border="1" width="800" height="600">
<tr valign="top" height="60">
<td colspan="2" bgcolor="#9999FF">
<f:subview id="topMenu">
<c:import url="top.jsp"/>
</f:subview>
</td>
</tr>
<tr
valign="top">
<td width="300">
<%-- THE TREE COMPONENT --%>
<f:subview id="treeMenu">
<x:tree id="tree" value="#{TreeMenu.tree}"
styleClass="tree"
nodeClass="treenode"
selectedNodeClass="treenodeSelected"
expandRoot="true">
<x:treeSelectionListener type="mycompany.menus.TreeMenu"/>
</x:tree>
</f:subview>
</td>
<td width="500">
<table border="0" align="center">
<tr align="center">
<td align="center">
<%--
THE MAIN BLOCK, CONTAINS OTHER JSF DATAPAGES
--%>
<f:subview id="${sessionScope.render}">
<c:import url="${sessionScope.render}"/>
</f:subview>
</td>
</tr>
</table>
</td>
</tr>
<tr>
<td colspan="2" align="center" bgcolor="#9999FF" height="20">
<f:subview id="footer">
<c:import url="footer.jsp"/>
</f:subview>
</td>
</tr>
</f:view>
</table>
THINGS TO NOTICE IN THE PRECEDING CODE:
-The code is a
mix of standard HTML, JSF and JSTL tags (I would like to know if this creates
some sort of unwanted side effects)
-The main block of the table is going to
show a JSF datapage in the center of the users screen and it is initialized via
a session parameter containing the correct URL of that JSP
Other
solutions that were tested:
-Having several subviews defined in the file and
then initializing which one to render in view via the JSF component render
attribute. The treeMenu backing bean would then have to include a
boolean for every page in the site. I could not get this working
because the attribute does not seem to be read at page reload. Therefore the
first pageview was always shown even though the boolean values were changed in
the backing bean.
THE BACKING BEAN -
TreeMenu
faces-config.xml:
<managed-bean>
<managed-bean-name>TreeMenu</managed-bean-name>
<managed-bean-class>mycompany.menus.TreeMenu</managed-bean-class>
<managed-bean-scope>session</managed-bean-scope>
</managed-bean>
Class implementation:
There are two helper classes
heavily used in the tree, they are:
SiteNavigation - A class
which is put into the nodes as the userObject. The class contains the relative
URL that is going to be rendered and any parameters that needs to be set in the
request/session to that datapage. The getRealURL() of this class returns the
complete URL relative to the installation.
RequestParameter
- A simple class with two string values "paramName" &
"param"
public class TreeMenu implements
TreeSelectionListener
{
private DefaultTreeModel
tree;
public
TreeMenu()
{
super();
this.init();
//DEFINES DEFAULT
RENDER PAGE WHEN MAINPAGE IS FIRST
SHOWN
FacesContext.getCurrentInstance().getExternalContext().getSessionMap().
put("render","datapages/statsPage.jsp");
}
public void init()
{
c = new
CompanyHandler().getCompany();
//*************************ROOT
NODE********************************
root = new
DefaultMutableTreeNode(c.getName());
SiteNavigation rootNav = new
SiteNavigation();
rootNav.setURL("datapages/statsPage.jsp");
rootNav.setName(c.getName());
root.setUserObject(rootNav);
//*************************ORGANIZATION
NODE*************************
organizationNode = new
DefaultMutableTreeNode("Organization");
SiteNavigation orgNav = new
SiteNavigation();
orgNav.setURL("datapages/organization.jsp");
orgNav.setName("Organization");
organizationNode.setUserObject(orgNav);
root.insert(organizationNode);
//SEVERAL MORE NODES
ARE ADDED AFTER THIS.....
//EXAMPLE WITH
SITENAVIGATION REQUESTPARAMETERS
//ONE DEPARTMENT WORKPLACE
Workplace wp
= (Workplace) wpIterator.next();
DefaultMutableTreeNode wpNode = new DefaultMutableTreeNode(wp
.getName());
SiteNavigation wpNav = new
SiteNavigation();
wpNav.setName(wp.getName());
wpNav.setURL("datapages/workplacePage.jsp");
wpNav.getParameters().add(new RequestParameter("wid",
wp.getId().toString()));
wpNav.getParameters().add( new RequestParameter("depid",
dep.getId().toString()));
wpNode.setUserObject(wpNav);
//END OF INIT
-> All NODES HAVE BEEN ADDED TO
THE ROOT
tree = new
DefaultTreeModel(root);
}
public void valueChanged(TreeSelectionEvent
event)
{
DefaultMutableTreeNode node = (DefaultMutableTreeNode)
event
.getNewSelectionPath().getLastPathComponent();
if (node ==
null)
{
log.debug("NO NODE");
}
else
{
try
{
Object nodeInfo =
node.getUserObject();
SiteNavigation s = (SiteNavigation)
nodeInfo;
//PUT DATAPAGE VIEW INTO SESSION
PARAMETER
FacesContext.getCurrentInstance().getExternalContext().getSessionMap().put("render",
s.getRealURL());
//ADD PARAMETERS IF
ANY
Iterator i =
s.getParameters().iterator();
while
(i.hasNext())
{
RequestParameter rp = (RequestParameter)
i.next();
//I
TRIED THIS FIRST, BUT THE REQUEST VALUES NEVER REACH THE "render" JSP
PAGE/BACKINGBEAN
//FacesContext.getCurrentInstance().getExternalContext().getRequestMap().put(rp.getParamName(),
rp.getParam());
FacesContext.getCurrentInstance().getExternalContext()
.getSessionMap().put(rp.getParamName(),
rp.getParam());
}
}
catch
(Exception
e)
{
log.error("ERROR IN menuTree
valueChanged",e);
}
}
}
}
CURRENT PROBLEMS WITH THE SOLUTION:
1. Request values that are being set in the TreeMenu backing
bean are not being fed to the backing bean of the datapage referenced in
the c:import->sessionScope.render. Instead I have writen the values into a
session parameter, which in turn is a bad idea seeing as many of the pages will
use the same parameter and parameter name. I will have to remember to
reinitialize these parameters if they are written/read to other
places.
2. If a different backing bean (for example a datapage)
writes a variable into the session.render attribute this value is never updated
in the rendering of the mainpage. Example of datapage backing bean write:
FacesContext.getCurrentInstance().getExternalContext().getSessionMap().put("render",
"datapages/employee.jsp"); This means that if a backing bean writes ie.
"datapages/employee.jsp" to the render session parameter, the employee.jsp page
will be shown for the rest of the session even if you click other nodes in the
tree. This makes the tree completely useless as the mainpage main
block never updates.
Currently I have yet been able to find a
complete tutorial on how to actually use the Myfaces tree component. What I
need now is comments and solutions to the problems listed here, hopefully
without me needing to rewrite much of the code. I know about the Myfaces example
application and code, much of this is based on that. However that example does
not show how to navigate using the nodes it only tells you how to fill it with
data.
QUESTIONS
1. What is the correct way of
writing request values from a backing bean? Is
"FacesContext.getCurrentInstance().getExternalContext().getRequestMap().put("render",
"datapages/employee.jsp");" ok code?
2. Would it be
possible to implement the myfaces tree within a frame based solution
instead?
3. Are there any other tree componentes ready
for use out there? I have not yet tried the component from http://www.ourfaces.net, it seems much more
complicated than the Myfaces solution. Anybody tried it? Please give me an
extended example if you have.
If there is too little information in
this post to comment on, please let
me know and I will try to update it.