只為了樹也要學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]了嗎?就這么簡單喲。