Dorado 5 : 2.2.1.Tree(自定义树) (RF2)

简述

这儿的Tree特指Dorado开发中的自定义树,树的节点一般来说都是在视图模型配置文件中定义,也可以通过视图模型实现类,由java代码初始化。
对于系统开发中我们常常会遇到树的节点较多以及树节点的层次很深的情况,如果在视图模型中一次性初始化会耗费比较长的时间时,我们建议尽量用DataTree实现,DataTree可以自动实现树节点的延迟加载,该部分的实现办法参考DataTree的使用详解内容部分。当然了,用Tree组件也可以实现延迟加载,只要我们灵活利用Tree中的相关API就可以实现。

使用

使用方式一:通过视图模型配置文件设定

如果树节点相对来说是静态的,在应用发布后一般不会发生变动,如系统菜单等。这种数的节点,我们通常都是在视图模型的XML配置文件中完成Tree的节点设定,如下的配置文件中,我们可以通过dorado的IDE设定如下的配置代码:

<Control id="treeNav" type="Tree" width="100%" height="100%">
<TreeNode label="示例中心" expanded="true" icon="images/folder-closed.gif" expandedIcon="images/folder-opened.gif" >
<TreeNode label="dorado5新特性" >
<TreeNode label="BRich-Client端的MVC" />
<TreeNode label="全新设计的表格" />
<TreeNode label="高性能的表格" />
<TreeNode label="视图重用与门户" />
<TreeNode label="更易用的树" />
<TreeNode label="更实用的数据集" />
<TreeNode label="AJAX特性" />
</TreeNode>
<TreeNode label="控件体验" >
<TreeNode label="BRich对象模型" />
<TreeNode label="自定义数据表格" />
<TreeNode label="录入辅助" />
<TreeNode label="数据树" />
<TreeNode label="树型表格" />
<TreeNode label="日历" />
<TreeNode label="各种菜单" />
</TreeNode>
</TreeNode>
</Control>

该实现办法还可以参考doradosample中的例子

运行效果如下图:

图表 18

使用方式二:通过视图模型实现类定义tree中的节点

如果树中的节点信息在经常性的动态变化的,如组织结构等,在应用系统中往往会被设计为数据库中的关联表格描述这些关系,已经便于提供用户界面维护其中的数据,对于这种数据,我们直接用视图模型的xml配置文件静态设置其节点显然是不可取的。则我们可以覆盖视图模型实现类的initControls方法,在其中添加tree的节点,示例代码如下:

protected void initControl(Control control)
throws Exception {
super.initControl(control);

if ("treeNav".equals(control.getId()){
Tree treeNav = (Tree)control;

DefaultTreeNode menuRootNode = new DefaultTreeNode("示例中心");
menuRootNode.setIcon("images/folder-closed.gif");
menuRootNode.setExpandedIcon("images/folder-opened.gif");
menuRootNode.setExpanded(true);
treeNav.getRoot().addNode(menuRootNode);

DefaultTreeNode level1Node1 = new DefaultTreeNode("dorado5新特性");
level1Node1.setIcon("images/folder-closed.gif");
level1Node1.setExpandedIcon("images/folder-opened.gif");
level1Node1.setExpanded(true);
menuRootNode.addNode(level1Node1);

DefaultTreeNode brichNode = new DefaultTreeNode("BRich-Client端的MVC");
brichNode.setIcon("images/leaf.gif");
brichNode.setHasChild(false);
brichNode.setPath("basic.jsp");
level1Node1.addNode(brichNode);

DefaultTreeNode level1Node2 = new DefaultTreeNode("控件体验");
level1Node2.setIcon("images/folder-closed.gif");
level1Node2.setExpandedIcon("images/folder-opened.gif");
level1Node2.setExpanded(true);
menuRootNode.addNode(level1Node2);
}

}

以上的代码利用通过tree.getRoot()获得根节点,再通过节点的addNode方法添加DefaultTreeNode节点。递归添加直到完成树的初始化工作。

使用方式三:利用Dataset实现延迟加载树

对于树中节点层次很深的树,为了获取所有的节点构造树的过程中往往需要我们多次的访问数据库。而实际上用户操作这个树的时候却往往只关心其中的少部分树节点,根据二八原则,如果我们采用了延迟加载技术,对用户而言操作体验上没有变化,但是整体性能却提高了一个档次。对于这种延迟加载的实现技巧,我们可以利用dataset实现(当然整合度更好,编程量更少的是直接利用DataTree组件,详细使用参考DataTree说明部分)。
例如使用Tree实现组织结构树,我们可以定义一个特殊的dataset对象datasetNode,并定义其中的field为id,label, type等字段。
预先定义type字段区分树节点是分公司节点、部门节点还是员工节点,其值分别预设为branch,dept,employee。
接着我们定义一个Listener,将这个Listener绑定到datasetNode.Listener中实现beforeLoadData方法,代码如下:

public boolean beforeLoadData(Dataset dataset) throws Exception {
String id = dataset.parameters().getString("id");
String type = dataset.parameters().getString("type");
String nodeType = null;

if (StringHelper.isEmpty(type)){
List branchs = daoBranch.getALL();
dataset.fromDO(branchs);

nodeType = "branch";
}

dataset.moveFirst();
while (!dataset.isLast()){
dataset.setString("type", nodeType);
dataset.setRecordState(Record.STATE_NONE);
dataset.moveNext();
}
dataset.moveFirst();

return true;
}

利用这个Listener,我们只下载所有的分工司信息。并强制设置dataset的type字段的属性为branch。之后我们就可以在window的onload事件代码中初始化树的根节点:

datasetNode.disableControls();
try {
datasetNode.moveFirst();
while (!datasetNode.isLast()) {
var current = datasetDept.getCurrent();
var node = new DefaultTreeNode(current.getValue("label"));
node.id = current.getValue("id");
node.type = current.getValue("type");

treeOrg.addRootNode(node);
datasetNode.moveNext();
}
}
finally {
datasetNode.enableControls();
}

如此在页面初始化结束之后,Tree中既有了第一层节点,此时如果我们单击展开其中的一个branch类型的节点,我们希望获得部门信息并作为branch类型节点的子节点。则我们在tree的beforeExpandNode事件中编写如下代码:

datasetNode.parameters().setValue("id", node.id);
datasetNode.parameters().setValue("type", node.type);
datasetNode.flushData();
datasetNode.disableControls();
try {
datasetNode.moveFirst();
while (!datasetNode.isLast()) {
var current = datasetDept.getCurrent();
var newNode = new DefaultTreeNode(current.getValue("label"));
newNode.id = current.getValue("id");
newNode.type = current.getValue("type");

node.addNode(newNode);
datasetNode.moveNext();
}
}
finally {
datasetNode.enableControls();
}

其中node为beforeExpandNode事件,Tree传入的参数,表示当前被展开的节点,我们利用前面onload代码中初始化好的id和type信息,设定dataset的parameters,并调用flushData方法,使其向对应的listener发出数据请求。这样我们调整listener中的数据请求,使得代码可以支持部门数据下载。代码调整如下(新增蓝色代码):

public boolean beforeLoadData(Dataset dataset) throws Exception {
String id = dataset.parameters().getString("id");
String type = dataset.parameters().getString("type");
String nodeType = null;

if (StringHelper.isEmpty(type)){
List branchs = daoBranch.getALL();
dataset.fromDO(branchs);

nodeType = "branch";
}
if ("branch".equals(type)){
List depts = daoBranch.getDepts(id);
dataset.fromDO(depts);

nodeType = "dept";
}

dataset.moveFirst();
while (!dataset.isLast()){
dataset.setString("type", nodeType);
dataset.setRecordState(Record.STATE_NONE);
dataset.moveNext();
}
dataset.moveFirst();

return true;
}

蓝色代码根据beforeExpandNode方法传入的id和type获得部门列表,并作为数据导入dataset。Dataset的flushData执行结束后就可以获得上述的depts中的相关信息。于是在beforeExpandNode方法中,就利用dataset的数据遍历方法,并利用DefaultTreeNode的addNode方法将部门子节点动态的添加到branch类型的节点中。这样我们就完成了部门树节点的延迟加载工作。
基于以上实现原理,同样我们也可以实现员工节点的延迟加载。

常用技巧

技巧一:菜单树的实现

自定义树的树节点提供了一个path属性,在自定义树的默认功能设计中,当一个定义了path属性的节点被单击选择时,系统会自动将当前的页面导向到path所指定的页面,这也就是我们通常意义上所谓的菜单树的基本功能。
在一般情况下,我们都希望菜单树所打开的页面在一个指定的目标区域打开,例如在新窗口打开,或替换本页面或在一个框架页面指定的区域打开。该功能在自定义树中我们可以通过自定义树本身的target属性设定,例如target的取值为"_self",则选择菜单树的节点时,系统会自动的用该树节点的path属性所对应的页面替换本页面打开。如果target属性取值为"blank"属性,则单击树节点时,系统会将树节点的path属性对应的页面在一个新的浏览器窗口中打开,另外这儿要特别介绍的是doradosample中的范例应用,如下图:

图表 19
在dorado提供的在线范例演示中,我们可以看到选择树中的不同节点,系统会自动的将新的窗口在右侧的示例中心区域打开。
浏览地址: http://www.bstek.com/dorado5/main.jsp
这里利用的就是一种iframe技术。如下的JSP范例设计:

<%@ page contentType="text/html; charset=UTF-8" %>
<%@ taglib uri="http://www.bstek.com/dorado" prefix="d" %>
<html>
<head></head>

<body>
<d:View config="sample.Main">
<d:SplitPanel id="spiltpanel1" orientation="horizontal" position="220" width="100%" height="100%" padding="0">
<d:Tree id="treeNav" />
<d:Splitter />
<iframe id="frameSample" name="sample" frameborder="0" style="width: 100%; height: 100%" onload="hideCovering()"></iframe>
</d:SplitPanel>
</d:View>
</body>
</html>

在以上的jsp代码中,在treeNav组件对象下方,我们添加了一个name为sample的iframe框架。我们希望选择treeNav中节点的时候,系统自动将新页面展示在sample这个iframe中。那么我们对treeNav的定义就如同下面这样,以下列出xml定义中的基本要素:

<Control id="treeNav1" type="Tree" width="100%" height="100%" target="sample">
<TreeNode label="示例中心" expanded="true" icon="images/folder-closed.gif" expandedIcon="images/folder-opened.gif">
<TreeNode label="dorado5新特性" icon="images/folder-closed.gif" expandedIcon="images/folder-opened.gif" expanded="true">
<TreeNode label="BRich-Client端的MVC" path="basic1.jsp" hasChild="false" icon="images/leaf.gif" />
<TreeNode label="全新设计的表格" path="basic2.jsp" hasChild="false" icon="images/leaf.gif" />
<TreeNode label="高性能的表格" path="basic3.jsp" hasChild="false" icon="images/leaf.gif" />
<TreeNode label="视图重用与门户" path="basic4.jsp" hasChild="false" icon="images/leaf.gif" />
<TreeNode label="更易用的树" path="basic5.jsp" hasChild="false" icon="images/leaf.gif" />
<TreeNode label="更实用的数据集" path="basic6.jsp" hasChild="false" icon="images/leaf.gif" />
<TreeNode label="AJAX特性" path="basic7.jsp" hasChild="false" icon="images/leaf.gif" />
</TreeNode>
</Events>
</Control>

在以上的自定义树中定义了两个关键的属性,一个是树本身的target属性,另一个是node节点的path属性。其中的target属性指定为sample,表示单击树节点时,系统自动的将node的path属性对应的jsp页面在名称为sample的Iframe框架中打开。这也就是我们在doradosample中看到的效果。

技巧二:树节点单击事件的定制

在技巧1中我们已经知道,树节点默认拥有菜单导航功能。对于默认的导航处理时,我们希望可以控制导航过程,或在导航之前给用户一个提示,则我们就可以利用树的onClick事件。视图模型配置文件代码如下:

<Control id="treeNav1" type="Tree" width="100%" height="100%">
<TreeNode label="示例中心" expanded="true" icon="images/folder-closed.gif" expandedIcon="images/folder-opened.gif">
<TreeNode label="dorado5新特性" icon="images/folder-closed.gif" expandedIcon="images/folder-opened.gif" expanded="true">
<TreeNode label="BRich-Client端的MVC" hasChild="false" icon="images/leaf.gif" tag="basic1.jsp" />
<TreeNode label="全新设计的表格" hasChild="false" icon="images/leaf.gif" tag="basic2.jsp" />
<TreeNode label="高性能的表格" hasChild="false" icon="images/leaf.gif" tag="basic3.jsp" />
<TreeNode label="视图重用与门户" hasChild="false" icon="images/leaf.gif" tag="basic4.jsp" />
<TreeNode label="更易用的树" hasChild="false" icon="images/leaf.gif" tag="basic5.jsp" />
<TreeNode label="更实用的数据集" hasChild="false" icon="images/leaf.gif" tag="basic6.jsp" />
<TreeNode label="AJAX特性" hasChild="false" icon="images/leaf.gif" tag="basic7.jsp" />
</TreeNode>
</TreeNode>
<Events>
<Event name="onClick">var node = tree.getCurrentNode(); //获取当前选中节点
if (node && node.getTag()){
if (confirm("你要确认在新窗口打开页面吗?")){
open(node.getTag(),"sample");
}
}</Event>
</Events>
</Control>

上面的设计中,我们将node的path属性调整到node的tag属性中。并添加了tree的onClick事件:

var node = tree.getCurrentNode(); //获取当前选中节点
if (node && node.getTag()){
if (confirm("你要确认在新窗口打开页面吗?")){
open(node.getTag(),"sample");
}
}

以上代码基本用意就是通过tree.getCurrentNode()方法获取当前发生单击事件的树节点对象,并获取其中的tag属性,利用open方法,将tag属性指定的jsp页面在框架sample中打开。
由于tree提供了onClick事件开发人员就可以利用该事件灵活的实现页面逻辑的处理。

技巧三:实现树节点的超链接功能

通常情况下我们可以通过node节点的label属性设置树节点的显示标题,我们也可以利用树的节点刷新事件实现树节点的超链接功能,如下我们在树的onRefreshNode事件中加入代码:

cell.innerHTML="<a href=\"http:"value"\">"value"</a>";
return false;

通过自定义cell对象的innerHTML方式实现超链接,其中cell为onRefreshNode事件中的系统参数,该对象既为要显示标题的对应的HTML单元格。

树的右键菜单实现

要实现树中的右键菜单,我们首先得先定义一个Menu对象,里面配置好所需要的各种菜单元素。之后只要设定menu对象的popupContainer属性为相关树的id即可。如下图:

右键菜单的单击动作的控制,我们可以直接利用Menu对象的onItemClick事件处理即可,onItemClick事件的说明以及Menu对象的使用参考Menu对象的使用说明

TagLib标签属性列表

参考可见对象的基本属性说明。

Tree主要属性说明

draggable

如果要进行树节点拖拽编程,则必须打开该属性。该属性为true时,Tree相关的几个事件(onDragStart(); onDragOver(); onDragEnd())才有效;

width,height

树的高宽设定。在很多情况下我们直接利用dorado中的layout负责布局,而此时树我们希望它可以充满一个独立的pane,则我们就需要在此处设置width,height为100%.当然了你也可以利用top,left属性配合设置tree在网页中的绝对位置和大小。

highlightSelection

用来设定选中节点是否高亮显示,如下图中左侧树组件中的"BRich(Client端的MVC)"节点,选中该节点时,该节点的背景色高亮显示为绿色。该属性可以通过修改skin.css文件中的classname为currentTreeNode的设定加以调整,default中的皮肤设定如下:

.Tree .CurrentTreeNode {
padding: 4;
background-color: #B7F39B;
}

target

当Tree具有菜单导航功能的时候会使用的一个属性,Tree通过该属性确定单击菜单树节点将要在target对应的目标框架中打开新页面。该功能与window.open()函数中的target功能相同。如果不做设定,则默认该值为"_self"。

DefaultTreeNode的主要属性说明

DefaultTreeNode是Tree中节点对象,主要拥有的属性如下:

checkable

DefaultTreeNode通过该属性决定其实有拥有复选框,如果应用需要提供用户进行复选框操作,则需要将该属性打开,打开后效果如下的员工节点:

checkable属性打开后,DefaultTreeNode的checked属性才有意义,而Tree的onCheckStateChange()事件也才会起作用,开发时可以利用该事件捕捉用户的操作。
该属性一般是在节点初始化的时候设置,运行期可以通过DefaultTreeNode提供的JS函数isCheckable(), setCheckable(Boolean checkable)存取。

checked

复选框选中状态标志位,该属性表示复选框是否处于选中状态。例如我们可以在节点初始化的时候直接设置checked为true。DefaultTreeNode提供了相关的JS函数操作checked属性:
isChecked();
setChecked(checked);

expanded

树节点的展开状态,DefaultTreeNode通过isExpanded()函数与setExpanded(Boolean expand)函数判断是否展开和调整展开状态。在设计的时候,如果树的节点较多,而我们又想用户在页面打开后直接展开到一个用户感兴趣的树节点,则我们就可以在设计时,就动态设定该树节点的expanded为true。当然同时不要忘了设置相关父节点的expanded都为true。

icon, expandedIcon

DefaultTreeNode支持树节点图标功能,便于我们可以根据需要给不同的节点设置不同的图标。在dorado中节点图标拥有两种类型,即:目录节点和叶子节点。应此当我们设置节点图标的时候需要同时提供这两种资源,当然了,如果您不需要,可以将两个属性设为同一个资源图标。这两个属性的值都是对应为一张图片资源的URL。根据系统需要设定,一般情况下我们都会将图片资源统一的存储到web应用中的一个images目录中,则icon或expandedIcon的设定通常就会这样:

node.setIcon("../../images/folder-closed.gif");
node.setExpandedIcon("../../images/folder-opened.gif");

当然,你也可以将该处调整为一个action请求,由服务器端返回图片的流输出。这样图片的储存和管理可以更为灵活。

hasChild

用以设定DefaultTreeNode是否是叶子节点,该属性打开的时候,表示该节点为叶子节点,则Tree的expand特性就会失效,影响到的方法有:
isExpanded();
setExpanded(boolean expanded);
beforeExpandNode();
afterExpandNode();

label

设定DefaultTreeNode的标题,也就是用户看到的节点内容。如下图黑框中的内容:

该属性一般来说都是非空的。label是一段文本内容,不支持html内嵌使用方式,如果要实现html内嵌建议您通过Tree的onRefreshNode()方法实现。

path

一般情况下在Tree作为菜单树的时候使用该属性,如果设定了该属性,树节点单击时自动的将path作为要导航的URL,进行菜单导航操作。该属性一般会联合Tree的target属性起作用。

主要事件说明

由于Tree是直接提供用户操作的复杂组件,应此提供了丰富的事件接口便于应用程序的编程和控制,主要的事件有:
当前节点的焦点变动事件:beforeCurrentChange,afterCurrentChange;
节点的单击和双击事件:onClick, onDbClick;
节点的展开和收缩事件:beforeExpandNode, afterExpandNode, beforeCollapseNode, afterCollapseNode;
节点的拖拽事件:onDragStart, onDragOver, onDragEnd;
节点的初始化和刷新事件:onInitNode, onRefreshNode;
其他相关事件:onCheckStateChangeed, onKeyDown, onActive;
以下详细说明:

当前节点的焦点变动事件

Tree与dataset类似,拥有当前节点概念。当我们展开一个有节点的树时,如果树本身的highlightSelection打开,则我们可以看到一个背景色不一样的树节点,如下图的员工节点"辛连波":

当我们选择其它的树节点时,这时候就会引起树的当前节点变化事件(在Tree的快捷键操作中,我们还可以利用键盘的上下键移动树的当前节点):beforeCurrentChange事件与afterCurrentChange事件。在beforeCurrentChange事件处理机制中还提供了一个DoradoException类型的返回值。用以终止当前的节点变化动作。
看看其中的beforeCurrentChange事件的声明:

public DoradoException beforeCurrentChange(Tree tree, TreeNode node)

其中的参数node代表将要取代树当前节点的节点对象。
该事件被应用于一些应用系统中,用以控制某些节点不允许被设置为树的当前节点,例如下图中的分公司节点:

上图当用户选择不同的部门时,左侧利用AJAX技术显示该部门下的员工列表。假设应用中不希望用户选择分公司节点查看该分公司下的员工列表,则我们可以利用技巧,不允许Tree的当前节点变化到分公司节点,则我们可以通过如下的代码实现Tree的beforeCurrentChange事件:

if (node.getLevel() != 2) {
return new AbortException();
}

上面代码中做了一个简单的判断,如果用户选择的树节点的level属性不等于2,则该节点不允许被设置为树的当前节点。由于该树只有两层节点,第一层为分公司,第二层为部门,则以上代码的判断逻辑就是屏蔽非部门节点。代码中还return一个AbortException对象,用以终止当前的树节点变动动作。AbortException是一个继承自DoradoException的对象。
afterCurrentChange事件的声明与beforeCurrentChange事件基本类似:

public void afterCurrentChange(Tree tree, TreeNode node)

afterCurrentChange事件没有返回值,该事件发生在树的当前节点变化已经完成时触发,该事件发生时,树的当前节点切换已经完成,已经不能做终止动作了。在使用的时候尤其需要注意。
afterCurrentChange使用参考范例(doradosample中的产品分类维护界面):

*

http://www.bstek.com/dorado5/training/ui/product-tree.jsp*

在该范例中选择产品分类维护分类信息以及选择产品维护产品信息的时候都在同一个区域使用AutoForm修改和保存信息,并且两个AutoForm是完全不一样的。
页面如下:

该页面的功能是对树的节点做维护,其中选择产品分类,右侧要显示产品分类信息(如上图),而选择树中的具体产品时,希望可以在右侧显示产品详细信息:

该范例中的实现就利用了Tree的afterCurrentChange事件,代码如下:

if (node == null) return;

if (node.getTreeLevel().getName() == "category") {
tabsetDetail.setCurrentTab("category");
}
else {
tabsetDetail.setCurrentTab("product");
}

代码中根据节点是产品分类还是产品决定右侧页面的具体显示的内容(本处利用了TabSet技术,详细内容可以参考TabSet详细说明部分)。

当前节点的单击和双击事件

Tree对节点的鼠标单击和双击事件也提供了支持,其中鼠标双击事件,默认的功能是展开或收缩当前树节点对象。如果要屏蔽默认的双击事件的动作,则可以自定义Tree的onDblClick事件,范例如下:

//do something
throw new AbortException();

在代码的最后抛出一个AbortException来终止Tree默认的节点展开或收缩的动作。
onDblClick的声明如下:

public void onDblClick(Tree tree, Node node)

其中参数是事件的srcElement对象。即触发该事件对应的树节点对象。
树节点的单击事件是Tree事件中应用最为常用的一个事件。通常的功能有:
页面导航: 参考页面链接 http://www.bstek.com/dorado5/main.jsp ;
主从关联特效: 参考页面链接
http://www.bstek.com/dorado5/skills/tree/nav-tree.jsp ;
下拉选择(树型自定义下拉框): 参考页面链接
http://www.bstek.com/dorado5/skills/form/input-assist/input-assist.jsp ;
数据编辑: 参考页面链接
http://www.bstek.com/dorado5/skills/data/vo2.jsp ;

页面导航功能

页面导航功能的基本原理就是window.open()功能。Open函数的声明如下:

window.open( [sURL] [, sName] [, sFeatures] [, bReplace])

当用实现页面导航功能时,window.open的后两个参数是无效的。应此我们只关注前两个参数。
第一个参数是一个URL,用以设定新打开页面对应的页面路径;
第二个参数是新打开页面的窗口句柄,也叫目标框架,如下图:

在上图中我们希望实现菜单导航功能,在页面设计中我们设计页头为一个Menubar组件,左侧为菜单导航树。通过单击Menubar或左侧的菜单导航树的时候,我们希望导航树中被选中的节点代表的页面可以在右侧工作区中名称为content的iframe中显示。
完成以上需求,我们可以在树初始化的时候将需要导航的页面对应的URL存入树节点的path属性中。之后我们在利用Tree的onClick事件,在该事件中我们完成如下的代码:

Window.open(node.getPath(), "content");

其中node为onClick的事件参数,代表当前被单击的树节点。通过取出节点的path属性,我们再利用window.open方法在窗口句柄content中打开新页面。这样我们就完成了导航树的功能。
当然了,前面我们也提到过Tree提供的target属性也是辅助我们完成导航树功能的。如果我们此时将tree的target属性配置为"content"。则Tree节点发生鼠标单击事件时,会自动的将node对应的path属性在target代表的窗口句柄中打开。也就是等同于我们上面定义的tree的onClick事件。如果我们用了Tree的target属性,则我们就可以省去onClick事件代码。
onClick事件代码的声明与onDblClick事件的声明是一样的。你可以参考详细Client的API说明。

主从关联功能

主从关联功能效果页面设计首先必须依赖于dataset的查询原理以及dataset的主从关联技术。以上内容不在本节的重点,详细内容请参考<<快速入门一>>2.0版中的dataset原理说明和dorado ajax处理技术以及<<用户手册>>中的相关文档。
上述主从关联中实例的实现方式(MasterLink)与下文要实现的方式(flushData)略有不同,不过效果一样。为实现范例中的效果我们可以在tree的onClick事件中添加如下代码:

var deptId = node.getRecord().getValue("dept_id");
datasetEmployee.parameters().setValue("dept_id", deptId);
datasetEmployee.flushData();

页面的执行效果就如下:

当我们选择不同部门的时候可以在右侧的表格中看到不同的员工列表。

下拉框选择功能

在一些复杂录入界面中,我们可能会利用dorado的Tree组件作为下拉框对象提供给用户实现下拉选择。如图:

当用户选择一个树节点之后,主页面要负责回写部分信息,可能是页面上的多个编辑框对象(dorado术语应该叫回写多个字段)。
此处的实现需要利用CustomDropDown的DropDown.closeFrame()技术,该方法是的主要作用是关闭下拉框并根据页面开发的需要返回信息到主界面上。详细参考Client-API中该方法说明。在该实例中,我们可以利用树的单击事件进行关闭下拉框的动作,tree的onClick代码如下:

var node = treeHR.getCurrentNode();
if (node != null && node.getLevel() == 3) {
DropDown.closeFrame(node.getRecord());
}

上述代码利用CustomDropDown的closeFrame()方法返回node中的一个附属对象。由于在该实例中采用了DataTree对象,应此可以通过node的getRecord()方法获取树节点绑定的记录对象。再利用DropDown组件的onSelect事件,事件代码如下:

var employeeId = selectedObject.getValue("id");
var employeeName = selectedObject.getValue("name");
var salary = selectedObject.getValue("salary");

datasetEmployee.setValue("employee_id", employeeId);
datasetEmployee.setValue("employee_name", employeeName);
datasetEmployee.setValue("salary", salary);

return true;

以上代码的onSelect事件在dorado开发中也可以通过配置解决,并不是一定要写这些代码的,详细内容可以参考CustomDropDown的使用。

数据编辑功能

数据编辑功能中典型界面如下:

我们选择不同的产品,可以在右侧编辑不同的产品信息。该处就可以利用Tree的onClick事件,代码如下:

var id = node.getRecord().getValue("id");
var record = datasetProduct.find(["id"],[id]);
datasetProduct.setCurrent(record);

利用dataset的setCurrent()方法,使右侧的表单显示被查找出来的记录信息。以上的onClick代码与实际范例中采用的技术有所不同。仅作说明。

节点的展开和收缩事件

当我们展开或收缩树的节点时,Tree会自动触发以下的几个事件:
beforeExpandNode:节点展开前触发;
afterExpandNode:节点展开后触发;
beforeCollapseNode:节点收缩前触发;
afterCollapseNode:节点收缩后触发;
与树当前节点变动事件一样,beforeExpandNode与beforeCollapseNode也提供了DorodoException类型的处理机制,允许我们根据需要禁止节点的展开和收缩动作。
beforeExpandNode方法在Tree的使用方式三中已经提及,方式三中在beforeExpandNode方法内部,利用Tree的API动态添加Tree的树节点。从而实现一个延迟加载树。

节点拖拽事件

当我们打开Tree的draggable特性的时候,我们就可以在界面上直接拖拽树的节点了。节点拖拽的时候,应用中当节点移动的时候如果要改变对象的状态。我们就可以利用Tree的节点拖拽事件完成对象状态改变的业务处理。例如在如下的产品分类维护界面中:

如果我们将一个产品分类从一个位置移动到另一个位置我们就必须改变对象的父子关系。在该页面中节点的父子关系是通过节点对象的parent_id属性区分的。则我们就可以在Tree的onDragEnd事件中动态调整parent_id的值。

var draggingRecord = draggingObject.getRecord();
var targetRecord = targetObject.getRecord();

draggingRecord.setValue("parent_id", targetRecord.getValue("category_id"));

其中的draggingObject表示被拖拽的树节点,targetObject表示要接受draggingObject树节点的目标节点。
onDragEnd事件的声明如下:

public Boolean onDragEnd(Tree tree, Object draggingObject, Object targetObject)

另外在节点移动功能的开发当中我们通常会动态决定某些节点允许拖动,某些节点不允许拖动,则我们可以通过onDragStart事件进行控制,看onDragStart的事件声明:

public Boolean onDragStart(Tree tree, Object draggingObject)

该事件提供一个boolean类型的返回值,该返回值用以控制当前选中的节点是否允许移动操作。
在动态控制节点是否移动的同时,我们还可能会动态设定树中的某些节点可以接受节点的入坞操作。例如上述产品树目录中,很显然我们把一个产品分类节点移动到产品叶子节点下面是不合理的。则我们就需要利用onDragOver事件动态控制那些节点可以接受入坞操作。onDragOver事件的声明如下:

public Boolean onDragOver(Tree tree, Object draggingObject, Object targetObject)

targetObject参数表示入坞的树节点,利用该事件提供一个boolean类型的返回值,用来控制是否targetObject是否接受入坞操作。示例代码:

var draggingLevel = draggingObject.getTag();
var targetLevel = targetObject.getTag();
if (draggingLevel == "category") {
*return (targetLevel == "root"

targetLevel == "category");*
}
else if (draggingLevel == "product") {
return (targetLevel == "category");
}

该范例利用TreeNode的tag属性存储的节点类型信息,在onDragOver事件中利用该属性判断节点类型,动态决定是否允许入坞操作。

节点的初始化事件和刷新事件

Tree初始化树节点的时候会自动触发onInitNode事件,而在节点内容发生变化的时候会触发onRefreshNode事件。一般来说我们都是在onInitNode事件中完成节点属性的设定工作,而在onRefreshNode事件中实现节点节点显示内容的动态控制,在onRefreshNode事件中提供了cell参数,该参数是一个Table中的单元格,对于部分希望动态控制树显示内容的用户来说可以通过cell参数实现,例如实现超链接功能,范例代码:

cell.innerHTML="<a href='http://www.bstek.com'>"value"</a>";
return false;

其中cell与value都是onRefreshNode的事件参数,事件声明如下:

public Boolean onRefreshNode(Tree tree, HtmlElement cell, String value, TreeNode node)

当系统刷新树节点被触发

说明:此方法返回值用于通知系统是否要终止后续默认的内部操作.返回true表示继续默认的操作.
Parameters
tree - Tree - 触发事件的树
cell - HtmlElement - 对应的HTML单元格
value - String - 将要在此树节点中显示的数据
node - TreeNode - 触发事件的树节点

Returns:
Boolean

在超链接功能实现的代码中,我们特意写了retrun false;使事件的返回值为false。告诉树我们已经自定义了node的显示内容。已经不需要系统的默认处理机制(默认会直接显示value的值)。

复选框事件(onCheckStateChangeed)

当我们打开TreeNode的checkable属性后,Tree中的树节点就会自动出现一个复选框对象,页面效果可以参见:

对于这种带复选框对象的树节点,当我们单击复选框使其选中状态发生变化的时候就会自动触发onCheckStateChangeed事件。该事件的声明如下:

public void onCheckStateChanged(Tree tree, TreeNode node)

当节点的复选框对象的状态发生变化时触发

Parameters
tree - Tree - 树状列表
node - TreeNode - 状态发生变化的节点

Returns:
void

在该事件中我们可以通过TreeNode的hasChecked()方法判断其选中状态,进而根据需要调整页面对象的状态。

树的激活事件

当树组件被激活时触发onActive事件。常用的应用有当树被激活时,我们希望其自动定位到某一个节点。则我们可以在onActive事件中实现:

var node;
//利用Tree的API查找符合条件的节点,如果找到就设置该节点给node变量
if (node != null) treeHR.expandNode(node);

树的键盘事件

Tree也支持键盘事件,并且自动支持的四个键盘键是up、down、left、right。这四个键的含义如下:
Up、down:如果当前focus的组件是树,则up、down快捷键会自动根据当前树节点对象查找近邻的上一个或下一个树节点,并将其设置为当前树节点;
Left: 如果当前focus的组件是树,则默认将当前节点收缩起来;
Right: 如果当前focus的组件是树,则默认将当前节点展开;
当然了你也可以屏蔽树的默认快捷键功能,只要我们在onKeyDown事件中返回一个false的值即可:

return false;

如果你还想自定义其中的快捷键操作,则可以利用事件提供的evt参数做详细设定,该事件的声明如下:

public Boolean onKeyDown(Tree tree, Event evt)

当Tree处于focus状态时,按下鼠标键盘触发的事件,根据提供的返回值决定是否屏蔽系统默认的按键快捷操作

Parameters
tree - Tree - 触发事件的Tree对象
evt - Event - 事件句柄

Returns:
boolean

Tree主要方法说明

getCurrentNode(),setCurrentNode()

Tree提供了getCurrentNode()与setCurrentNode(TreeNode node)方法获得或设置当前节点。例如我们希望在Tree组件激活时自动定位到一个指定的节点,则我们可以在Tree的onActive事件中用如下的代码:

var current = null;
//.....省略....该处代码实现查找符合条件的TreeNode,如果找到则将它赋给变量current
if (current != null) {
treeHR.setCurrent(current);
}

expandNode(),collapseNode()

展开和收缩指定的树节点可以用Tree提供的expandNode(TreeNode node)或collapseNode(TreeNode node)方法。这两个方法都接收一个指定要展开或收缩的树节点对象。如果我们希望Tree组件激活时自动展开一个指定的树节点,则我们可以在Tree的onActive事件中用如下的代码:

var current = null;
//.....省略....该处代码实现查找符合条件的TreeNode,如果找到则将它赋给变量current
if (current != null) {
treeHR.expandNode(current);
}

clearAllCheckedState(),getAllCheckedNodes()

如果Tree中的部分树节点的checkable功能打开,则我们可以通过Tree的getAllCheckedNodes()方法获取Tree中选中的所有树节点对象,该对象返回Array对象数组。如:

var checkedNodes = treeHR.getAllCheckedNodes();
var length = checkedNodes.length;
for (i = 0; i <length; i++) {
var node = checkedNodes[i];
}

针对selectable功能,Tree还提供了clearAllCheckedState()方法帮组我们请所有处于选中状态的树节点改为未选中状态。该函数主要做清理工作,执行时不会触发Tree的onCheckStateChanged事件,使用时要注意。

getTopNode(),getFirstRootNode()和getLastRootNode()

任何类型的Tree在构造时都会自动构造一个topNode节点,下面挂的才是中我们动态添加或配置到xml中的节点。topNode下的第一层子节点在Tree中称为rootNode。
获取topNode我们可以通过getTopNode()得到,注意该节点对象是Tree自动构造的,应此在使用的时候要注意该对象禁止修改和重置的。
getFirstRootNode()用以获取topNode下的第一层节点的第一个子节点。getLastRootNode指topNode下一层节点的最后一个子节点。如下图:

此时getFirstRootNode()对象即北京分公司节点,getLastRootNode()对象是深圳分公司节点。

addRootNode()

Tree中添加rootNode需要通过Tree的addRootNode(TreeNode node)方法实现。

DefaultTreeNode主要方法说明

getLevel()

DefaultTreeNode对象拥有层次信息,表示距离topNode节点有几层。rootNode的getLevel()为1。topNode的getLevel()为0。

getNodes()

对一个指定的TreeNode我们可以通过其getNodes()方法获取该节点下所有的子节点集合。返回结果以Collection结构提供。
对于一个实现延迟加载功能的树,其子节点还未初始化,则getNodes()方法是无法取道这些未初始化的子节点的。使用的时候尤要注意。

getParent()

一个TreeNode可以通过getParent()方法获得其父节点对象。

getNextNode(),getPreviousNode()

getNextNode()用以取得该节点在树中对应位置的下一个节点,如果有子节点且子节点已经展开,则该方法返回第一个子节点,否则系统通过父节点查找临近的下一个子节点,如果还未找到,则到父节点的父节点中查找,如此循环查找,直到找到符合条件的树节点返回,如果未找到则返回null对象。使用时需要注意getNextNode()方法返回的TreeNode不一定与当前节点在同一层树节点上。而getPreviousNode()的处理方式与此类似,它用以取得该节点在树中对应位置的上一个节点。同样有可能取得的树节点与当前节点不在同一层树节点上。

getFirstChild(),getLastChild()

getFirstChild()方法用以取得下一层节点中的第一个子节点,在延迟加载实现机制中,如果子节点还未完成动态加载和初始化,则getFirstChild()会返回null对象。getLastChild()方法则是取得下一层节点中最后一个节点,同样在延迟加载实现机制中,如果子节点还未完成动态加载和初始化,则getLastChild ()会返回null对象。如果子节点只有一个则getFirstChild()与getLastChild()将会返回同一个TreeNode对象。

getNextSibling(),getPreviousSibling()

getNextSibling()方法用以获取与本节点对象同级别的下一个树节点对象,如果不存在则返回null。getPreviousSibling()方法用以获取与本节点对象同级别的上一个树节点对象,如果不存在则返回null。

remove(),removeNode()

remove()方法用以从当前节点的父节点中删除自身。removeNode(TreeNode node)方法则是将一个自定的下一级的一个节点对象删除。注意参数node必须为当前节点的下一级节点的其中一个。而不能是多层节点下的某一个子节点对象。

addNode()

DefaultTreeNode提供addNode(TreeNode node, String mode, TreeNode refNode)方法将参数中指定的TreeNode对象作为当前节点的子节点插入,默认插入到下一层子节点的最后一个位置上。如果想定制节点插入位置则可以通过addNode的第二个参数指定插入方式,第三个参数指定参照节点。addNode的声明如下:

public void addNode(TreeNode node, String mode, TreeNode refNode)

将一个指定的节点添加为本节点的子节点

Parameters
node - TreeNode - 要插入的子节点对象
mode - String - 插入方式,默认为end.
取值范围:

    • begin - 在所有单元的之前插入*
    • before - 在参照单元之前插入*
    • after - 在参照单元之后插入*
    • end - 在所有单元的最后追加*

      refNode - TreeNode - 参照子节点
      对于"begin"和"end"两种插入方式可以不指定此参数的值.

      Returns:
      void

其中参数mode是相对于refNode而言的。

CSS说明

/树组件整体风格设定/
.Tree {
background-color: #F5F7F9; //背景色
border-width: 1; //树外框的边线宽度
border-color: #C5D9E8; //树外框的边线颜色
border-style: solid; //树外框的边线样式
}

/树节点代表的行的css设定/
.Tree .TreeNode {
padding: 4; //树节点之间空白大小
}

/树当前节点代表的行的css设定/
.Tree .CurrentTreeNode {
padding: 4; //当前节点与上下两个节点的空白大小
background-color: #B7F39B; //当前节点的颜色
}

/树入坞节点代表的行的css设定/
.Tree .DraggingTarget {
padding: 4; //拖动的节点与目标节点的空白大小
background-color: #E8E8E8; //节点拖动到目标节点时的背景色
}

/树节点前面的展开和收缩图标对应位置的css设定/
.TreeNodeButton {
cursor: hand; //当鼠标移到节点打开&关闭的图标时鼠标的形状
}

/树节点拖动时其光标的css设定/
.TreeDraggingCursor {
filter: progid:DXImageTransform.Microsoft.Alpha(opacity=60);
-moz-opacity: 0.6;
}