论坛首页 Web前端技术论坛

(原创)新概念智能树形菜单--利用加权多叉树结合JavaScript树形控件实现

浏览 11442 次
精华帖 (3) :: 良好帖 (0) :: 新手帖 (6) :: 隐藏帖 (0)
作者 正文
   发表时间:2012-04-15   最后修改:2012-07-26

(原创)新概念智能树形菜单--利用加权多叉树结合JavaScript树形控件实现


一、 智能树形菜单的概念
在描述智能树形菜单的概念之前,请先阅读我的上一篇文章利用多叉树实现Ext JS中的无限级树形菜单(一种构建多级有序树形结构JSON的方法)》,请点击链接查看:http://www.iteye.com/topic/1119961,阅读完上一篇文章之后,再继续向下阅读,因为这两篇文章是紧密联系、前后关联的,谢谢。

 

大家都知道树形菜单,不管在基于什么样的技术开发的系统上,树形菜单都是用户界面中非常常用的一种菜单形式,在目前市场上常见的JavaScript框架及组件库中均包含自己的树形控件,例如JQuery、Dojo、Yahoo UI、Ext JS等,还有一些独立的树形控件,例如dhtmlxtree等,这些树形控件完美的解决了层次数据的展示问题。那么树形菜单发展到现在就算完美了吗?还有没有进一步的发展空间呢?还有没有进一步的完善空间呢?答案是肯定的。在这篇文章里,我提出一种新的想法对树形菜单进行扩展,也许会给您的项目带来一些新的用户体验,我管这种扩展型的树形菜单叫做“智能树形菜单”。那么智能树形菜单和普通的树形菜单到底有什么区别呢,它到底智能在哪呢,让我们先来看一组用例场景: 

 

网民高小宝是一名网上银行的忠实用户,他经常利用网银办理各种银行业务,假设高小宝在
某某银行开通了网上银行功能,他回到家之后,想试验一下这个新的网银系统,于是他首次登录了某某银行的网上银行,登录成功后,系统展示界面如下所示(简略概要图):
图1.1

 

他想给自己的朋友李小磊转一笔钱,于是他在界面左上角的“输入交易功能关键字”输入框中输入“转账”二字,点击“搜索”按钮,查询出了如下的结果,如图:
图1.2


 

他看到左侧的树形菜单区域中搜索出了三个交易功能,分别是行内转账、跨行转账和跨行快速转账,于是他点击跨行转账后,主界面进入跨行转账信息录入界面,高小宝很快完成了转账操作,如下图:
图1.3
 

 

高小宝一看,这个银行的网上银行系统操作这么方便,于是他又陆续使用了以下几个功能,分别是“投资理财->定活互转”、“投资理财->理财产品->产品购买”、“我的贷款->贷款信息查询”、“客户服务->昵称设置”,本次登录他一共使用了五个功能点,其中“转账汇款->跨行转账”功能使用了两次,一次是给朋友李小磊转钱,另一次是给朋友王小然转钱,其他四个功能各使用了一次。


第二天,高小宝又登录了网上银行系统,登录成功后,他看到系统的操作界面和第一次登录时不太一样了,变成了如下样子:
图1.4


 

他看到昨天使用的五个功能点菜单全部自动展开了,而且菜单顺序也发生了变化:原来排在最后的一级菜单“客户服务”居然移到了“我的贷款”下面,而原来排在第一位的“账户管理”却移到了“客户服务”下面,由于部分菜单自动展开,所以菜单栏右侧出现了滚动条。
不光一级菜单的顺序发生了变化,二级菜单的顺序也发生了变化,原来在“转账汇款”下面的子菜单中,“跨行转账”排在“行内转账”下面,现在却排在了第一位。不光菜单发生了变化,中间主界面也发生了变化,原来是欢迎界面,现在变成了“跨行转账”信息录入界面。
高小宝一看,正合我意,他正想给朋友吴艳清转一笔钱,吴艳清的开户行是工商银行,不是本行,正好需要使用跨行转账功能,于是他很快完成了转账操作。他今天还想通过“定活互转”功能,存一笔定期,由于昨天使用了一次该功能,所以这个功能也默认自动展开,他很容易就看到了这个菜单项,顺利完成了存定期的操作,也就是说功能使用率大于平均功能使用率的功能节点自动展开了(假设有10个功能,功能A和功能B分别被使用了3次和2次,其它功能都被使用了1次,那么平均使用率是(3+2+8)/ 10 = 1.3次,A和B的使用率大于平均使用率,自动展开,其他8个功能的使用率小于1.3,不展开),真智能啊!高小宝心想,看来以后得多多使用网上银行了,太方便了。


好了,读者朋友们,通过上面这组用例场景,相信您已经看出了本文要论述的主题内容,对了,这就是智能树形菜单,现在我们给智能树形菜单下一个完整科学的定义,


智能树形菜单满足如下几个条件:

 

A、用户首次登录系统时,功能菜单树全部闭合,并且按照菜单编号排序,且每一层菜单节点都按照这个规则排序,保持整棵功能树兄弟节点横向有序(关于“兄弟节点横向有序”的概念请查看我的上一篇文章);

B、系统会自动记录每一个功能路径(功能路径指的是由一级菜单节点到功能叶子节点的一条完整路径,例如“投资理财->理财产品->产品购买”就是一条功能路径)的使用率;

C、系统提供菜单节点搜索功能,用户可以输入菜单节点关键字,系统会搜索出包含关键字的所有功能路径,并展示出搜索结果;

D、用户再次登录系统时,功能菜单重新排序,并且部分展开,其规则如下:
(1)整棵功能菜单树按照使用率加节点编号的方式排序,先按使用率排序,使用率相同的情况下按照节点编号排序,保持兄弟节点横向有序;
(2)热点功能自动展开(热点功能指的是功能使用率大于所有功能平均使用率的功能叶子节点,例如在上面的用例场景中,高小宝第一次使用网银时,“跨行转账”被使用了两次,“定活互转”、“产品购买”、“贷款信息查询”、“昵称设置”都被使用了一次,其他功能被使用了零次,假设一共有30个功能,那么功能平均使用率是(2+1+1+1+1)/ 30 = 0.2, 这五个功能的使用率均大于0.2,所以都属于热点功能)

E、系统主界面默认进入焦点功能(焦点功能指的是热点功能中使用率最高的功能)的操作界面;

 

 

好了,智能树形菜单的概念就介绍完了,下面讲述详细实现方案。

 

 

二、 详细实现方案
在《利用多叉树实现Ext JS中的无限级树形菜单(一种构建多级有序树形结构JSON的方法) 》一文中,谈到了多叉树,智能树形菜单就可以在多叉树的基础上进行扩展,实现概念定义中所描述的所有功能,下面介绍详细设计方案。

 

下面这张图就是扩展之后的多叉树,并且根据这张图定义了一系列名词概念,如图所示:
图1.5

 

我们先根据这张图定义一组名词概念:
1、 菜单树
所有菜单可以抽象成一棵菜单树,为了保持树的完整性,在一级菜单上面加了一个虚拟的根节点,每个节点由两个值组成,用逗号分割,前一个值是菜单节点编号,后一个值是节点权值,默认权值都为1。

 

2、节点权值
为了描述某一个节点在其兄弟节点之中的优先级(也就是功能使用率),用一个数值来表示,就叫节点权值。

 

3、功能叶子节点
菜单树中的叶子节点,下面不再下挂其他子功能节点。

 

4、功能路径
由根节点到功能叶子节点的一条完整路径,例如图中的蓝色线条部分,即 “根->3->32->323”就是一条功能路径,上图的菜单树中共有13条功能路径,您可以数一数,呵呵。

 

5、功能路径权值
功能路径权值由功能路径中所有节点的权值由父到子拼接而成,例如路径“根->3->32->323”的路径权值是“111”。

 

6、 优先功能路径
指的是功能路径权值最高的那一条路径,如上图菜单树中的红色线条部分,该路径为“根->1->11”,它的路径权值是“32”。路径权值比较的规则是逐级比较,“32”的第一级是3,“111”的第一级是1,所以“32”大于“111”,路径权值相同时比较节点编号,也就是用权值加节点编号进行比较,例如路径“根->1->11”和路径“根->1->12->121”,它们的路径权值分别是“32”和“322”,当比较到第二级时,节点权值都是2,但是由于节点11比节点12的编号小,所以节点11排列在节点12前面,也就是说功能路径“根->1->11”的优先级排在“根->1->12->121”之前。

 

7、功能叶子列表
当构造好一棵菜单树后,通过先序遍历,找到所有叶子节点,并将其存放在一个列表里面,这个列表叫做功能叶子列表。

 

8、功能叶子权值
功能叶子节点的权值叫做功能叶子权值。

 

9、热点功能叶子
功能叶子权值大于所有功能叶子节点的平均权值的叶子节点,例如上图中功能叶子列表中的前两个节点:11和121,其权值分别是2和2,而叶子节点的平均权值是1.15。

 

10、焦点功能叶子
热点功能叶子中权值最高的叶子叫做焦点功能叶子,如果权值相等,则比较节点编号,节点编号优先的叶子为焦点功能叶子。

 

11、功能路径可见
在菜单节点中增加一个“是否可见”属性,如果一条功能路径中所有节点的“是否可见”属性为true,那么称该条路径可见,否则该条路径不可见,所谓不可见,指的是不向用户展示这条功能路径。

 

12、菜单节点搜索
指的是在整棵功能树的所有节点中按照节点关键字查找包含该关键字的菜单节点。

 

13、功能路径筛选
指的是对整棵树的功能路径的可见性进行重新设置,首先将所有节点设置为不可见,然后根据菜单节点的搜索结果,将包含该菜单节点的所有功能路径设置为可见,也就是向用户展示菜单节点搜索的结果。

 

14、增加功能路径权值
指的是将包含某一功能叶子节点的一条完整功能路径上的所有节点的权值增加相同的幅度(例如将某一功能路径上的所有节点权值加1)。

 

 

我们可以把这棵加了节点权值的多叉树叫做“加权多叉树”。

 


好了,定义完名词概念之后,用下面一组图来展示加权多叉树的变化过程。

 


图一,用户首次登录系统时,在服务器端构造出加权多叉树和功能叶子列表:
图1.6
 

 

其中黄色圆圈代表功能叶子节点。

 

构造出这棵树之后,向客户端的JavaScript树形控件返回树形结构的JSON字符串,返回如下内容:

{
  id: '0',
  text: '根',
  children: [
    {
      id: '1',
      text: '1',
      children: [
        {
          id: '11',
          text: '11',
          leaf: true
        },
        {
          id: '12',
          text: '12',
          children: [
            {
              id: '121',
              text: '121',
              leaf: true
            },
            {
              id: '122',
              text: '122',
              leaf: true
            },
            {
              id: '123',
              text: '123',
              leaf: true
            }
          ]
        }
      ]
    },
    {
      id: '2',
      text: '2',
      children: [
        {
          id: '21',
          text: '21',
          leaf: true
        },
        {
          id: '22',
          text: '22',
          leaf: true
        }
      ]
    },
    {
      id: '3',
      text: '3',
      children: [
        {
          id: '31',
          text: '31',
          leaf: true
        },
        {
          id: '32',
          text: '32',
          children: [
            {
              id: '321',
              text: '321',
              leaf: true
            },
            {
              id: '322',
              text: '322',
              leaf: true
            },
            {
              id: '323',
              text: '323',
              leaf: true
            }
          ]
        }
      ]
    },
    {
      id: '4',
      text: '4',
      children: [
        {
          id: '41',
          text: '41',
          leaf: true
        },
        {
          id: '42',
          text: '42',
          leaf: true
        },
        {
          id: '43',
          text: '43',
          leaf: true
        }
      ]
    }
  ]
}

 


图二,用户使用关键字“12”在功能叶子列表中进行搜索,并将包含搜索结果的功能路径设置为可见,其他路径设置为不可见,图中红色线条部分为可见路径,其他的为不可见路径,如图所示:
图1.7
 

 

进行功能路径筛选后,向客户端返回如下JSON字符串:

{
  id: '0',
  text: '根',
  children: [
    {
      id: '1',
      text: '1',
      children: [
        {
          id: '12',
          text: '12',
          children: [
            {
              id: '121',
              text: '121',
              leaf: true
            },
            {
              id: '122',
              text: '122',
              leaf: true
            },
            {
              id: '123',
              text: '123',
              leaf: true
            }
          ]
        }
      ]
    }
  ]
}

 

 

图三,用户再使用关键字“321”在功能叶子列表中进行搜索,搜索结果如图所示:
图1.8
 

 

进行功能路径筛选后,向客户端返回如下JSON字符串:

{
  id: '0',
  text: '根',
  children: [
    {
      id: '3',
      text: '3',
      children: [
        {
          id: '32',
          text: '32',
          children: [
            {
              id: '321',
              text: '321',
              leaf: true
            }
          ]
        }
      ]
    }
  ]
}

 

到这里,您一定会问:菜单节点搜索必须要在服务器端进行吗,这样响应速度岂不是很慢?是的,这的确是个问题,那么,该功能能否放在客户端来实现呢?通过改造现有的树形控件或者重新开发新的树形控件,利用JavaScript在客户端来实现菜单节点的搜索功能,而无需与服务器进行交互。比如,能否利用JavaScript在客户端构造一棵多叉树,然后采用类似的方法来实现菜单节点的搜索呢?具体实现方法不是本文讨论的重点,本文只是提供一个思路,具体实现还要看您了,呵呵。

 


图四,用户使用某个功能后,系统将该功能路径权值加1,并将该功能路径上的所有节点的权值更新到数据库中的菜单节点定义表中,如图所示,路径“根->3->32->323”的权值加1,由“111”变成了“222”:
图1.9
 

 

图五,当用户退出,再次登录系统后,在服务器端再次构造出加权多叉树和功能叶子列表,此时数据结构变成如下样子:
图2.0
 

 

树中红色线条部分为优先功能路径,功能叶子列表中红色圆圈为焦点功能叶子。


图六,用户再次使用某个功能后,系统将该功能路径权值加1,并将该功能路径上的所有节点的权值更新到数据库中的菜单节点定义表中,如图所示,路径“根->1->12->122”的权值加1,由“111”变成了“222”:
图2.1
 

 

图七,当用户退出,再次登录系统后,在服务器端再次构造出加权多叉树和功能叶子列表,此时数据结构变成如下样子:
图2.2
 

 

树中红色线条部分根据路径权值和节点编号重新进行了兄弟节点横向排序;
功能叶子列表中红色圆圈为热点叶子,现在有两个热点叶子了,其中122为焦点叶子。

 

现在服务器向客户端返回的JSON字符串变成了如下:

{
  id: '0',
  text: '根',
  children: [
    {
      id: '1',
      text: '1',
      children: [
        {
          id: '12',
          text: '12',
          children: [
            {
              id: '122',
              text: '122',
              leaf: true
            },
            {
              id: '121',
              text: '121',
              leaf: true
            },
            {
              id: '123',
              text: '123',
              leaf: true
            }
          ]
        },
        {
          id: '11',
          text: '11',
          leaf: true
        }
      ]
    },
    {
      id: '3',
      text: '3',
      children: [
        {
          id: '32',
          text: '32',
          children: [
            {
              id: '323',
              text: '323',
              leaf: true
            },
            {
              id: '321',
              text: '321',
              leaf: true
            },
            {
              id: '322',
              text: '322',
              leaf: true
            }
          ]
        },
        {
          id: '31',
          text: '31',
          leaf: true
        }
      ]
    },
    {
      id: '2',
      text: '2',
      children: [
        {
          id: '21',
          text: '21',
          leaf: true
        },
        {
          id: '22',
          text: '22',
          leaf: true
        }
      ]
    },
    {
      id: '4',
      text: '4',
      children: [
        {
          id: '41',
          text: '43211',
          leaf: true
        },
        {
          id: '42',
          text: '42',
          leaf: true
        },
        {
          id: '43',
          text: '43',
          leaf: true
        }
      ]
    }
  ]
}

 

 

对于热点功能叶子自动展开,和系统默认进入焦点功能界面这两个功能,只需要客户端通过AJAX的方式从服务器端的功能叶子列表中取出热点功能叶子和焦点功能叶子的节点编号后,在客户端通过编程处理,取热点功能叶子的方法很简单,只需要取出叶子权值大于叶子平均权值的节点即可,然后再找到一个权值最大的叶子就是焦点功能叶子;至于如何展开树形控件的叶子节点本文不做叙述,那是树形控件要实现的功能。

 

 

三、 源代码实现(服务器端JAVA代码演示)

以下代码拷贝出来后可以直接运行测试(注:关于菜单节点搜索功能,这里用java来演示,在服务器端进行,系统响应速度很慢,应该放在客户端实现):

 

package test;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Collections;

/**
 * 功能菜单树类
*/
public class FunctionTree {
	
	public static void main(String[] args) {
		// 读取层次数据结果集列表
		List dataList = VirtualDataGenerator.getVirtualResult();

		// 构造加权多叉树
		Node root = buildWeightedMultiTree(dataList);
		
		// 构造功能叶子列表
		List functionLeafList = buildFunctionLeafList(root);
		
		// 对多叉树重新进行横向排序
		root.sortChildren();
		// 输出首次登录后的树形菜单
		System.out.println("首次登录时的树形菜单:\n" + root.toString());	
		
		// 进行菜单节点搜索(即功能路径筛选)
		searchTreeNode(root, "321");
		
		// 输出搜索结果
		System.out.println("搜索后的树形菜单:\n" + root.toString());
		
		// 增加功能路径权值
		increaseRouteWeight(root, functionLeafList, "122");
		increaseRouteWeight(root, functionLeafList, "323");
		
		// 对多叉树重新进行横向排序
		root.sortChildren();
		// 输出权值变化后的树形菜单		
		System.out.println("路径权值变化后再次登录时的树形菜单:\n" + root.toString());
		
		// 获取热点功能叶子
		List hotFunctionLeaf = getHotFunctionLeaf(functionLeafList);
		
		// 输出热点功能叶子
		printHotFunctionLeaf(hotFunctionLeaf);

		// 程序输出结果如下:
		// 首次登录时的树形菜单:
		// {id : '0', text : '根', children : [{id : '1', text : '1', children : [{id : '11', text : '11', leaf : true},{id : '12', text : '12', children : [{id : '121', text : '121', leaf : true},{id : '122', text : '122', leaf : true},{id : '123', text : '123', leaf : true}]}]},{id : '2', text : '2', children : [{id : '21', text : '21', leaf : true},{id : '22', text : '22', leaf : true}]},{id : '3', text : '3', children : [{id : '31', text : '31', leaf : true},{id : '32', text : '32', children : [{id : '321', text : '321', leaf : true},{id : '322', text : '322', leaf : true},{id : '323', text : '323', leaf : true}]}]},{id : '4', text : '4', children : [{id : '41', text : '41', leaf : true},{id : '42', text : '42', leaf : true},{id : '43', text : '43', leaf : true}]}]}
		// 搜索后的树形菜单:
		// {id : '0', text : '根', children : [{id : '3', text : '3', children : [{id : '32', text : '32', children : [{id : '321', text : '321', leaf : true}]}]}]}
		// 路径权值变化后再次登录时的树形菜单:
		// {id : '0', text : '根', children : [{id : '1', text : '1', children : [{id : '12', text : '12', children : [{id : '122', text : '122', leaf : true},{id : '121', text : '121', leaf : true},{id : '123', text : '123', leaf : true}]},{id : '11', text : '11', leaf : true}]},{id : '3', text : '3', children : [{id : '32', text : '32', children : [{id : '323', text : '323', leaf : true},{id : '321', text : '321', leaf : true},{id : '322', text : '322', leaf : true}]},{id : '31', text : '31', leaf : true}]},{id : '2', text : '2', children : [{id : '21', text : '21', leaf : true},{id : '22', text : '22', leaf : true}]},{id : '4', text : '4', children : [{id : '41', text : '41', leaf : true},{id : '42', text : '42', leaf : true},{id : '43', text : '43', leaf : true}]}]}
		// 热点功能叶子:
		// [{id : '323', text : '323', leaf : true}, {id : '122', text : '122', leaf : true}]
	}			
	
	/**
	 * 构造加权多叉树
	 * @return
	 */
	public static Node buildWeightedMultiTree(List dataList) {
		// 节点列表(散列表,用于临时存储节点对象)
		HashMap nodeList = new HashMap();
		// 根节点
		Node root = null;
		// 根据结果集构造节点列表(存入散列表)
		for (Iterator it = dataList.iterator(); it.hasNext();) {
			Map dataRecord = (Map) it.next();
			Node node = new Node();
			node.id = (String) dataRecord.get("id");
			node.text = (String) dataRecord.get("text");
			node.parentId = (String) dataRecord.get("parentId");
			node.weight = Integer.parseInt((String) dataRecord.get("weight"));
			nodeList.put(node.id, node);
		}
		// 构造无序的多叉树
		Set entrySet = nodeList.entrySet();
		for (Iterator it = entrySet.iterator(); it.hasNext();) {
			Node node = (Node) ((Map.Entry) it.next()).getValue();
			if (node.parentId == null || node.parentId.equals("")) {
				root = node;
			} else {
				((Node) nodeList.get(node.parentId)).addChild(node);
				// 在节点中增加一个父节点的引用
				node.parentNode = (Node) nodeList.get(node.parentId);
			}
		}

		return root;
	} 
	
	/**
	 * 构造功能叶子列表
	 * @param root
	 * @return
	 */
	public static List buildFunctionLeafList(Node root) {
		List functionLeafList = new ArrayList();
		root.initializeLeafList(functionLeafList);
		return functionLeafList;
	}
	
	/**
	 * 进行菜单节点搜索(即功能路径筛选)
	 * @param root
	 * @param keyWord
	 */
	public static void searchTreeNode(Node root, String keyWord) {
		// 首先设置整棵树的功能路径为不可见
		root.setTreeNotVisible();
		// 在整棵功能树中搜索包含关键字的节点,并进行路径筛选
		root.searchTreeNode(keyWord);					
	}
	
	/**
	 * 增加功能路径权值
	 * @param root
	 */
	public static void increaseRouteWeight(Node root, List functionLeafList, String nodeId) {
		// 首先设置整棵树的功能路径为可见
		root.setTreeVisible();		
		// 对包含功能叶子节点的路径权值加1
		for (Iterator it = functionLeafList.iterator(); it.hasNext();) {
			Node leafNode = (Node) it.next();
			if (leafNode.id.equals(nodeId)) {
				leafNode.increaseRouteWeight();
			}
		}		
	}
	
	/**
	 * 获取热点功能叶子
	 * @param functionLeafList
	 * @return
	 */
	public static List getHotFunctionLeaf(List functionLeafList) {
		int count = 0;
		int totalWeight = 0;
		BigDecimal avgWeight;
		for (Iterator it = functionLeafList.iterator(); it.hasNext();) {
			Node node = (Node) it.next();
			totalWeight += node.weight;
			count++;
		}
		avgWeight = (new BigDecimal(totalWeight)).divide(new BigDecimal(count), 2, BigDecimal.ROUND_HALF_UP);
		List retList = new ArrayList();
		for (Iterator it = functionLeafList.iterator(); it.hasNext();) {
			Node node = (Node) it.next();
			if (node.weight > avgWeight.doubleValue()) {
				retList.add(node);
			}
		}
		return retList;
	}
	
	/**
	 * 输出热点功能叶子
	 * @param hotFunctionLeaf
	 */
	public static void printHotFunctionLeaf(List hotFunctionLeaf) {
		System.out.println("热点功能叶子:\n" + hotFunctionLeaf);
	}
	
	
}


/**
* 节点类
*/
class Node {
	/**
	 * 节点编号
	 */
	public String id;
	/**
	 * 节点内容
	 */
	public String text;
	/**
	 * 父节点编号
	 */
	public String parentId;
	/**
	 * 节点权值
	 */
	public int weight;
	/**
	 * 是否可见,默认为true
	 */
	public boolean visible = true;
	/**
	 * 父节点引用
	 */
	public Node parentNode;
	/**
	 * 孩子节点列表
	 */
	private Children children = new Children();
	
	// 添加孩子节点
	public void addChild(Node node) {
		this.children.addChild(node);
	}
	
	// 先序遍历,拼接JSON字符串
	public String toString() {
		if (visible) {
			String result = "{"
				+ "id : '" + id + "'"
				+ ", text : '" + text + "'";
			
			if (children != null && children.getSize() != 0) {
				result += ", children : " + children.toString();
			} else {
				result += ", leaf : true";
			}
					
			return result + "}";
		} else {
			return "";
		}
	}
	
	// 兄弟节点横向排序
	public void sortChildren() {
		if (children != null && children.getSize() != 0) {
			children.sortChildren();
		}
	}
	
	// 先序遍历,构造功能叶子列表
	public void initializeLeafList(List leafList) {
		if (children == null || children.getSize() == 0) {
			leafList.add(this);
		} else {
			children.initializeLeafList(leafList);
		}
	}
	
	// 先序遍历,设置该节点下的所有功能路径为不可见
	public void setTreeNotVisible() {
		visible = false;
		if (children != null && children.getSize() != 0) {
			children.setTreeNotVisible();
		}
	}
	
	// 先序遍历,设置该节点下的所有功能路径为可见
	public void setTreeVisible() {
		visible = true;
		if (children != null && children.getSize() != 0) {
			children.setTreeVisible();
		}
	}
	
	// 设置包含该叶子节点的功能路径可见
	public void setRouteVisible() {
		visible = true;
		for (Node parentNode = this.parentNode; parentNode != null; parentNode = parentNode.parentNode) {
			parentNode.visible = true;
		}
	}
	
	// 对包含该叶子节点的功能路径权值加1
	public void increaseRouteWeight() {
		weight++;
		updateNodeWeightToDB(this);
		for (Node parentNode = this.parentNode; parentNode != null; parentNode = parentNode.parentNode) {
			parentNode.weight++;
			updateNodeWeightToDB(parentNode);
		}
	}
	
	// 更新节点权值到数据库
	public void updateNodeWeightToDB(Node node) {
		// 暂时不实现,实际应用中需要实现该方法
		// 或者用户退出系统时,遍历整棵树,统一更新所有节点的权值到数据库中,应该这样做比较好,一次性统一处理
	}
	
	// 先序遍历,搜索菜单节点,同时进行功能路径过滤
	public void searchTreeNode(String keyWord) {
		if (this.text.indexOf(keyWord) > -1) {
			this.setTreeVisible();
			this.setRouteVisible();
		} else {
			if (children != null && children.getSize() != 0) {
				children.searchTreeNode(keyWord);
			}
		}
	}
}

/**
* 孩子列表类
*/
class Children {
	private List list = new ArrayList();
	
	public int getSize() {
		return list.size();
	}
	
	public void addChild(Node node) {
		list.add(node);
	}
	
	// 拼接孩子节点的JSON字符串
	public String toString() {
		String result = "[";		
		for (Iterator it = list.iterator(); it.hasNext();) {
			Node node = (Node) it.next();
			if (node.visible) {
				result += node.toString();
				result += ",";
			}
		}
		result = result.substring(0, result.length() - 1);
		result += "]";
		return result;
	}
	
	// 在孩子节点中寻找功能叶子节点
	public void initializeLeafList(List leafList) {
		for (Iterator it = list.iterator(); it.hasNext();) {
			((Node) it.next()).initializeLeafList(leafList);
		}
	}
	
	// 孩子节点排序
	public void sortChildren() {
		// 对本层节点进行排序
		// 可根据不同的排序属性,传入不同的比较器,这里传入优先级比较器
		Collections.sort(list, new NodePriorityComparator());
		// 对每个节点的下一层节点进行排序
		for (Iterator it = list.iterator(); it.hasNext();) {
			((Node) it.next()).sortChildren();
		}
	}
	
	// 设置孩子节点为不可见 
	public void setTreeNotVisible() {
		for (Iterator it = list.iterator(); it.hasNext();) {
			((Node) it.next()).setTreeNotVisible();
		}
	}
	
	// 设置孩子节点为可见 
	public void setTreeVisible() {
		for (Iterator it = list.iterator(); it.hasNext();) {
			((Node) it.next()).setTreeVisible();
		}
	}
	
	// 搜索菜单节点,同时进行功能路径过滤
	public void searchTreeNode(String keyWord) {
		for (Iterator it = list.iterator(); it.hasNext();) {
			((Node) it.next()).searchTreeNode(keyWord);
		}
	}
}

/**
 * 节点比较器
 */
class NodePriorityComparator implements Comparator {
	// 按照 (节点权值+节点编号) 比较
	public int compare(Object o1, Object o2) {
		// 按权值由大到小排序
		int w1 = ((Node)o1).weight;
	    int w2 = ((Node)o2).weight;
	    if (w1 < w2) {
	    	return 1;
	    } else if (w1 > w2) {
	    	return -1;
	    } else { // 权值相等时,按照节点编号由小到大排序
	    	int i1 = Integer.parseInt(((Node)o1).id);
	    	int i2 = Integer.parseInt(((Node)o2).id);
	    	return i1 < i2 ? -1 : (i1 == i2 ? 0 : 1);
	    }
	}	
}

/**
 * 构造虚拟的层次数据
 */
class VirtualDataGenerator {
	// 构造无序的结果集列表,实际应用中,该数据应该从数据库中查询获得;
	public static List getVirtualResult() {				
		List dataList = new ArrayList();
		
		HashMap dataRecord1 = new HashMap();
		dataRecord1.put("id", "0");
		dataRecord1.put("text", "根");
		dataRecord1.put("parentId", "");
		dataRecord1.put("weight", "1");
		
		HashMap dataRecord2 = new HashMap();
		dataRecord2.put("id", "1");
		dataRecord2.put("text", "1");
		dataRecord2.put("parentId", "0");
		dataRecord2.put("weight", "1");
		
		HashMap dataRecord3 = new HashMap();
		dataRecord3.put("id", "2");
		dataRecord3.put("text", "2");
		dataRecord3.put("parentId", "0");
		dataRecord3.put("weight", "1");
						
		HashMap dataRecord4 = new HashMap();
		dataRecord4.put("id", "3");
		dataRecord4.put("text", "3");
		dataRecord4.put("parentId", "0");
		dataRecord4.put("weight", "1");
						
		HashMap dataRecord5 = new HashMap();
		dataRecord5.put("id", "4");
		dataRecord5.put("text", "4");
		dataRecord5.put("parentId", "0");
		dataRecord5.put("weight", "1");
		
		HashMap dataRecord6 = new HashMap();
		dataRecord6.put("id", "11");
		dataRecord6.put("text", "11");
		dataRecord6.put("parentId", "1");
		dataRecord6.put("weight", "1");
		
		HashMap dataRecord7 = new HashMap();
		dataRecord7.put("id", "12");
		dataRecord7.put("text", "12");
		dataRecord7.put("parentId", "1");
		dataRecord7.put("weight", "1");
		
		HashMap dataRecord8 = new HashMap();
		dataRecord8.put("id", "21");
		dataRecord8.put("text", "21");
		dataRecord8.put("parentId", "2");
		dataRecord8.put("weight", "1");
		
		HashMap dataRecord9 = new HashMap();
		dataRecord9.put("id", "22");
		dataRecord9.put("text", "22");
		dataRecord9.put("parentId", "2");
		dataRecord9.put("weight", "1");
		
		HashMap dataRecord10 = new HashMap();
		dataRecord10.put("id", "31");
		dataRecord10.put("text", "31");
		dataRecord10.put("parentId", "3");
		dataRecord10.put("weight", "1");
		
		HashMap dataRecord11 = new HashMap();
		dataRecord11.put("id", "32");
		dataRecord11.put("text", "32");
		dataRecord11.put("parentId", "3");
		dataRecord11.put("weight", "1");
		
		HashMap dataRecord12 = new HashMap();
		dataRecord12.put("id", "41");
		dataRecord12.put("text", "41");
		dataRecord12.put("parentId", "4");
		dataRecord12.put("weight", "1");
		
		HashMap dataRecord13 = new HashMap();
		dataRecord13.put("id", "42");
		dataRecord13.put("text", "42");
		dataRecord13.put("parentId", "4");
		dataRecord13.put("weight", "1");
		
		HashMap dataRecord14 = new HashMap();
		dataRecord14.put("id", "43");
		dataRecord14.put("text", "43");
		dataRecord14.put("parentId", "4");
		dataRecord14.put("weight", "1");
				
		HashMap dataRecord15 = new HashMap();
		dataRecord15.put("id", "121");
		dataRecord15.put("text", "121");
		dataRecord15.put("parentId", "12");
		dataRecord15.put("weight", "1");
		
		HashMap dataRecord16 = new HashMap();
		dataRecord16.put("id", "122");
		dataRecord16.put("text", "122");
		dataRecord16.put("parentId", "12");
		dataRecord16.put("weight", "1");
		
		HashMap dataRecord17 = new HashMap();
		dataRecord17.put("id", "123");
		dataRecord17.put("text", "123");
		dataRecord17.put("parentId", "12");
		dataRecord17.put("weight", "1");
				
		HashMap dataRecord18 = new HashMap();
		dataRecord18.put("id", "321");
		dataRecord18.put("text", "321");
		dataRecord18.put("parentId", "32");
		dataRecord18.put("weight", "1");
		
		HashMap dataRecord19 = new HashMap();
		dataRecord19.put("id", "322");
		dataRecord19.put("text", "322");
		dataRecord19.put("parentId", "32");
		dataRecord19.put("weight", "1");
		
		HashMap dataRecord20 = new HashMap();
		dataRecord20.put("id", "323");
		dataRecord20.put("text", "323");
		dataRecord20.put("parentId", "32");
		dataRecord20.put("weight", "1");
				
		dataList.add(dataRecord1);
		dataList.add(dataRecord2);
		dataList.add(dataRecord3);
		dataList.add(dataRecord4);
		dataList.add(dataRecord5);
		dataList.add(dataRecord6);
		dataList.add(dataRecord7);		
		dataList.add(dataRecord8);
		dataList.add(dataRecord9);
		dataList.add(dataRecord10);
		dataList.add(dataRecord11);
		dataList.add(dataRecord12);
		dataList.add(dataRecord13);
		dataList.add(dataRecord14);		
		dataList.add(dataRecord15);
		dataList.add(dataRecord16);
		dataList.add(dataRecord17);
		dataList.add(dataRecord18);
		dataList.add(dataRecord19);
		dataList.add(dataRecord20);
		
		return dataList;
	}	
}

  

 

四、 联系方式
multipletree@163.com

 

 

   发表时间:2012-04-17  
memorymultitree 写道

他看到昨天使用的五个功能点菜单全部自动展开了,而且菜单顺序也发生了变化:原来排在最后的一级菜单“客户服务”居然移到了“我的贷款”下面,而原来排在第一位的“账户管理”却移到了“客户服务”下面,由于部分菜单自动展开,所以菜单栏右侧出现了滚动条。
不光一级菜单的顺序发生了变化,二级菜单的顺序也发生了变化,原来在“转账汇款”下面的子菜单中,“跨行转账”排在“行内转账”下面,现在却排在了第一位。不光菜单发生了变化,中间主界面也发生了变化,原来是欢迎界面,现在变成了“跨行转账”信息录入界面。

 


高小宝没iteye账号,让我帮忙回复楼主,“尼玛,这什么网银,上次的菜单竟然找不到了,不好用”,完毕。

0 请登录后投票
   发表时间:2012-04-17   最后修改:2012-07-08

*******************************************************************************

这篇文章是我睡觉做梦的时候梦到的,醒来之后赶紧写成文章发到iteye上,

 

我觉得这篇文章最有价值的地方是 “菜单节点搜索” 功能,现有的树形控件应该加入这个功能就完美了。

 

树形菜单的节点数量一般是几十个节点,如果层次比较深的话,要找到某一个节点,找起来很麻烦,应该加入一个搜索功能,您说呢?

另:我做梦的时候一共梦到三篇文章,您别不信,这是真的,分别是:

《多叉树结合JavaScript树形控件实现无限级树形菜单(一种构建多级有序树形结构JSON(或XML)数据源的方法)》

《(原创)新概念智能树形菜单--利用加权多叉树结合JavaScript树形控件实现》

《(原创)利用加权多叉树实现统计分析系统中的树形结构统计报表》

其实这篇文章是个过渡,文中提到的“加权多叉树”的概念是在为第三篇文章做铺垫!我觉得第三篇文章也很有价值,欢迎阅读,谢谢。

另:我这三篇文章是经过十几次修改才逐渐完善的,费了我不少精力,不管是否有价值,请提宝贵意见,多谢了。

文章中提到的新概念“加权多叉树”,到底能够在哪些地方有应用,到底有多大价值,我也不知道,我只是觉得很有价值,所以发表在网上,供大家参考,我不想让肉烂在锅里。

 

各位,对我这篇文章发表回复请到【java综合】板块,那里有一篇帖子,叫《【分享原创文章,推动技术创新】“多叉树”和“加权多叉树”在软件开发中的应用(讨论帖,欢迎各位高手踊跃发表评论)》,专门集中讨论这个话题的,多谢了,链接是:http://www.iteye.com/topic/1124411

*******************************************************************************

0 请登录后投票
   发表时间:2012-04-17   最后修改:2012-04-17

常用菜单不应该在这棵树上,应该在另外一棵菜单树上,或者这棵树有个常用菜单节点,不然菜单经常变化,让使用者无所适从。

搜索功能的菜单树,可以分为。
1.全加载树的js收缩
2.异步加载树的异步后台搜索。
0 请登录后投票
   发表时间:2012-04-17  
anry513 写道
常用菜单不应该在这棵树上,应该在另外一棵菜单树上,或者这棵树有个常用菜单节点,不然菜单经常变化,让使用者无所适从。

 

 很有道理,多谢指教,这是我需要改进的地方。

0 请登录后投票
   发表时间:2012-04-17  
菜单每次都变化会给客户带来不好的体验的!客户每次都要花时间去适应新的树形结构!菜单搜索的话我觉得不应该把其他节点去掉,应该只展开你搜索的节点,关闭其他节点
0 请登录后投票
   发表时间:2012-04-17  
个人觉得搜索可以用,但是各节点权值调整功能放出去有时候用户操作习惯会受影响,估计有一片用户不喜欢这样。
0 请登录后投票
   发表时间:2012-04-17  
LZ的想法不错,对于tree来说,确实是智能了,但是如果菜单是tree,给使用者的角度就是 复杂。我个人感觉,能不用,尽量不用tree来表现(管理系统除外)。
我个人感觉智能按钮更能体现人性化的便捷操作。
0 请登录后投票
   发表时间:2012-04-17   最后修改:2012-06-14
[quote=&quot;zb7503&quot;]LZ的想法不错,对于tree来说,确实是智能了,但是如果菜单是tree,给使用者的角度就是 复杂。我个人感觉,能不用,尽量不用tree来表现(管理系统除外)。我个人感觉智能按钮更能体现人性化的便捷操作。


你说的有道理,用Tree来表现菜单确实感觉复杂,说说你的“智能按钮”是怎么回事?
0 请登录后投票
   发表时间:2012-04-17  
思路挺好 不过应用场景你可能需要再考虑下 确实一般都有两棵树分两个tab页 否则使用起来真的不好用
0 请登录后投票
论坛首页 Web前端技术版

跳转论坛:
Global site tag (gtag.js) - Google Analytics