只为了树也要学ext

3.1. 真的,我是为了树,才开始学ext的。
之前使用过xtree和dojo中的tree,感觉都是怪怪的,界面简陋,功能也不好上手,待看到ext里的树形真是眼前一亮,在此之前,动态增添,修改删除节点,拖拽和右键菜单,我一直认为是不可能实现的任务,而在ext上却轻松实现了,而且界面和动画效果相当完美。真是让人爱不释手啊。

树形是非常典型的一种数据结构,多级菜单,部门组织结构,省市县三级这种金字塔结构都可以用树形表示,要表示一个老爸有一帮孩子的情况真是非树形莫属啊,做好了这部分,绝对是个亮点。

3.2. 传统是先做出一棵树来。
树形世界的万物之初是一个TreePanel。
var tree = new Ext.tree.TreePanel('tree');
这里的参数'tree',表示渲染的dom的id。html写着个<div id="tree"></div>做呼应呢,最后这棵树就出现在这个div的位置上。

现在我们获得了树形面板,既然是树就必须有一个根,有了根才能在上边加枝子,放叶子,最后装饰的像一棵树似的。嗯,所以根是必要的,我们就研究研究这个根是怎么咕哝出来的。
var root = new Ext.tree.TreeNode({text:'偶是根'}); //看到了吧,它自己都说它自己是根了,所以它就肯定是根没错。再看下面。

tree.setRootNode(root);
tree.render();

首先,我们把这个根root,放到tree里,用了setRootNode()方法,就是告诉tree,这是一个根,你可得把它放好啊。

立刻对tree进行渲染,让它出现在id="tree"的地方,这个id可是在上面指定的,如果有疑问,请翻回去继续研究,我们就不等你,继续了。

当当,我非常荣幸的向您宣布,咱们的第一棵树出来了。这是它的照片。


3.3. 超越一个根
上回书说道,我们要偷偷插上几个杈子,让这个本来就是树的树,更像一棵树。
var root = new Ext.tree.TreeNode({text:'偶是根'});
var node1 = new Ext.tree.TreeNode({text:'偶是根的第一个枝子'});
var node2 = new Ext.tree.TreeNode({text:'偶是根的第一个枝子的第一个叶子'});
var node3 = new Ext.tree.TreeNode({text:'偶是根的第一个叶子'});
node1.appendChild(node2);
root.appendChild(node1);
root.appendChild(node3);


嗯,现在的确有点儿意思了,不过它开始的时候就那么缩在一团,看着很不爽,每次都要点这么几下才能看到底下的东西,咱们有没有办法让它每次渲染好就自己展开呢?

方法当然有咯,请上眼。

 

root.expand(truetrue);
这一下就能解您燃眉之急,第一个参数是说,是否递归展开所有子节点,如果是false,就只展开第一级子节点,子节点下面还是折叠着的,。第二个参数是说是否有动画效果,true的话,你可以明显看出来那些节点是一点儿点儿展开的,否则就是刷拉一下子就出来了.




3.4. 你不会认为2.0里跟1.x是一样的吧?
第一个区别就是TreePanel的定义,原来的id要放到{}中,对应的名字是el。像这样改:

var tree = new Ext.tree.TreePanel({
    el:'tree'
}
);


即使这样改完了,还是什么都看不见,我们错过了什么?用findbug看了一下dom,height竟然是0,当然啥也看不见了,2.0里的树为啥不会自动伸缩呢,只好咱们给它设置一个初始高度,在html里设置个300px的高度,就可以显示出来了。
<div id="tree" style="height:300px;"></div>


另一个也如法炮制。我们就可以看到2.0比1.x多了鼠标移到树节点上时的高亮显示。

好了,看了这些例子,应该对树型有些认识了,虽然这里只有TreeNode,却能表示枝杈或者叶子,原理很简单,如果这个TreeNode下有其他节点,它就是一个枝杈,如果没有,它就是一个叶子,从它前头的图标就很容易看出来。嘿嘿,根其实就是一个没有上级节点的枝杈了。实际上,他们都是TreeNode而已,属性不同而已。

3.5. 这种装配树节点的形式,真是让人头大。
如此刀耕火种不但麻烦,而且容易犯错,有没有更简便一些的方法吗?答案是利用Ext.tree.TreeLoader和后台进行数据交换,我们在只提供数据,让TreeLoader帮咱们做数据转换和装配节点的操作。


啦啦啦,json和ajax要登场了,不过你是否还记得我说过,一旦涉及到ajax就需要配合服务器了,ajax是无法从本地文件系统直接取得数据的。

首先,让我们为TreePanel加上TreeLoader



var tree = new Ext.tree.TreePanel('tree', {
    loader: 
new Ext.tree.TreeLoader({dataUrl: '03-03.txt'})
}
);

在此,TreeLoader仅包含一个参数dataUrl: '03-03.txt',这个dataUrl表示,在渲染后要去哪里读取数据,为了方便模拟,我们写了一个txt文本文件提供数据,直接打开03-03.txt可以看到里边的内容。
[
    
{text:'not leaf'},
    
{text:'is leaf',leaf:true}
]


里边是一个包含了两个节点定义的数组,可能你会发觉那个多出来的属性leaf:true,它的效果很神奇,这一点我们马上就可以看到。

如果你现在就去匆匆忙忙的刷新页面,想看一下咱们的成果,那一定会失望而归,页面上没有像你期待的那样,从03-03.txt读取数据,显示到页面上,你依然只能看到那个孤零零的根。这是因为TreeNode是不支持ajax的,我们需要把根节点换成AsyncTreeNode,它可以实现咱们的愿望。

var root = new Ext.tree.AsyncTreeNode({text:'偶是根'});



估计谁第一次看到这场面都一定吓傻了。我们不是只定义了两个节点吗?怎么一下子跑出这么多东西来?先别着急,让我们先把root.expand(true, true)改成root.expand(),避免节点无限展开下去,然后慢慢研究这个情况。



现在场面被控制住了,取消了递归展开,只展开根节点的第一层节点,我们得到的确实是与03-03.txt文件里相对应的两个节点,不过这两个节点有些不同,not leaf节点的图标赫然是枝杈的图标,如果点击它前面的加号,便又成了上面的场景。Why?

原因就来自AsyncTreeNode,这个东西会继承根节点TreeLoader中dataUrl,你点展开的时候,会执行这个节点的expand()方法,ajax会跑到dataUrl指定的地址去取数据,用firebug可以看到当前节点id会作为参数传递给dataUrl指定的地址,这样我们的后台就可以通过这个节点的id计算出该返回什么数据,得到了数据TreeLoader去解析数据并装配成子节点,然后显示出来。
哈哈,现在就是关键部分了。因为咱们使用的03-03.txt提供的数据,不会判断当前节点的id,所以每次返回的数据都是一样的,这就是树会无限循环下去的原因了。

那么为啥只有第一个节点会无限循环下去呢?第二个节点就没有那个小加号,呵呵~因为第二个节点不是AsyncTreeNode ,TreeLoader在生成节点的时候会判断数据里的leaf属性,如果是leaf:true,那么就会生成TreeNode而不是AsyncTreeNode,TreeNode可不会自动去用ajax取值,自然就不会无限循环展开了。

现实中,异步读取属性的节点是很爽的一件事情,因为你可能要保存成千上万条节点记录。一次性全部装载的话,无论读取和渲染的速度都会很慢。使用异步读取的方式,只有点击某一节点的时候,才去获得子节点属性,并进行渲染,极大的提高了用户体验。而且ext的树形本身有缓存机制,打开一次,再点击也不会去重复读取了,提升了响应速度。

为了巩固学习效果,咱们再写一个json获得数据的例子,这次的json稍微写复杂一点儿。

这次对应的json数据文件是03-04.txt。

[
    
{text:'01',children:[
        
{text:'01-01',leaf:true},
        
{text:'01-02',children:[
            
{text:'01-02-01',leaf:true},
            
{text:'01-02-02',leaf:true}
        ]}
,
        
{text:'01-03',leaf:true}
    ]}
,
    
{text:'02',leaf:true}
]

 


这也可以看作是在数据不多的情况下,一次加载所有数据的途径,只要确保所有叶子节点上都加上leaf:true的属性,就不会出现循环展开的问题了。
 
 
 祝福吧!把表单和输入控件都改成ext的样式
 
 4.1. 不用ext的form啊,不怕错过有趣的东西吗?初看那些输入控件,其实就是修改了css样式表而已。
 你打开firebug看看dom,确实也是如此,从这点看来,似乎没有刻意去使用ext的必要,诚然,如果单单要一个输入框,不管添入什么数据,就点击发
 送到后台,的确是不需要ext呢。你不想用一些默认的数据校验吗?你不想在数据校验失败的时候,有一些突出的提示效果吗?你不想要超炫的下拉列
 表combox吗?你不想要一些你做梦才能朦胧看到的选择控件吗?唉,要是你也像我一样禁不起诱惑,劝你还是随着欲望的节拍,试一下ext的form和输
 入控件。
 4.2. 慢慢来,先建一个form再说

 
var form = new Ext.form.Form({
    labelAlign: 'right',
    labelWidth: 
50
}
);
form.add(
new Ext.form.TextField({
    fieldLabel: '文本框'
}
));
form.addButton(
"按钮");
form.render(
"form");

 
简单来说,就是构造了一个form,然后在里边放一个TextField,再放一个按钮,最后执行渲染命令,在id="form"的地方画出form和里边包含的所有输
入框和按钮来。刷拉一下就都出来了。不过即使这样,圆角边框可不是form自带的,稍稍做一下处理,参见html里的写法。
<div style="width:220px;margin-left:0px;">
    
<div class="x-box-tl"><div class="x-box-tr"><div class="x-box-tc"></div></div></div>
    
<div class="x-box-ml"><div class="x-box-mr"><div class="x-box-mc">
        
<h3 style="margin-bottom:5px;">form</h3>
        
<div id="form"></div>
    
</div></div></div>
    
<div class="x-box-bl"><div class="x-box-br"><div class="x-box-bc"></div></div></div>
</div>
<div class="x-form-clear"></div>


开头结尾那些div就是建立圆角的,有了这些我们都可以在任何地方使用这种圆角形式了,不限于form哟。

2.0里的FormPanel跟1.x里已经基本完全不一样了,咱们先看个简单例子:


代码如下:


var form = new Ext.form.FormPanel({
    defaultType: 'textfield',
    labelAlign: 'right',
    title: 'form',
    labelWidth: 
50,
    frame: 
true,
    width: 
220,

    items: [
{
        fieldLabel: '文本框'
    }
],
    buttons: [
{
        text: '按钮'
    }
]
}
);
form.render(
"form");

html里不需要那么多东西了,只需要定义一个div id="form"就可以实现这一切。明显可以感觉到初始配置更紧凑,利用items和buttons指定包含的控件和按钮。

4.3. 胡乱扫一下输入控件
兄弟们应该都有html开发的经验了,像什么input用的不在少数了,所以咱们在这里也不必浪费唾沫,大概扫两眼也知道ext的输入控件是做什么的。

像TextField,TextArea,NumberField这类当然是可以随便输入的了。

ComboBox,DateField继承自TriggerField。他们长相差不多,都是一个输入框右边带一个图片,点击以后就跳出一大堆东西来让你选择,输入框里头显示的是你选中的东西。

Checkbox和Radio,ext没有过多封装,基本上还是原来的方式。

Button,这个东东其实就是一个好看的div,跟comboBox一样,不是对原有组件的美化,而是重新做的轮子。你可以选择用以前那种难看的type="button",还是用咱们漂亮的div,看你的爱好了。type="submit"和type="reset"也一样没有对应的组件,都使用Button好了。

文件上传框,type="file",因为浏览器的安全策略,想上传文件,必须使用type="file",而且我们不能使用js修改上传框的值,所以非常郁闷,目前的方式是把它隐藏起来,然后在点击咱们漂亮的Button时,触发上传框的点击事件,从而达到上传的目的。在这方面extjs.com论坛上有不少实现上传的扩展控件,咱们可以参考一下。

4.4. 起点高撒,从comboBox往上蹦。
我觉得像TextField啊,TextArea啊,都是在原来的东西上随便加了几笔css弄出来的,大家都会用,所以没什么大搞头,最后综合起来一说就ok了。而这个comboBox跟原有的select一点儿关系都没有,完全是用div重写画的。所以,嘿嘿~

耳听为虚,眼见为实,先看看所谓的comboBox究竟是个什么模样。
 

雀跃吧!超脱了一切的弹出窗口

5.1. 呵呵~跳出来和缩回去总给人惊艳的感觉。
浏览器原声的alert(),confirm(),prompt()显得如此寒酸,而且还不能灵活配置,比如啥时候想加个按钮,删个按钮,或者改改按下按钮触发的事件了,都是难上加难的事情。

既然如此,为何不同ext提供的对话框呢?那么漂亮,那么好配置,可以拖啊,可以随便放什么东西,在里边用啥控件都可以,甚至放几个tab乱切换呀,连最小化窗口的功能都提供了。哈哈,神奇啊,完全可以让alert退役了。

5.2. 先看看最基本的三个例子
嘿嘿,为了加深认识,还是先去看看examples下的例子吧。1.x在dialog目录下。2.0在message-box目录下。

5.2.1. Ext.MessageBox.alert()

Ext.MessageBox.alert('标题', '内容', function(btn) {
    alert('你刚刚点击了 ' 
+ btn);
}
);

现在可以通过第一个参数修改窗口的标题,第二个参数决定窗口的的内容,第三个参数是你关闭按钮之后(无论是点ok按钮还是右上角那个负责关闭的小叉叉),就会执行的函数,嘿嘿,传说中的回调函数。

5.2.2. Ext.MessageBox.confirm()

Ext.MessageBox.confirm('选择框', '你到底是选择yes还是no?', function(btn) {
    alert('你刚刚点击了 ' 
+ btn);
}
);




选择yes或者是no,然后回调函数里可以知道你到底是选择了哪个东东。

5.2.3. Ext.MessageBox.prompt()

Ext.MessageBox.prompt('输入框', '随便输入一些东西', function(btn, text) {
    alert('你刚刚点击了 ' 
+ btn + ',刚刚输入了 ' + text);
}
);


随便输入几个字,然后点按钮,它会告诉你输入了些什么东西

5.3. 如果你想的话,可以控制得更多
5.3.1. 可以输入多行的输入框

Ext.MessageBox.show({
    title: '多行输入框',
    msg: '你可以输入好几行',
    width:
300,
    buttons: Ext.MessageBox.OKCANCEL,
    multiline: 
true,
    fn: 
function(btn, text) {
        alert('你刚刚点击了 ' 
+ btn + ',刚刚输入了 ' + text);
    }

}
);




其实只需要show,我们就可以构造各种各样的窗口了,title代表标题,msg代表输出的内容,buttons是显示按钮,multiline告诉我们可以输入好几行,最后用fn这个回调函数接受我们想要得到的结果。

5.3.2. 再看一个例子呗
可能让我们对show这个方法的理解更深

Ext.MessageBox.show({
    title:'随便按个按钮',
    msg: '从三个按钮里随便选择一个',
    buttons: Ext.MessageBox.YESNOCANCEL,
    fn: 
function(btn) {
        alert('你刚刚点击了 ' 
+ btn);
    }

}
);




我相信buttons这个参数是一个数组,里边的这个参数绝对了应该显示哪些按钮,Ext.MessageBox给我们提供了一些预先定义好的组合,比如YESNOCANCEL,OKCANCEL,可以直接使用。

5.3.3. 下一个例子是进度条
实际上只需要将progress这个属性设置为true,对话框里就会显示个条条。
Ext.MessageBox.show({
    title: '请等待',
    msg: '读取数据中',
    width:
240,
    progress:
true,
    closable:
false
}
);



看到进度条了吧,不过它可不会自动滚啊滚的,你需要调用Ext.MessageBox.updateProgress让进度条发生变化。

另外多说一句,closable: false会隐藏对话框右上角的小叉叉,这样咱们就不能随便关掉它了。

现在让咱们加上更新进度条的函数,使用timeout定时更新,这样咱们就可以看到效果了。呵呵~效果真不错,这样咱们以后就可以使用进度条了。

var f = function(v){
    
return function(){
        
if(v == 11){
            Ext.MessageBox.hide();
        }
else{
            Ext.MessageBox.updateProgress(v
/10, '正在读取第 ' + v + ' 个,一共10个。');
        }

   }
;
}
;
for(var i = 1; i < 12; i++){
   setTimeout(f(i), i
*1000);
}



5.3.4. 动画效果,跳出来,缩回去
超炫效果,让对话框好像是从一个按钮跳出来的,关闭的时候还会自己缩回去。你可以看到它从小变大,又从大变小,最后不见了。实际上的配置缺非常简单,加一个animEl吧。让我们看看上边那个三个按钮的例子会变成什么样子。

Ext.MessageBox.show({
    title:'随便按个按钮',
    msg: '从三个按钮里随便选择一个',
    buttons: Ext.MessageBox.YESNOCANCEL,
    fn: 
function(btn) {
        alert('你刚刚点击了 ' 
+ btn);
    }
,
    animEl: 'dialog'
}
);

animEl的值是一个字符串,它对应着html里一个元素的id,比如<div id="dialog"></div>。指定好了这个,咱们的对话框才知道根据哪个元素播放展开和关闭的动画呀。

只需要这样,咱们就得到动画效果,嘿嘿,截不到动画效果的图,大家自己去看吧。

以上的例子在examples里都可以找到,不过咱们也提供了一份自己的例子,1.x在lingo-sample/1.1.1/05-01.html。2.0在lingo-sample/2.0/05-01.html。

好消息是,这部分的api没有什么改动。不过表现形式上有些差别,如果像我在例子里写的那样,一次生成N个MessageBox,只能显示最后一个对话框。


不过在1.x里明显有一些数据同步的问题,1.x里的updateProgress甚至可以影响其他对话框的msg,以及可以关闭最后那个对话框。2.0里至少是好的



5.4. 让弹出窗口,显示我们想要的东东,比如表格
2.0需要window来完成这个任务,1.x版的BasicDialog稍后加上。

5.4.1. 2.0的弹出表格哦
稍微说一下window咋用呢?其实看起来跟MessageBox差不多啦,只是可以在里边随便放东西,现在先看个单纯的例子。

var win = new Ext.Window({
    el:'window
-win',
    layout:'fit',
    width:
500,
    height:
300,
    closeAction:'hide',

    items: [
{}],

    buttons: [
{
        text:'按钮'
    }
]
}
);
win.show();


首先要讲明的是,这个window需要一个对应的div呀,就像el对应的'window-win'一样,这个div的id就应该等于'window-win',然后设置宽和高,这些都很明朗。
其次,需要设置的是布局类型,layout:'fit'说明布局会适应整个window的大小,估计改变大小的时候也要做响应的改变。
closeAction:'hide'是一个预设值,简单来说就是你用鼠标点了右上角的叉叉,会执行什么操作,这里就是隐藏啦。问为啥是隐藏?因为,因为预设啦,乖,背下来撒。
items部分,嘿嘿~就是告诉咱们的window里要有什么内容啦。这里放表格,放树形,吼吼。
buttons里设置在底端显示的按钮。我们就为了试一下,弄了一个按钮,但是按了没反应,嘿嘿。
最后调用一下show(),让窗口显示出来。


中间的空白就是items:[{}]的杰作,默认{}会成为一个Ext.Panel,咱们什么都没定义,里边自然什么都没有。当然500*300不会只有这么大,但是为了让图片小一点儿,我把它拖下了,嘿嘿~自动支持的修改大小效果,帅吧?

5.4.2. 向2.0的window里加表格
唉,地方都划出来了,弄个表格放进去就好了呗。

首先弄一个grid,超简单那种。我是直接把第二章的例子给copy了过来,嘿嘿,表格还是那个表格哟。

有了表格,直接扔到window里,然后ok,哈哈~效果如下:



第一,grid不用调用render()了,只要加入了window,在win.show()的时候,会自动渲染里边的组件。
第二,html里,要把grid对应的div写到window的div里面,嵌套关系。
<div id="window-win">
    
<div id="grid"></div>
</div>


第三,如果还不知道怎么把grid放进window里,我给你看下代码。
var win = new Ext.Window({
    el:'window
-win',
    layout:'fit',
    width:
500,
    height:
300,
    closeAction:'hide',

    items: [grid],

    buttons: [
{
        text:'按钮'
    }
]
}
);


看到items:[grid]了吗?就这么简单哟。