`

EXTJS实现的TREEGRID(后台java,框架SpringMVC)

阅读更多

一。一些废话

近日来,有几个项目用到了EXTJS作为Web前端。也看到了一些童鞋苦苦在网上寻觅树形列表控件,碰巧项目中用到了一种,现在给大家分享一下。

二。前端

      前端代码,用的是JS。主要就是指定数据读取地址,然后定义列表表头。这里实例化TreeGrid对象

// function loadGrid(json) 方法中的内容,由第三个代码片段,取得表头方法调用
// 等号左边,定义了一个对象,相当于var a=,这里用的是命名空间statisticBaseSummary,定义一个属于这个命名空间的对象
// 等号右边实例一个TreeGrid,在ExtJs4以前的版本都是在Ext.ux.tree命名空间下,ExtJs4以及日后版本全部整理到了Ext.Forml的空间下
// 并且名称发生了变化
statisticBaseSummary.gridPanel = new Ext.ux.tree.TreeGrid({
        autoScroll : 'auto',// 滚动条
        baseParams: statisticBaseSummary.parameter,// 请求树形数据参数
        columnLines: true,// 斑马线
        columns: Ext.util.JSON.decode(createDynamicGrid(fieldsArr, headerArr, headerHiddenArr, fieldsWidthArr, fieldsAlingArr)),// 动态列,如果不用动态列可以直接定义,关于动态列的做法另开博文介绍
        tbar : statisticBaseSummary_tbar,
        region : 'center',
        loadMask : true,// 如何加载
        useArrows: true,
        enableSort : false,
        animate: true,
        enableDD: true,
        containerScroll: true,
        requestMethod : 'POST',// 请求方式
        // 这里发现没有用到store对象,直接定义了一个服务器请求地址(statisticBaseSummary.reportUrl,这个是在文件头部定义的静态常量,value是url地址,比如:path + '/getTreeGridInfo'等等)
        // 我们可以看看TreeGrid的源码发现,虽然没有定义store对象但是在源码中确实实例了一个store对象,可以想象,我们应该可以用store来处理数据请求
        // 不过设立了dataUrl这种方式虽然有数据处理局限性,但是目前我还是建议用3.x版本的童鞋都采用这种方式
        dataUrl: statisticBaseSummary.reportUrl,
        listeners: {
                // 数据返回到页面之后触发这个事件
            load : function(node) {
                // 监听你想的处理,比如load的等待画面隐藏之类的
            }
        }
    });

 下面这个方法,是做一个列头出来。

/** 基本信息-数据列 */
function createDynamicGrid(fieldsArr, headerArr, headerHiddenArr, fieldsWidthArr, fieldsAlingArr) {
	var statisticColumns = "[";
    for (var i = 0; i < fieldsArr.length; i++) {
        statisticColumns += "{header:\"<div style='text-align:center'>" + headerArr[i] + "</div>\", width:" + fieldsWidthArr[i] + ", dataIndex:'" + fieldsArr[i] + "', hidden:" + headerHiddenArr[i] + ", align:'" + fieldsAlingArr[i] + "'}";
        if (i < fieldsArr.length - 1) {
            statisticColumns += ",";
        }
    }
    statisticColumns += "]";
    return statisticColumns;
}
 下面这个方法是上面的方法所需的参数,是异步取得,但是一定要在new gridtree之前取得。

 

/** 取得表头 */
function getData() {
	Share.AjaxRequest({
		method: 'POST',
		url: homePage.homePageHeaderUrl, // 设定Ajax请求数据源路径
		showMsg : false,
		params : homePage.parameter,
		callback : function(json) {
			//var obj = Ext.util.JSON.decode(request.responseText);
			var obj = json.o;
			homePage.headerMsg = obj;
			loadGrid(json); // 这个loadGrid方法就是上面的构造树形列表的方法
		}
	});
}

 

三。后端

      相对前端来说,后端动态取得一个可以让TreeGrid能够识别的数据则是关键的地方。后端我用的是JAVA,框架是SpringMVC。数据总体来说是一个List(里面放着Map)。

      (一) 我们首先需要分析,官方给的例子的JSON数据是什么样子的。

[{
    // 表头第一列,该列也是树形列(aList:把这一列的数据作为aList),可以看图一
    task:'Project: Shopping',
    // 表头第二列,从这列开始就是与第一列的每一个节点所对应的数据字段了(把从第二以及以后列的数据作为bList)
    duration:13.25,
    user:'Tommy Maintz',// 表头第三列
    iconCls:'task-folder',// 节点图标样式,与数据无关
    expanded: true,//是否可以展开,与数据无关
    // 接下来就是上面那个节点task:'Project: Shopping'的子节点以及子节点对应的数据
    children:[{
        // 表头第一列,该列也是树形列(aList:把这一列的数据作为aList),可以看图一
        task:'Housewares',
        // 表头第二列,从这列开始就是与第一列的每一个节点所对应的数据字段了(把从第二以及以后列的数据作为bList)
        duration:1.25,
        user:'Tommy Maintz',// 表头第三列
        iconCls:'task-folder',// 节点图标样式,与数据无关
        
        // 接下来就是上面那个节点task:'Project: Shopping'的子节点以及子节点对应的数据
        children:[{
            task:'Kitchen supplies',
            duration:0.25,
            user:'Tommy Maintz',
            leaf:true,
            iconCls:'task'
        }
   // 下面的代码我就略去了,上面的编码已经说明了Treegrid的数据格式
   // 首先task这个属性是固定的
   .......
    ]}
}]

 

 

      (二)看过数据格式了,那么我们分析一下。

从这段代码可以看出,数据属性格式,一共分为三部分,一个是节点,一个是节点对应的数据,一个是节点对应的子节点以及子节点的数据。请往下看

 第一部分(节点):

 task:树形的节点,这个请固定不要更改

 iconCls:树形节点的icon

 expanded:树形节点是否可以展开

 第二部分(节点对应的数据):

 duration、user:这是数据表示列,样例中是有两个,但是我们可以定义很多列,当然你需要表示的列为准

 第三部分(子节点以及节点对应数据)

 children:隶属于该节点的下属节点以及节点数据。

 

      针对于第三部分来说,我们可以发现,children中的结构实际上就是包含了上述这三部分属性内容。如果这块比较难以理解的话,我们可以倒过来想。有一个这样的叶子节点A,这种节点是一个没有子节点的节点,那么A的children属性是没有内容的是null,B节点是A节点的父节点,那么B节点的children属性内容就是A。这样大家明了了吧,如果再进一步,假设C节点是B节点的父呢?C节点的children属性内容就是B,那么C-B-A就构成了三级树形结构,其中这三个节点的属性字段都是一致的只是内容不同。这里大家会问,如果B节点的子节点是A和D呢,那么B的children属性值就是A+D,储存形式是:chidren.add(A),chidren.add(D)。这样一来B的子节点就是A和D了。

 

(三)如何实现。

首先,我们要定义一个List allList;

其次,我们要定义一个Model,这里我们定义为HashMap rootMap;

最后,我们要有两个中间交换变量,一个是childrenList,childrenMap;

 

那么我们现在从数据库检索出来两个List:

1.aList:代表树形节点数据,里面的Model一定会有这样几种字段:

parentId(该节点的父节点id)

id(该节点id,一般为主键)

name(该节点名称)

type(是否为叶子节点)

level(节点等级)

weight(权重,一般用于排序来用)

  这些字段,我们只用前4个即可。

2.bList:代表对应树形节点的数据,这里面的Model一般是我们自定义的,且一定有一个或者多个字段与树形节点对应。

 

       接下来我们谈谈如何打造出样例中的数据。

       其实就是将aList与bList结构逻辑化为样例中的数据格式,就是讲这两个List置换成rootMap放到allList中。

 

// 我们需要两个方法,一个是调用,一个是执行转换
public ArrayList result() {
        // 。。。。。前面的代码省略
        // 先定义一个返回List,就是最终我们需要的Treegrid默认的数据格式
        ArrayList allList = new ArrayList<HashMap<String, Object>>();
        // 定义一个根Map,这个Map直接被allList.add(rootMap)这个List中
        HashMap rootMap = new HashMap();
        
        // 定义子List
        ArrayList childrenList= new ArrayList<HashMap<String, Object>>();
        // 定义子Map,这个Map直接被allList.add(rootMap)这个List中
        HashMap childrenMap = new HashMap();

        // 下面是aList, bList的定义,大家可以更改
	List<HashMap> bList = dao.getBList();
        HashMap<String, Object> bResult = new HashMap<String, Object>();
	String temp = "";
	for (Iterator iterator = bList.iterator(); iterator.hasNext();) {
		HashMap rs = (HashMap) iterator.next();
		
		ModelB modelB= new ModelB();
		temp = rs.get("ID") == null ? "" : rs.get("ID").toString();// 对应的树形节点ID
		modelB.setFiled1(Integer.parseInt(rs.get("filed1") == null ? "0" : rs.get("filed1").toString()));
		modelB.setFiled2(Integer.parseInt(rs.get("filed2") == null ? "0" : rs.get("filed2").toString()));
		modelB.setFiled3(Integer.parseInt(rs.get("filed3") == null ? "0" : rs.get("filed3").toString()));
	
		bResult.put(temp, modelB);
	}

        ArrayList aList = dao.getAList();
        Iterator iterator = aList.iterator();
	if (iterator.hasNext()) {
                HashMap tempMap = (tempMap)iterator.next();
                String id = tempMap.get("ID");
                String parentId = tempMap.get("parentId");
                String name = tempMap.get("name");//第一条树形结构数据,一定要是根节点。比如总计、合计等。也就是说在aList取得的语句中要排序
                rootMap.put("task", name);
		rootMap.put("expanded", "true");
                run(iterator, rootMap, null, bResult, childrenList)
	}


        // 调用执行转换方法返回追最终的List
        allList.add(rootMap);

        // 返回查询的List
        return allList;
}

/**
 * Treegrid数据转换
 * 
 * @param  iterator:节点记录
 * @param  parent:父节点对象
 * @param  last:子节点对象
 * @param  bResult:节点对应的数据
 * @param  childrenList:子节点集合
 * @return 
 */
public void run(Iterator iterator, HashMap parent, HashMap last, HashMap bResult, ArrayList childrenList) {
       // 如果last(这个对象是子节点对象,包括子节点和对应的数据,第一次进到这个方法的时候为null)非空,则将last放到parent中
       if (last!= null) parent.put("children", last);
       if (childrenList== null) childrenList= new ArrayList<HashMap<String, Object>>();
       while (iterator.hasNext()) {
                // 得到节点
                HashMap rs = (HashMap) iterator.next();
                // 声明一个ic,这个对象就是子节点对象,包括节点和节点对应的数据
                HashMap ic = new HashMap();
                String id = rs.get("ID");
                String parentId = rs.get("parentId");
                String name = rs.get("name");
                ic.put("id", id);
                ic.put("parentId", parentId);        
                ic.put("task", name);
		ic.put("expanded", "true");
                // 利用节点ID从bResult中寻找是否有对应该节点的数据,如果有的话则将节点对应的数据put到ic中
                if (bResult.containsKey(ic.get("id")))
			ic.put("children", bResult.get(ic.get("id")));
		else
                        ic.put("children", "");

                        // 下面的代码就很重要了,是一个递归调用自身的逻辑
                        // 首先判断节点对应的数据是否存在,如果为null则代表该节点为根节点
                       if (last == null) {
                                // 将根节点对应的数据put到父节点对象中
                                childrenList.add(ic);
                                parent.put("children", childrenList);
                                // 我们得到的ic子节点对象就变为下一次循环的父节点对象了
				last = ic;
                                // 根节点记录处理结束,进行下一节点处理,就是走上面的那个while循环的处理。
			} else {
                                // 如果当前子节点对象ic所对应的父节点与前一个子节点对象last所对应的节点记录一致
                                // 代表当前子节点为前一节点子节点
                                while (ic != null && ic.get("parentId").equals(last.get("id")))
				        ic = run(iterator, last, ic, bResult, null);
                                // 如果当前子节点对象ic所对应的父节点与父节点对象parent所对应的节点记录一致
                                // 代表当前子节点与前一节点同为parent的子节点
				if (ic != null && ic.get("parentId").equals(parent.get("id"))) {
                                        childrenList.add(ic);
					parent.put("children", childrenList);
					last = ic;
				} else
					return ic;
			}
         }

 

以上代码则完成了aList,bList的转换,返回结果为Treegrid能够解析的数据,接下来就是返回给页面端的Controller.java

    介绍这个文件主要就是告诉大家,利用SpringMVC框架直接返回List对象给TreeGrid控件。

/** Report列表数据 */
	@RequestMapping(value = "statisticSummarizing", method = RequestMethod.POST)
	@ResponseBody
	public ListstatisticSummarizing(ExtPager pager, HttpServletRequest request,
			@RequestParam(required = false, defaultValue = "") HashMap<String, String> queryParam) {
		//清除空字符串
		Criteria criteria = new Criteria(request, queryParam, dataDialect);
		if (pager.getLimit() != null && pager.getStart() != null) {
			criteria.setSplit(pager);
		}
                //这里返回了List容器对象,这个对象直接会被TreeGrid控件解析
		return statisticManager.statisticSummarizing(criteria);
	}

四。总结

      以上就是EXTJS实现的TREEGRID的具体步骤,如有不明请留言。

五。整个代码

<%@ page language="java" contentType="text/html; charset=UTF-8"%>
<%@ page import="java.util.*;"%>
<%@ include file="/WEB-INF/views/commons/taglibs.jsp"%>
<div id="${param.id}_div"></div>
<script type="text/javascript">
Ext.ns("Ext.Conac.homePage");
homePage = Ext.Conac.homePage;
homePage = {
	homePageHeaderUrl : ctx + "/homepage/homePageHeader",
	homePageDataUrl : ctx + "/homepage/homePageData",
	zhuanSongPage : ctx + "/instauration/zhuanSongPage",
	parameter : new Object(),
	headerMsg : '',
	reportType : 'summary',	// 统计报告类型:简表:simple,汇总:summary
	reportFlag : '2',
	searchFlag : '0',
	textFlag : '0',	
 	treeParentId : '',
	treeGroupId : '1',
	treeGroupName : 'XXXXXX',
	headerMsg : '',
	type : '1'	// 统计表查询功能
};

/** Search页面 */
homePage.searchPanel = new Ext.form.FormPanel({
        region : 'north',
		margins:'1 1 1 1',
		border:false,
		height: 60,
		layout : 'absolute',
		defaults: {
			style: {
				margin: '0 0 0 10px' 
			}
		},
        items: [{
                xtype: 'checkbox',
                name :'searchFlag',
                boxLabel: '<b>点击这里搜索</b>',
                listeners :{
                	check : function(chkBox,checked){
                		var f = homePage.searchPanel.findById('homePage-fieldset1');
                		var c = homePage.searchPanel.findById('homePage-container1');
                		
                   		if(checked){
                   			f.setVisible(true);
                   			homePage.searchPanel.setHeight(170);
                   			c.setPosition(0,145);
                   		}else{
                   			homePage.searchPanel.getForm().reset();
                   			f.setVisible(false);
                   			homePage.searchPanel.setHeight(60);
                   			c.setPosition(0, 35);
                   		}
                   		if(homePage.myPanel){
               				homePage.myPanel.doLayout(false);
               			}
                	}
                }
            },{
            	id:'homePage-fieldset1',
                xtype: 'fieldset',
                height: 130,
                layout: 'absolute',
                hidden: true,
                border: false,
                x: 30,
                y: 25,
                items: [{
                        xtype: 'label',
                        text: 't1:',
                        x: 5,
                        y: 5
                    },{
                        xtype: 'label',
                        text: 't2:',
                        x: 5,
                        y: 30
                    },{
                        xtype: 'label',
                        text: 't3:',
                        x: 5,
                        y: 50
                    },{
                        xtype: 'textfield',
                        name:'certId',
                        maxLength: 12,
                        maxLength: 12,
                        width: 225,
                        x: 75,
                        y: 0
                    },{
                        xtype: 'textfield',
                        name:'unitName',
                        maxLength: 50,
                        width: 225,
                        x: 75,
                        y: 25
                    },{
                    	id:'holdUnit',
                        xtype: 'textfield',
                        name:'holdUnit',
                        editable: false,
                        width: 225,
                        x: 75,
                        y: 50
                    },{
                        xtype: 'button',
                        width: 50,
                        iconCls:'tick',
                        text: '选择',
                        x: 300,
                        y: 50,
                        handler :function(){
                    		Share.SelectJbdwCategory.show('holdUnit', 'jbdwId');
                        }
                    },{
                        xtype: 'button',
                        width: 50,
                        iconCls:'query',
                        text: '搜索',
                        x: 160,
                        y: 77,
                        handler :function(){                    		
                    		var params = homePage.searchPanel.getForm().getValues();
                    		homePage.parameter["flag"] = params.flag;	// 0: ;1:
                    		homePage.parameter["certId"] = params.certId;
                    		homePage.parameter["unitName"] = params.unitName;
                    		homePage.parameter["holdUnit"] = params.holdUnit;
    						
    						callReport();
                        }
                   
                    },{
                        xtype: 'radio',
                        name : 'flag',
                        checked :true,
                        inputValue: '0',
                        boxLabel: '不搜索子节点',
                        x: 370,
                        y: 50
                    },{
                        xtype: 'radio',
                        name : 'flag',
                        inputValue: '1',
                        boxLabel: '搜索子节点',
                        x: 470,
                        y: 50
                    },{
                    	id : 'jbdwId',
                        xtype: 'hidden',
                        name:'jbdwId'
                    }
                ]
            },{
                  xtype: 'container',
                  id : 'homePage-container1',
                  html: "<div style=\"color:blue;\"><a href=\"#\" onclick=\"Share.openMorePage('/homepage/quickAnnualCheck','快速年检')\"><b>${annualCheckYear}t4</b></a></div>",
                  x: 0,
                  y: 35
              }
        ]
	});
	
/** Report报表 */
homePage.reportGrid = new Ext.FormPanel({
	layout: 'fit',
	border:false,
	region : 'center'
});

/** 最外层布局 */
homePage.myPanel = new Ext.Panel({
	renderTo : '${param.id}' + '_div',
    layout: 'border',
    border:false,
    items: [homePage.searchPanel, homePage.reportGrid],
	height : index.tabPanel.getInnerHeight() - 1
});

/** 基本信息-数据列 */
function createDynamicGrid(fieldsArr, headerArr, headerHiddenArr, fieldsWidthArr, fieldsAlingArr) {
	var statisticColumns = "[";
    for (var i = 0; i < fieldsArr.length; i++) {
        statisticColumns += "{header:\"<div style='text-align:center'>" + headerArr[i] + "</div>\", width:" + fieldsWidthArr[i] + ", dataIndex:'" + fieldsArr[i] + "', hidden:" + headerHiddenArr[i] + ", align:'" + fieldsAlingArr[i] + "'}";
        if (i < fieldsArr.length - 1) {
            statisticColumns += ",";
        }
    }
    statisticColumns += "]";
    return statisticColumns;
}

/** report表单展示 */
function loadGrid(json) {
	waiting = Ext.Msg.wait('正在处理,请稍等...', '', '');
    var fieldsArr = new Array();
    var headerArr = new Array();
    var headerHiddenArr = new Array();
    var fieldsWidthArr = new Array();
    var fieldsAlingArr = new Array();
    var records = json.rows;    // 将Json数据过滤得到每一行数据
    // 将过滤过的Json的行数分进行如下操作
	for(var i=0;i<records.length;i++){
		// 取得每一个域名种类
		var record = records[i];
		fieldsArr[i] = record.name;
		headerArr[i] = record.text;
		headerHiddenArr[i] = record.hidden;
		fieldsWidthArr[i] = record.width;
		fieldsAlingArr[i] = record.alignment;
	}	
	
	/** Report顶部工具条 */
	homePage.tbar = new Ext.Toolbar({
		height: 26,
		items: ["<div id='_tbar_item'>t5</div>"]
	});
	
	/** Report中央布局如果已经渲染关闭数据加载窗口 */
	if (homePage.reportGrid) {
		/** Report顶部工具条-信息 */
		if (waiting != null) {
			waiting.hide();
		}
	}

	/** Report中央布局  */
	homePage.gridPanel = new Ext.ux.tree.TreeGrid({
		enableSort : false,
		autoScroll : 'auto',
        baseParams: homePage.parameter,
        columns: Ext.util.JSON.decode(createDynamicGrid(fieldsArr, headerArr, headerHiddenArr, fieldsWidthArr, fieldsAlingArr)),
		tbar : homePage.tbar,
		region : 'center',
		loadMask : true,
	    useArrows: true,
    	autoScroll: true,
    	animate: true,
    	enableDD: true,
    	containerScroll: true,
		border: false,
		requestMethod : 'POST',
        dataUrl: homePage.homePageDataUrl,
        listeners: {
        	load : function(node) {
        		if (node.isFirst) {
        			if (waiting != null) {
        				waiting.hide();
        			}
        		}
        	}
        }
	});
	
    homePage.reportGrid.add(homePage.gridPanel);
    homePage.reportGrid.doLayout(false);
}

/** 取得表头 */
function getData() {
	Share.AjaxRequest({
		method: 'POST',
		url: homePage.homePageHeaderUrl, // 设定Ajax请求数据源路径
		showMsg : false,
		params : homePage.parameter,
		callback : function(json) {
			//var obj = Ext.util.JSON.decode(request.responseText);
			var obj = json.o;
			homePage.headerMsg = obj;
			loadGrid(json);
		}
	});
}

/** 确定按钮操作 */
function callReport() {	
	homePage.reportGrid.remove(homePage.gridPanel);

	homePage.parameter["start"] = '0';	// 初期表示数据
	homePage.parameter["limit"] = homePage.pageSize;				// 统计表查询功能
	
	// 取得表头 
	getData();
}

callReport();

//转送
function openZhuanSong(sydwName, applyId) {
	homePage.zhuansongWindow = new Ext.Window({
        title : '转送',
        pageY : 100,
        width :370,
        height : 180,
        plain : true,
        border : false,
        modal : true,
        resizable : false,
        autoScroll : true,
        autoLoad : {
            url : homePage.zhuanSongPage,
            params : {
				sydwName : sydwName,
				applyId : applyId
			},
            method: 'GET',
            scripts : true,
            nocache : true
        }
    });
    homePage.zhuansongWindow.show();
}

</script>

 

分享到:
评论
6 楼 gotosuzhou 2014-12-25  
你好,你的tree-grid 有分页吗?我现在的需求是tree-grid 如果  一级菜单多的话,希望有分页,但是现在好像没有这些
5 楼 yayamebaobei0 2013-11-06  
有图有效果图没有
4 楼 hwak 2013-07-05  
heroshell 写道
每一次动态生成列时都需要new一个treeGrid吗?还是有其他的方法?createDynamicGrid放是异步从后台获取column的定义吗

可以销毁前一个,创建一个新的。这样不会占用资源。
另外,你问的那个方法,我添加了,请参考。
3 楼 hwak 2013-07-05  
陈卫林 写道
aList 、b List获取的list是同一个?
run方法中部知道你这个temp是怎么来的。
HashMap ic = new HashMap();  
String id = temp.get("ID"); 


第一个问题:aList 、b List获取的list是同一个?
回答:不是
      aList容器中装载树形节点,比如说操作系统中的文件夹,他是每一级文件夹集合。
      bList:代表对应树形节点下面的数据,比如说操作系统中文件夹中的文件集合。
第二个问题:run方法中部知道你这个temp是怎么来的。
回答:笔误
      多谢,陈卫林童鞋,我写错了,temp应该是rs,上面的方法后面还有一个temp改为tempMap。
文章已经修改,请参考。
2 楼 heroshell 2013-05-10  
每一次动态生成列时都需要new一个treeGrid吗?还是有其他的方法?createDynamicGrid放是异步从后台获取column的定义吗
1 楼 陈卫林 2013-01-16  
aList 、b List获取的list是同一个?
run方法中部知道你这个temp是怎么来的。
HashMap ic = new HashMap();  
String id = temp.get("ID"); 

相关推荐

Global site tag (gtag.js) - Google Analytics