花千骨之杀骨续写同人:ExtJS 2.2左边导航菜单

来源:百度文库 编辑:九乡新闻网 时间:2024/04/19 18:45:03
左边导航菜单就是Asp.net中TabBox控件。在这里,我们也是形成一个组件的形式,看起来实现是有点复杂,但是如果建立在Ext的组件架构基础之上,就一点也不难了。Ext有一种布局叫做accordion。它是专门用来处理这种效果,它可以应用在任何布局类中(如panel及其子类)。因为布局类中都会有着一些需要布局中的子组件。accordion布局的特性就是任何一个时候都只有一子组件处在激活状态,它的内容处在展开状态,其它不处在激活状态的子组件都会折叠其内容。

如果布局类中的子组件都是面板的话(即Panel类),那么因为面板内容的折叠/展开都是针对于其head标题部分。也就是说展开时,可以看到其内容,没有折叠时能看到其head标题部分,这就和我们TabBox特别相似了。

我们的TabBox是要在任何时刻都要显示每个Tab子项的标题,而任何一个时刻都要显示唯一的激活状态的Tab子项的内容。对于激活状态的Tab子项的高度,它会占据其它Tab子项的所没有占据的所有高度。这个accordion布局,在没有指定子组件时,也会根据子组件来自动计算激活状态子组件的高度以便占据整个剩余高度。

那么说来我们就可以采用Panel布局上加上accordion布局来模拟整个TabBox控件,而itmes中子Panel组件来用模拟Tab子项。组件就是类的形式,那么我们通过Panel来构建这个LeftMenu类。为了使用这个leftMenu类拥有组件特性和Panel的功能,我们让它来继承于Panel类。那么如何使用LeftMenu类拥有Panel类的功能呢?第一是要继承于Panel类,第二是在LeftMenu类把当前的配置项都传入到Panel类的构建函数中去,同时改变它的作用域为当前LeftMenu类的作用域,LeftMenu中的代码:Morik.Office.LeftMenu = function(config) {
Morik.Office.LeftMenu.superclass.constructor.call(this,config||...{});①
    //实现本类的功能                                            ②
}
Ext.extend(Morik.Office.LeftMenu, Ext.Panel, ...{//加上LeftMenu类的方法});③
对于继承,我们可以采用Ext.extend函数,把Panel类中所有的方法都继承到当前类来,另外我们还可以通过第三个参数来实现本类的方法,这个与在构建函数中通过this来实现方法有点不同,那就是其方法都定义在当前类的prototype中。

在①处,我们通过Function类的call方法来改变Panel类的作用域为当前类的作用域,同时把配置项也传递给Panel类。这就是为什么在该类中能使用父类中定义的配置项。在①处我们完全是传入当前类的配置项,这里应该给了出一些默认值,而对于layout的配置项就不能通过当前类传入配置项来进行修改。看一下如何实现LeftMenu中的代码:var d = Ext.apply( {// default set
              split : true,
              region : 'west',
            width : 200,
              defaults : ...{
                  border : false
              },
              layoutConfig : ...{
                  animate : true
              }
           }, config || ...{});

    config = Ext.apply(d, ...{
       layout : 'accordion',
       collapsible : true
    });  
    Morik.Office.LeftMenu.superclass.constructor.call(this, config);
这段代码替换代码2.8中①处的代码。它首先apply把传入的配置项和默认是配置项组合起来,如果没有传入就采用默认的配置项。一般说来TabBox都会在左边区域。其默认的宽度差不多也就是200px。每个Tab条目都不需要边框。

接下就是不管是否传入layout和collapsible的值为何值,都采用accordion布局和能折叠来进行配置。这两个是TabBox的核心,不能修改。通过这些,我们就能通过panel父类来实现它的显示出的样子。

我们来运行下看看其效果。在看到其效果之后,我们得调用运行这个类,这个任务要在main.js中完成。即代码2.7第三步中去进行完成。我们讲到了LeftMenu的items子组一定要Panel。同时Panel中的内容是树形的条目(一般都是采用树形结构,也可以不采用)。对应图2.5,我们先创建两个子panel,这个两个子Panel中items又含有一棵树。Main.js中 的代码:var leftmenu = new Morik.Office.LeftMenu( {
           title : '我的办公系统',
           items : [ ...{
              title : '我的办公桌',
              items : [t1]
           }, ...{
              title : '主数据管理',
              items : [t2]
           }]      
}); 
这一段代码取代原有leftmenu变量部分。两个panel子组件都只要定义title,用来指定Tab项的标题,这个是一定要的,一是因为它的的提示作用,第二是因为如果没有它,就不会有Tab条目了。那么不是TabBox了。items中只要包含一颗树。两个panel中分别包含是t1,t2两棵树。

那么我们得在代码前面定义t1,t2两个树。定义树一般都是通过TreePanel在实现树的样式及布局,树的节点通过其树的根节点一层层地串联起来。我们可以采用每个节点都通过new TreeNode来创建,如何节点与节点之间有父子关系,那么就采用appendChild把子节点追加入父节点上,最后形成一条以根节点为首的节点链。把这个根节点通过TreePanel的root配置项传到树panel中。

这样做会有很多冗余代码。对于静态的数据还会一种做法,就是通过AsyncTreeNode做为根节点的类型,之后就可以采用数组及Json对象的形式来动态构建节点链。这个的用法在Ext的docs/resources/docs.js就是采用了它。我们看的文档的左边树的节点数据就是这样实现的。我们还是要看一下实现吧,Main.js中的代码:var t1 = new Ext.tree.TreePanel(  
    border : false,
    rootVisible : false,
    root : new Ext.tree.AsyncTreeNode( ...{
       text : "我的办公桌",
       expanded : true,
       children : [ ...{id : "docRec",text : "接收公文",leaf : true },  
...{id : "docSend",text : "发送公文",leaf : true},  
...{id : "docManage",text : "公文管理",leaf : true}]
           })
       });
var t2 = new Ext.tree.TreePanel( ...{
    border : false,
    rootVisible : false,
    root : new Ext.tree.AsyncTreeNode( ...{
      text : "主数据管理",
      expanded : true,
      children : [
...{id : "department",text : "部门管理",leaf : true},  
...{id : "company",text : "公司管理",leaf : true},
...{id : "permissions",text : "权限管理",
         children : [ ...{id : "permission",text : "权限管理",leaf : true},
...{id : "permissionType",text : "权限类别",leaf : true}]
       }]
    })
});
这两棵树的结构差不多,先通过Ext.tree.TreePanel来生成一个树面板。对于TabBox的树面板,我们要取消其默认的边框线。我们还看到两个配置项,一个是root,一个是rootVisible,即然指定了root,又为什么要隐藏它呢。rootVisible设为false的话,那就是隐藏根节点本身,而根节点的子节点都不会隐藏的。很多时间根节点除了作一个串接所有子节点的作用,就没有它的用途,所以就不要显示出来,这里也是一样。其根节点的提示作用就在上一部分的panel中title定义好了。

在根节点的配置项中,我们一定要指定expanded为true(也可以通过代码来指定)。因为其默认是不展开,通过点击根节点来展开其下层的子节点,而现在隐藏了,不能点击,只能通过其expanded配置项来让其下层的所有子节点都展开。

具体的数据其实都在children配置项给定了。对于每个节点,我们要一般来都要定义三项配置项:id,text,leaf/children。其中id是可以采用默认生成的。因为要用到它做为标识,我们就定义了。对于text,提示作用的。一定是要的。对于AsyncTreeNode的数据,leaf和children一定要配置一个,leaf为true说明其为叶子节点,如果没有就说明其不是叶子节点,那么就得到子节点,这个节点可能是多个,于是就得采用children来进行给定。

把这个加到main.js对应的位置,运行一下,我们就可以看到图2.5中左边菜单的效果。对于上面的代码,我们会觉得不应该把TabBox中Tab项目来放在配置项中来配置,也就是每传入一棵树,就会有一个Tab项。它的标题就是树不显示出来的根节点文本,同时又为能使用items来配置那些可以其内容不是树的组件。我们可以另外实现一个配置,如tree,其要求就是把代码2.10中items用trees:[t1,t2]来替换就能实现同样的效果。

现在我们改进一个其leftMenu类。在代码2.8的②后面加上如下:
for(var i=0;i<this.trees.length;i++)    
this.add(...{title:this.trees[i].getRootNode().text,
items:[this.trees[i]]});  
这段代码就是根据有多少颗树就在LeftMenu中的Items增加多少个Panel子组件。并指定子Panel的标题。加上这代码,在使用它时,就可以不采用itmes,直接采用treees传入tree就可以实现TabBox的效果。

实现完成上面,LeftMenu还没有完成。它还有一个重大的任务就是实现一个点击事件。实现事件可以分成如下三步,第一是在先注册事件名,第二是在类的某一个时刻的代码中运行这个事件函数,(它是和事件名相对应的回调函数)。第三就是在注册这个回调函数。这个注册回调函数在main.js已经完成。

根据上面的步骤,我们在leftMenu构建函数中追加如下代码:this.addEvents('nodeClick');
this.initTreeEvent();第一行是注册事件名,第二行是通过一个类方法来实现运行这个事件函数。这个方法在代码2.8中③处的第三个参数中的注释后面加上它,下面我们就给出代码2.8完整的LeftMenu中的代码:Ext.extend(Morik.Office.LeftMenu, Ext.Panel, {
    initTreeEvent : function() ...{
       if(!this.items) return;
       for (var i = 0;i < this.items.length; i++) ...{
         var p = this.items.itemAt(i);
         if (p)   var t = p.items.itemAt(0);
         if(t)  t.on( ...{
               'click' : function(node, event) ...{
                  if (node && node.isLeaf()) ...{
                     event.stopEvent();
                     this.fireEvent('nodeClick', node.attributes);}}
});
         }
       }
})
这段代码的initTreeEvent是根据LeftMenu类items中子panel找到其中的树组件,之后为每颗树都注册其click事件的回调函数。也就是每次点击树的时候(任何地方)它都会运行这个回调函数。在这个回调函数中,它进一步判断,判断点击是否其树的子节点。如果是的话,就停止事件的默认处理(冒泡给上一层,其缺省的动作),再运行我们在main.js中注册的nodeClick的回调函数。

从运行的函数可以看出,这个回调函数参数只有一个,就是树节点的属性集合,它包括id,text,leaf等属性。我们实现了LeftMenu的中树的点击事件,现在点击树叶子节点就会报错,原因是我们还没有实现MainPanel的loadTab方法。下一节中实现它。