ExtJS专题-TreePanel(1)

来源:互联网 发布:gta5御姐美女捏脸数据 编辑:程序博客网 时间:2024/05/21 22:21

这次我们来学习下ExtJS中的树面板(TreePanel),很多人都说,只是为了树,都要好好学习ExtJS!可见大家对Tree的一往情深。从另一方面来说,Tree这种结构在实际项目中也的确用得相当广泛,所以我们很有必要研究一下它。这次我们完成的树大致上有这样的功能:它的节点是动态异步从后台(存储在数据库中一张电子产品分类表)加载的,节点之间可以拖曳,节点可以编辑,同时还支持右键菜单,而且,它能够和TabPanel结合构成经典的布局方式。呵呵,是不是很强大?!大家已经看到,我们组件的讲解是逐步递推的,所以我们这里也会用到刚学过的GridPanel和TabPanel这些实用的面板。我们看效果先:

 

 

 

好啦,洪哥,我们动手吧!

 

1.      首先还是主要的显示页面tree.html,这里有两个地方要注意一下,一个是我们引用的JS如果采用GBK的默认编码,浏览器会显示未结束的字符串常量的错误,所以我们一般会修改JS文件的编码方式为UTF-8,或者在导入JS时加上编码字符集。第二个是我们要定义一个显示TreePanel的DIV。

 

<!DOCTYPEhtml PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">

<html>

<head>

<metahttp-equiv="Content-Type"content="text/html; charset=UTF-8">

<title>Insert title here</title>

<linkrel="stylesheet"type="text/css"href="resources/css/ext-all.css"/>

<scripttype="text/javascript"src="resources/js/ext-base.js"></script>

<scripttype="text/javascript"src="resources/js/ext-all.js"></script>

<scripttype="text/javascript"src="resources/js/tree.js"charset="gbk"></script>

</head>

<body>

<div id="tree-panel" style="overflow:auto; height:300px;width:200px;border:2px solid #c3daf9;"></div>

</body>

</html>

 

2.      然后是我们的主体JS文件,tree.js,为了凸显主题,这个我已经做了必要的简化,还做了详细的注释,大家好好看一下。

 

Ext.onReady(function() {

    Ext.QuickTips.init();//浮动信息提示

    Ext.BLANK_IMAGE_URL ='resources/images/default/s.gif';//替换图片文件地址为本地

 

    //创建一个简写

    var Tree =Ext.tree;

    //定义根节点的Loader

    var treeloader =new Tree.TreeLoader({

            // dataUrl : 'tree.jsp'//这里可以不需要指定URL,在加载前事件响应里面设置

            });

 

    //添加一个树形面板

    var treepanel =new Tree.TreePanel({

        // renderTo:"tree_div",//如果使用renderTo,则不能使用setRootNode()方法,需要在TreePanel中设置root属性。

        el : 'tree-panel',//将树形添加到一个指定的div,非常重要!

        region : 'west',

        title : '功能菜单',

        width : 200,

        minSize : 180,

        maxSize : 250,

        split : true,

        autoHeight : false,

        frame : true,//美化界面

        // width : 200,//面板宽度

        // title : '可编辑和拖动的异步树',//标题

        // autoScroll : true, //自动滚动

        enableDD : true,//是否支持拖拽效果

        containerScroll : true,//是否支持滚动条

        rootVisible : true,// 是否隐藏根节点,很多情况下,我们选择隐藏根节点增加美观性

        border : true,// 边框

        animate : true,// 动画效果

        loader : treeloader

            //树加载

        });

    //异步加载根节点

    var rootnode =new Tree.AsyncTreeNode({

                id : '0',

                text : '家电品牌总类',

                draggable : false,//根节点不容许拖动

                expanded : true

            });

 

    //tree设置根节点

    treepanel.setRootNode(rootnode);

 

    //响应加载前事件,传递node参数

    treepanel.on('beforeload',function(node) {

                treepanel.loader.dataUrl ='tree.jsp?parentId=' + node.id;// 定义子节点的Loader

            });

    //渲染树形

    treepanel.render();

    //展开节点,第一个参数表示是否级联展开子节点

    rootnode.expand(true);

 

    //设置树的点击事件

    function treeClick(node, e) {

        if (node.isLeaf()) {

            e.stopEvent();

            var n = contentPanel.getComponent(node.id);

            if (!n) {

                var n = contentPanel.add({

                            'id' : node.id,

                            'title' : node.text,

                            closable :true,

                            autoLoad : {

                                url : 'tabFrame.jsp?url=grid.html',

                                scripts : true

                            } // 通过autoLoad属性载入目标页,如果要用到脚本,必须加上scripts属性

                        });

            }

            contentPanel.setActiveTab(n);

        }

    }

    //增加鼠标单击事件

    treepanel.on('click', treeClick);

 

    //定义右键菜单

    var rightClick =newExt.menu.Menu({

                id : 'rightClickCont',

                items : [{

                            id :'rMenu1',

                            text :'添加节点',

                            //增加菜单点击事件

                            handler :function() {

                                alert('添加节点的实现!');

                            }

                        }, {

                            id :'rMenu2',

                            text :'编辑节点'

                        }, {

                            id :'rMenu3',

                            text :'删除节点'

                        }]

            });

    //增加右键点击事件

    treepanel.on('contextmenu',function(node, event) {//声明菜单类型

                event.preventDefault();//阻止浏览器默认右键菜单显示

                rightClick.showAt(event.getXY());//取得鼠标点击坐标,展示菜单

            });

 

    /*

     * 设置tree的节点放置函数此函数有一个很重要的参数对象e e对象有三个重要的属性,分别为dropNode,target,point

     * 1.dropNode为在拖动时鼠标抓住的节点 2.target为将要放置在某处的节点

     * 3.point为被放置的状态,分别有append表示添加,above节点的上方,below节点的下方。

     *

     */

    treepanel.on('nodedrop',function(e) {

 

                if (e.point =='append') {

                    alert('当前"' + e.dropNode.text +'"划到"' + e.target.text

                            + '"里面!');

                } elseif (e.point =='above') {

                    alert('当前"' + e.dropNode.text +'"放在了"' + e.target.text

                            + '"上面!');

                } elseif (e.point =='below') {

                    alert('当前"' + e.dropNode.text +'"放在了"' + e.target.text

                            + '"下面!');

                }

            });

 

    //在原有的树形添加一个TreeEditor

    var treeEditer =new Tree.TreeEditor(treepanel, {

                allowBlank : false

            });

    /*

     * 为创建的treeEditer添加事件有两个事件最为常用,一个为beforestartedit另一个为complete

     * 从名字就可以看出,beforestartedit事件是在编辑前的事件,因此可以通过它来判断那些节点可以编辑那些不可以。

     * complete为编辑之后的事件,在这里面可以添加很多事件,比如添加一个Ext.Ajax向后台传送修改的值等等。

     */

    treeEditer.on("beforestartedit",function(treeEditer) {

                var tempNode = treeEditer.editNode;//将要编辑的节点

                if (tempNode.isLeaf()) {//这里设定叶子节点才容许编辑

                    returntrue;

                } else {

                    returnfalse;

                }

            });

 

    treeEditer.on("complete",function(treeEditer) {

                alert("被修改为" + treeEditer.editNode.text);

            });

 

    //1)通过TabPanel控件的html属性配合<iframe>实现。该方法是利用

    // html属性中包含<iframe>的语法来调用另一个页面,具体见代码。

    //2)通过TabPanel控件的autoLoad属性实现。该方法是利用autoLoad属性,它有很多参数,

    //其中有两个比较重要,url表示要载入的文件,scripts表示载入的文件是否含有脚本,该属性相当重要,

    //如果在新的页面中要创建Ext控件,必须指定该参数。该方法实现较前一个复杂,因为引入的文件不是一个完整的html文件,

    //有可能只是内容的一部分,但是资源占用较少,而且载入速度较快(它有一个载入指示)

 

    //添加第一个节点(html)

    treepanel.root.appendChild(newExt.tree.TreeNode({

        id : 'htmlPanel',

        text : '通过html打开',

        listeners : {

            'click' :function(node, event) {

                event.stopEvent();

                var n = contentPanel.getComponent(node.id);

                if (!n) {// 判断是否已经打开该面板

                    n = contentPanel.add({

                        'id' : node.id,

                        'title' : node.text,

                        closable :true,// 通过html载入目标页

                        html : '<iframe scrolling="auto" frameborder="0" width="100%" height="100%" src="grid.html"></iframe>'

                    });

                }

                contentPanel.setActiveTab(n);

            }

        }

    }));

 

    //添加第二个节点(autoLoad)

    treepanel.root.appendChild(newExt.tree.TreeNode({

                id : 'autoLoadPanel',

                text : '通过autoLoad打开',

                listeners : {

                    'click' :function(node, event) {

                        event.stopEvent();

                        var n = contentPanel.getComponent(node.id);

                        if (!n) {// //判断是否已经打开该面板

                            n = contentPanel.add({

                                       'id' : node.id,

                                       'title' : node.text,

                                       closable : true,

                                       autoLoad : {

                                           url : 'tabFrame.jsp?url=grid.html',

                                           scripts : true

                                       } // 通过autoLoad属性载入目标页,如果要用到脚本,必须加上scripts属性

                                    });

                        }

                        contentPanel.setActiveTab(n);

                    }

                }

            }));

 

    //右边具体功能面板区

    var contentPanel =newExt.TabPanel({

        region : 'center',

        enableTabScroll : true,

        activeTab : 0,

        items : [{

            id : 'homePage',

            title : '首页',

            autoScroll : true,

            html : '<div style="position:absolute;color:#ff0000;top:40%;left:40%;">Tree控件和TabPanel控件结合功能演示</div>'

        }]

    });

 

    newExt.Viewport({

                layout : 'border', // 使用border布局

                defaults : {

                    activeItem : 0

                },

                items : [treepanel, contentPanel]

            });

 

});

 

3.   再接着是tree.js中ExtJS的TreeLoader调用的tree.jsp,在目录树上点击TreeNode后会加载下一级节点。tree.jsp负责TreeNode点击后,传回由下一级节点构造的JSON数据,也就是前台树异步请求访问的后台WEB组件。它会调用JAVABEAN操作数据库,得到每个节点的子节点数据。这里由于我们后台需要返回给客户端JSON格式的数据,也就是需要操作JSON数据格式。所以我们首先要下载JSON-lib,地址:http://json-lib.sourceforge.net/打开网址后,首页上有一段话:
 Json-lib requires (at least) the following dependencies in your classpath:
      jakarta commons-lang 2.3
      jakarta commons-beanutils 1.7.0
      jakarta commons-collections 3.2
      jakarta commons-logging 1.1.1
      ezmorph 1.0.4
   需要下载上述jar文件,配合JSON-lib 一起使用。

   commons 下载地址:http://commons.apache.org/
   ezmorph 下载地址:
http://ezmorph.sourceforge.net
   或者,到 http://www.docjar.com 搜索下载。

   JSON的用法,我们已经提过多次,大家可参考相关文档。看看tree.jsp的代码:

 

<%@ pagelanguage="java"pageEncoding="utf-8"%>

<jsp:useBeanclass="org.leno.javabean.JSONTree"id="JSONTree"></jsp:useBean>

<%

    String parentId = "";

    if (request.getParameter("parentId") !=null) {

        parentId = request.getParameter("parentId").toString();

    }

    JSONTree.setparentId(parentId);

%>

<%=JSONTree.getJSONString()%>

 

4.       后台用到的JAVA类,这里包括访问数据库的数据源工厂类DataSourceFactory(这里用到了DBCP连接池,大家要记得导入连接sqlserver 2000数据库和dbcp连接池的相关JAR包!),定义树节点的属性,包括节点ID、Text、图标、是否为叶子节点、是否展开等的类JSONTreeNode,还有类似DAO能够封装数据访问和格式转换细节的JSONTree

 

package org.leno.javabean;

 

import java.sql.SQLException;

import org.apache.commons.dbcp.BasicDataSource;

 

publicclass DataSourceFactory {

 

    /**

     *@paramargs

     */

    privatestatic BasicDataSourceds;

 

    publicstatic BasicDataSource getDataSource() {

        if (ds ==null) {

            ds =new BasicDataSource();

            ds.setDriverClassName("com.microsoft.jdbc.sqlserver.SQLServerDriver");

            ds.setUrl("jdbc:microsoft:sqlserver://localhost:1433;DatabaseName=java28");

            ds.setUsername("sa");

            ds.setPassword("123");

            ds.setMaxActive(5);

        }

        returnds;

 

    }

 

    publicstaticvoid main(String[] args) {

        //TODO Auto-generated method stub

        try {

            System.out.println(DataSourceFactory.getDataSource()

                    .getConnection());

        } catch (SQLException e) {

            //TODO Auto-generated catch block

            e.printStackTrace();

        }

 

    }

 

}

package org.leno.javabean;

 

/**

 *@authorleno

 *定义树节点的属性,包括节点IDText、图标、是否为叶子节点、是否展开等。

 */

publicclass JSONTreeNode {

 

        private Stringid;           //ID

         private Stringtext;         //节点显示

         private Stringcls;          //图标

         privatebooleanleaf;        //是否叶子

         private Stringhref;         //链接

         private StringhrefTarget;   //链接指向

         privatebooleanexpandable;  //是否展开

         private Stringdescription;  //描述信息

        

        get/set……

}

package org.leno.javabean;

 

import java.sql.*;

import java.util.*;

import net.sf.json.JSONArray;

 

publicclass JSONTree {

private StringparentId;

   

    public String getJSONString(){     

        Connection con =null;

        Statement st = null;

        ResultSet rs = null;

        List<JSONTreeNode> treeNodeArray =null;

       

        String SQLString = "SELECT * FROM Categories WHERE parentId="+this.parentId+" ORDER BY categoryId";   

           

        try

        {

            con = DataSourceFactory.getDataSource().getConnection();

            st = con.createStatement();

            //查找所有拥有下级类别的类别ID

            rs = st.executeQuery("SELECT parentId FROM Categories WHERE parentId>0 Group By parentId Order By parentId");           

           

            StringBuffer parentIDBuffer =new StringBuffer();

            parentIDBuffer.append("|");        

            while(rs.next())

            {

                parentIDBuffer.append(rs.getString("parentId"));

                parentIDBuffer.append("|");

            }

            //得到所有的parentcategoryId列表

            String parentIDString = parentIDBuffer.toString();        

           

            rs = st.executeQuery(SQLString);   

            treeNodeArray = new ArrayList<JSONTreeNode>();

            while(rs.next())

            {

                JSONTreeNode treeNode =new JSONTreeNode();

                String categoryId = rs.getString("categoryId");

                treeNode.setId(categoryId);

                treeNode.setText(rs.getString("categoryName"));

                treeNode.setDescription(rs.getString("description"));         

//                    treeNode.setHref("rightframe.jsp?categoryId="

//                      + rs.getString("categoryId").toString());

//                    treeNode.setHrefTarget("rightFrame");              

               

                if (parentIDString.indexOf("|"+categoryId+"|")>=0)//父节点

                    {

                        treeNode.setCls("folder");

                        treeNode.setLeaf(false);

                        treeNode.setExpandable(false);

                    }

                    else//子节点

                   {

                        treeNode.setCls("file");

                        treeNode.setLeaf(true);

                        treeNode.setExpandable(false);

                    }

                    treeNodeArray.add(treeNode);

            }

           

            JSONArray JsonArray = JSONArray.fromObject(treeNodeArray);//得到JSON数组    

 

            return JsonArray.toString();//返回JSON数据

        }

        catch(Exception e)

        {

            System.out.println("getJSONString() of JSONTree.java throws : "+e.toString());

            return"";

        }

        finally

        {

           try {

           if(con!=null&&!con.isClosed()){

                con.close();

           }

        } catch (SQLException e) {

            //TODO Auto-generated catch block

            e.printStackTrace();

        }

        }

    }  

 

 

    public String getparentId() {

        returnparentId;

    }

 

    publicvoid setparentId(String parentId) {

        this.parentId = parentId;

    }

 

}

 

5.      最后是我们的数据库脚本script.sql,大家用SQLServer 2000数据库创建一个java28的database,然后执行下列脚本即可。

 

createtable Categories

(

    categoryId intidentity(1,1)primarykey,

    categoryName varchar(30),

    description varchar(100),

    parentId int

)

insertinto Categoriesvalues('电脑','关于电脑',0);

insertinto Categoriesvalues('电视机','关于电视机',0);

insertinto Categoriesvalues('数码相机','关于数码相机',0);

insertinto Categoriesvalues('冰箱','关于冰箱',0);

insertinto Categoriesvalues('联想电脑','关于联想电脑',1);

insertinto Categoriesvalues('创维电视机','关于创维电视机',2);

insertinto Categoriesvalues('索尼相机','关于索尼相机',3);

insertinto Categoriesvalues('海尔冰箱','关于海尔冰箱',4);

 

insertinto Categoriesvalues('旭日电脑','关于旭日电脑',5);

insertinto Categoriesvalues('天逸电脑','关于天逸电脑',5);

insertinto Categoriesvalues('创维液晶电视','关于创维液晶电视机',6);

insertinto Categoriesvalues('索尼M23相机','关于索尼M23相机',7);

insertinto Categoriesvalues('海尔G1冰箱','关于海尔G1冰箱',8);

 

select *from Categories

SELECT parentId FROM Categories WHERE parentId>0 GroupBy parentIdOrderBy parentId

 

可能有人对ExtJS中树的节点属性不是很了解,所以我们要啰嗦一下。ExtJS只要求返回的数据格式类似下面这样即可:
[{'text':'welcome.html','id':'welcome.html','cls':'file',myPara:'myValue'},
{'text':'welcome2.html','id':'welcome2.html','leaf':true,'cls':'file','href':'welcome2.html'}]
这些数据是存储到一个数组中的,数组中的每一项代表一个节点,每一个节点都包含以下几个主要属性
:
text:定义该节点显示的名称
;
id:定义该节点的页面ID,便于document.getElementById方法获取该节点
;
leaf:true或者false,定义该节点是否是叶子节点
;
cls:定义该节点的class(显示的样式
);
href:定义点击该节点后链接的页面
;
另外你还可以为节点增加自定义的属性,方法如上面的myPara:'myValue'一样。ExtJS会自动将返回的数据解析成节点并正确显示到页面上。

 

好啦,今天我们TreePanel的学习就到这里,下次我们会学习FormPanel。祝愿大家学有所成。