的clientsidetoggle这个属性来选择你使用的是客户端的树或者服务端的树,<t:tree2>的clientsidetoggle这个属性来选择你使用的是客户端的树或者服务端的树,<t:tree2 clientsidetoggle="false" ...>将会使用服务端的树,属性值设为true将会使用客户端的树,默认值为true。
backing bean:
tree2组件对backing bean中的一个treemodel进行操作。通常情况,你只要把这个treemodel绑定到这个组件上就可以了,就像这样:
<t:tree2 value="#{myhandler.treemodel}"
下面需要建立一个类myhandler,在faces-config.xml中的managed bean配置成myhandler,在例子中这个类提供了一个方法gettreemodel()用于返回一个treemodel用于表示你的数据。
public class myhandler {
public treemodel gettreemodel() {
......
}
}
treemodel实际上是对treenode实例进行了一些简单的包装。
treenode是一个接口,其中和tree2相关有四个方法:
string gettype()
boolean isleaf()
list getchildren()
int getchildcount()
其它方法都没有什么用处了,可能会在今后的版本中取消。它们要求开发者在开发backing bean中做一些并不必要的操作。
int getchildcount() 方法返回这个节点的子结点数量,这个方法了很容易的采用如下的方式实现:
public final int getchildcount() {
return getchildren().size();
}
该方法的出现使得对于子结点的延迟加载变的可行。该方法的实现只需要返回该节点的子结点数量,而不需要返回每个子结点的实例。
boolean isleaf() 方法在该节点没有子节点的时候返回true。这样,一个很直截了当的实现可以这样:
public final boolean isleaf() {
return getchildren().isempty();
}
不管你提供了什么样的实现,在任何时间任何情况下你都得保持getchildren().isempty() ==> isleaf()。isleaf()方法实际上控制了节点被怎样呈现:是否被当做树叶节点(不能被继续展开)。
string gettype() 方法决定了用怎样的方式来呈现这个节点。在 jsf 页面中,可以在 <t:tree2> 的tag 中嵌套facet,jsf将会选出与 gettype() 方法返回值同名的 facet 用于呈现。如果该节点没有找到相符的 facet,将会导致一个错误,并且这个方法不会返回null。
list getchildren() 方法返回一个 list,其中包含了该节点下所有的 treenode,这就表示这些节点将被呈现为该节点下的子结点。该 list 不能包含 null,如果该 list 的大小和getchildcount()不符,将会报错。子结点将按照它们在 list 中的顺序呈现出来。
改变tree中的内容
(当展开树的节点时,在后台延迟加载)
在邮件列表中有很多关于这项任务的问题和讨论,我(marcel,一个 jsf 的初学者)在这里总结一下。如果你有更好的解决方案,请更新这些文字。
在这里存在的一个问题就是我要这样把“+”图标去掉:
· <t:tree2 ... shownav="false" ...>
然后再让文件夹图标(代表包含子节点的节点)变的可点击:
· <h:commandlink action="#{t.toggleexpanded}" actionlistener="#{navigationbacker.processaction}">
然后在 java 代码中接受鼠标点击的事件。在 navigationbacker.java 文件中的processaction(actionevent e) 方法里,我从 ejb3-persistency 中加载子结点的数据。
不好的是“+”图标变的不可见,但是我现在没有办法获取点击“+”图标的事件。
看起来在org.apache.myfaces.custom.tree2.htmltree.java这个文件里是通过注册了_expandcontrol = new htmlcommandlink(); 从内部获取“+”的点击事件,但是我现在没有办法从我的代码中接受到这一事件。
为了导航,我使用了含有entries的treenode.getidentifier() (参见:#{node.identifier}),看起来就是这个样子:
· db_id="car_id=7,person_id=2"
这代表了后台数据库表的主键(我还没有找到一个更好的解决方案用于导航)
程序代码如下:
navigation.jsp
<t:tree2 id="servertree" value="#{navigationbacker.treedata}"
var="node" varnodetoggler="t" clientsidetoggle="false" shownav="false"
showrootnode="false">
<f:facet name="project-folder">
<h:panelgroup>
<h:commandlink action="#{t.toggleexpanded}" actionlistener="#{navigationbacker.processaction}">
<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>
<h:commandlink action="#{navigationbacker.toviewid}"
styleclass="#{t.nodeselected ? 'documentselected':'document'}"
actionlistener="#{navigationbacker.nodeclicked}"
value="#{node.description}" immediate="true">
<f:param name="db_id" value="#{node.identifier}" />
</h:commandlink>
<h:outputtext value=" (#{node.childcount})" styleclass="childcount"
rendered="#{!empty node.children}" />
</h:panelgroup>
</f:facet>
<f:facet name="person-folder">
<h:panelgroup>
navigationbacker.java
/** *//**
* 拦截节点被展开的事件,并加载额外的数据
* @param event
* @throws abortprocessingexception
*/
public void processaction(actionevent event) throws abortprocessingexception {
system.out.println("entering processaction()");
uicomponent component = (uicomponent) event.getsource();
while (!(component != null && component instanceof htmltree)) {
component = component.getparent();
}
if (component != null) {
htmltree tree = (htmltree) component;
treenodebase node = (treenodebase) tree.getnode();
if (!tree.isnodeexpanded() && node.getchildren().size() == 0) {
map<string, string> map = splitkeyvalues(node.getidentifier()); // 一些辅助代码,用于将 "car_id=7" 或 "car_id=7&person_id=12" 拆分开
this.car_id = map.get("car_id");
if (this.car_id != null) {
appendpersonsnodes(node); // 参见下面的例子
}
this.person_id = map.get("person_id");
if (this.person_id != null) {
appendlicensesnodes(node); // 没有显示
}
}
}
}
/** *//** 把当前car_id下的person子结点加入导航中 */
private void appendpersonsnodes(treenodebase cardetailnode) {
variableresolver resolver = facescontext.getcurrentinstance().getapplication().getvariableresolver();
personstable personstable = (personstable) resolver.resolvevariable(facescontext.getcurrentinstance(),
"personstable");
list<person> personslist = personstable.getcarpersons();
for (person o : personslist) {
list<treenodebase> list = cardetailnode.getchildren();
list.add(new treenodebase("person-folder", o.getdescription(),"person_id=" + o.getpersonid(), true));
}
system.out.println("navigationbacker fetched " + personslist.size() + " persons for carid=" + this.car_id);
}
这里有一段辅助代码用于从 h:commandlink 中获取 f:param 用于多种用途。
/** *//**
* 当 jsf 组件 h:commandlink 包含有 f:param 成员, 这些 name-value 对被放到
* request 参数表中供后面的action handler使用。不幸的是,这样的用法不能用在
* h:commandbutton上。我们没有办法把通过 button 来传递这些参数。
*
* 因为 action listeners 可以保证在 action 方法前被执行到,所以 action listeners
* 可以调用该方法更新 action 方法所需要的任何上下文。
*
* from http://cvs.sakaiproject.org/release/2.0.0/
* sakai2/gradebook/tool/src/java/org/sakaiproject/tool/gradebook/jsf/facesutil.java
* educational community license version 1.0
*/
public static final map geteventparametermap(facesevent event) {
map<string, string> parametermap = new hashmap<string, string>();
list children = event.getcomponent().getchildren();
for (iterator iter = children.iterator(); iter.hasnext();) {
object next = iter.next();
if (next instanceof uiparameter) {
uiparameter param = (uiparameter) next;
parametermap.put(param.getname(), "" + param.getvalue());
}
}
//system.out.println("parametermap=" + parametermap);
return parametermap;
}
注:在上面的例子里,backing bean都存放于 session 作用域里,可以在web-inf/examples-config.xml 中进行配置。