# jQuery 套件 DataTables 的測試
ExtJS 有四種資料控件 (Data Controls), 都統一由 Ext.data 套件中的 Store 類別提供資料來源 :
- 下拉式選單 (Combobox)
- 表格 (Grid Panel)
- 樹狀結構 (Tree Panel)
- 圖表 (Chart)
其中 GridPanel 是 ExtJS 最受歡迎的功能之一. ExtJS 的表格是由 Ext.grid.Panel 類別之實體負責呈現, 它繼承自 Ext.panel.Panel 類別 :
Ext.panel.Panel
|__ Ext.panel.Table
|__ Ext.grid.Panel
此類別有三個別名 :
- Ext.ListView
- Ext.grid.GridPanel
- Ext.list.ListView
# http://docs.sencha.com/extjs/4.2.2/#!/api/Ext.grid.Panel
GridPanel 相當於豪華版的 <table> 元素, 因此跟 <table> 一樣主要由兩個部分構成 :
- 欄位標題 columns
- 資料儲存 store
var grid=Ext.create("Ext.grid.Panel", {
columns:columns,
store:store,
renderTo:"grid" //id 為 grid 之 div
});
或者 :
var grid=Ext.create("Ext.grid.Panel", {
columns:columns,
store:store
});
grid.render("grid"); //id 為 grid 之 div
其中, columns 是表格標頭 (欄位) 定義, 是一個具有 header 與 dataIndex 屬性的物件陣列. 表格中的欄位其顯示順序完全由此 columns 中的順序決定. 表格的原始資料可以是近端的二維陣列, 或是遠端的 JSON 或 XML 資料. 但 GridPanel 無法直接取用原始資料, 必須透過 Proxy 轉成 Store 物件才能被 GridPanel 所用. 二維陣列可以用 Ext.data.ArrayStore 來轉成 Store 物件, 利用 fields 屬性來與 columns 欄位相配對.
Ext.grid.Panel 通常從下列來源取得資料 :
- 二維陣列 (近端)
- Ajax JSON (遠端)
- Ajax XML (遠端)
測試範例 1 : http://mybidrobot.allalla.com/extjstest/extjs_grid_1.htm [看原始碼]
<div id="grid"></div>
<script type="text/javascript">
Ext.onReady(function() {
//定義表頭欄位
var columns=[{header:"股票名稱",dataIndex:"name"},
{header:"股票代號",dataIndex:"id"},
{header:"收盤價 (元)",dataIndex:"close"},
{header:"成交量 (張)",dataIndex:"volumn"}
];
//定義原始資料
var data=[["台積電","2330",123.0,25119],
["中華電","2412",96.4,5249],
["中碳","1723",192.5,918],
["創見","2451",108.0,733],
["華擎","3515",118.5,175],
["訊連","5203",97.0,235]
];
//轉成 Store 物件
var store=Ext.create("Ext.data.ArrayStore", {
data:data,
fields:[
{name:"name"},
{name:"id"},
{name:"close"},
{name:"volumn"}]
});
store.load();
//建立 GridPanel
var grid=Ext.create("Ext.grid.Panel",{
title:"台股",
columns:columns,
store:store,
renderTo:"grid",
autoHeight:true,
width:450
});
}); //end of onReady
</script>
注意, 在 store 的 fields 屬性中, 各元素的順序必須與原始資料 data 的排列順序相同, 但不必與 columns 的元素順序相同, 因為它是透過 name 屬性之值與 columns 的 tableIndex 屬性配對.
這裡我們設定了 width 屬性, 將 GridPanel 寬度限制為 450px, 否則預設會占滿整個瀏覽器寬度. 每一個欄位預設會分配到 100px 的寬度, 剩下的就會在右方留下一個空的欄位. 如果要讓個欄位占滿整個表格, 不要留白, 則可以在 GridPanel 中加入 forceFit : true.
預設欄位寬度可以用滑鼠拖曳欄位中間的分隔線加以調整, 如果要指定欄位寬度, 必須在 columns 欄位定義中加入 width 屬性. 如果要禁止調整欄位寬度, 可在 columns 中加入 fixed : true 即可.
預設每一個欄位都可以排序, 當滑鼠移到欄位標題上時, 會出現一個小三角形, 點一下可以選擇排序方式與欄位. 直接點欄位標題則會以該欄資料進行排序 (正向/反向每按一次 toggle). 如果想禁止排序, 可以在 columns 欄位定義中加入 sortable : false, 則點該欄位標題時就不會排序了.
在下列範例 2 中, 我們在第一欄位定義中加入 sortable:false 使其無法排序, 同時也將 GrdiPanel 設為forceFit :
測試範例 2 : http://mybidrobot.allalla.com/extjstest/extjs_grid_2.htm [看原始碼]
Ext.onReady(function() {
//定義表頭欄位
var columns=[{header:"股票名稱",dataIndex:"name",sortable:false},
{header:"股票代號",dataIndex:"id"},
{header:"收盤價 (元)",dataIndex:"close"},
{header:"成交量 (張)",dataIndex:"volumn"}
];
//定義原始資料
var data=[["台積電","2330",123.0,25119],
["中華電","2412",96.4,5249],
["中碳","1723",192.5,918],
["創見","2451",108.0,733],
["華擎","3515",118.5,175],
["訊連","5203",97.0,235]
];
//轉成 Store 物件
var store=Ext.create("Ext.data.ArrayStore", {
data:data,
fields:[
{name:"name"},
{name:"id"},
{name:"close"},
{name:"volumn"}]
});
store.load();
//建立 GridPanel
var grid=Ext.create("Ext.grid.Panel",{
columns:columns,
store:store,
renderTo:"grid",
width:450,
forceFit:true
});
}); //end of onReady
可見 forceFit: true 會讓所有欄位平均分配表格寬度, 而且第一欄位的正向與反向排序也被 sortable : false 給禁掉了, 沒辦法排序, 其他欄位則可.
前台的初始排序事實上可以呼叫 Store 物件的 sort() 方法並傳入欄位名稱與排序方式來達成 :
store.sort("id","desc");
測試範例 2-1 : http://mybidrobot.allalla.com/extjstest/extjs_grid_2_1.htm [看原始碼]
Ext.onReady(function() {
//定義表頭欄位
var columns=[{header:"股票名稱",dataIndex:"name"},
{header:"股票代號",dataIndex:"id"},
{header:"收盤價 (元)",dataIndex:"close"},
{header:"成交量 (張)",dataIndex:"volumn"}
];
//定義原始資料
var data=[["台積電","2330",123.0,25119],
["中華電","2412",96.4,5249],
["中碳","1723",192.5,918],
["創見","2451",108.0,733],
["華擎","3515",118.5,175],
["訊連","5203",97.0,235]
];
//轉成 Store 物件
var store=Ext.create("Ext.data.ArrayStore", {
data:data,
fields:[
{name:"name"},
{name:"id"},
{name:"close"},
{name:"volumn"}]
});
store.load();
store.sort("id","desc");
//建立 GridPanel
var grid=Ext.create("Ext.grid.Panel",{
columns:columns,
store:store,
renderTo:"grid",
width:450,
forceFit:true
});
}); //end of onReady
可見當網頁一載入時, 初始排序就依股票代號倒序排列了. 如果連續呼叫 sort() 的話, 會以最後一次呼叫來排序, 如下列範例 2-2 所示 :
測試範例 2-2 : http://mybidrobot.allalla.com/extjstest/extjs_grid_2_2.htm [看原始碼]
store.sort("id","desc");
store.sort("close","asc"); //最後一個才有作用 (覆蓋前面的)
可見初始載入時是以收盤價升序排列.
所以若要同時排序多個欄位, 不能像上面那樣連續呼叫 sort(), 必須需傳入物件陣列, 以 property 屬性指定要排序之欄位 (依前後順序), 以 direction 指定排序方向 :
store.sort([{property:"close",direction:"desc"},
{property:"id",direction:"asc"}]); //先用 close 降序後, 再用 id 升序
測試範例 2-3 : http://mybidrobot.allalla.com/extjstest/extjs_grid_2_3.htm [看原始碼]
Ext.onReady(function() {
//定義表頭欄位
var columns=[{header:"股票名稱",dataIndex:"name"},
{header:"股票代號",dataIndex:"id"},
{header:"收盤價 (元)",dataIndex:"close"},
{header:"成交量 (張)",dataIndex:"volumn"}
];
//定義原始資料
var data=[["台積電","2330",123.0,25119],
["中華電","2412",96.4,5249],
["中碳","1723",192.5,918],
["創見","2451",108.0,733],
["華擎","3515",108.0,175],
["訊連","5203",108.0,235]
];
//轉成 Store 物件
var store=Ext.create("Ext.data.ArrayStore", {
data:data,
fields:[
{name:"name"},
{name:"id"},
{name:"close"},
{name:"volumn"}]
});
store.load();
store.sort([{property:"close",direction:"desc"},
{property:"id",direction:"asc"}]);
//建立 GridPanel
var grid=Ext.create("Ext.grid.Panel",{
columns:columns,
store:store,
renderTo:"grid",
width:450,
forceFit:true
});
}); //end of onReady
雖說同時排序, 其實是依序排序, 所以會先用 close 降序後, 相同部分再用 id 升序排列, 此處為了觀察方便, 我把 data 中的創見, 華擎, 訊連三家的收盤價全部改成 108 元, 所以以 close 先排序時三者同順序, 這時就由第二個排序欄位 id (股票代號) 來做升序排序.
事實上 Ext.data.ArrayStore 與 Ext.data.Store 類別都有一個 sorters 屬性來設定初始排序欄位, 與呼叫 sorter() 方法效果是一樣的. Store 的 load() 方法也可以用 autoLoad:true 屬性代替, 我們把範例 2-3 改寫為如下範例 2-4 :
測試範例 2-4 : http://mybidrobot.allalla.com/extjstest/extjs_grid_2_4.htm [看原始碼]
Ext.onReady(function() {
//定義表頭欄位
var columns=[{header:"股票名稱",dataIndex:"name"},
{header:"股票代號",dataIndex:"id"},
{header:"收盤價 (元)",dataIndex:"close"},
{header:"成交量 (張)",dataIndex:"volumn"}
];
//定義原始資料
var data=[["台積電","2330",123.0,25119],
["中華電","2412",96.4,5249],
["中碳","1723",192.5,918],
["創見","2451",108.0,733],
["華擎","3515",108.0,175],
["訊連","5203",108.0,235]
];
//轉成 Store 物件
var store=Ext.create("Ext.data.ArrayStore", {
data:data,
fields:[
{name:"name"},
{name:"id"},
{name:"close"},
{name:"volumn"}],
sorters:[{property:"close",direction:"desc"},
{property:"id",direction:"asc"}],
autoLoad:true
});
//建立 GridPanel
var grid=Ext.create("Ext.grid.Panel",{
columns:columns,
store:store,
renderTo:"grid",
width:450,
forceFit:true
});
}); //end of onReady
接下來範例三則是測試固定欄位寬度, 另外既然 GridPanel 繼承自 Panel 類別, 我們也可以加上表格標題 (title) 與外框 (frame) :
測試範例 3 : http://mybidrobot.allalla.com/extjstest/extjs_grid_3.htm [看原始碼]
Ext.onReady(function() {
//定義表頭欄位
var columns=[{header:"股票名稱",dataIndex:"name",width:80,fixed:true},
{header:"股票代號",dataIndex:"id",width:80,fixed:true},
{header:"收盤價 (元)",dataIndex:"close",width:90,fixed:true},
{header:"成交量 (張)",dataIndex:"volumn",width:90,fixed:true}
];
//定義原始資料
var data=[["台積電","2330",123.0,25119],
["中華電","2412",96.4,5249],
["中碳","1723",192.5,918],
["創見","2451",108.0,733],
["華擎","3515",118.5,175],
["訊連","5203",97.0,235]
];
//轉成 Store 物件
var store=Ext.create("Ext.data.ArrayStore", {
data:data,
fields:[
{name:"name"},
{name:"id"},
{name:"close"},
{name:"volumn"}]
});
store.load();
//建立 GridPanel
var grid=Ext.create("Ext.grid.Panel",{
title:"台股",
columns:columns,
store:store,
renderTo:"grid",
frame:true,
width:450
});
}); //end of onReady
可見加入 frame:true 後, 表格外面多了一層外框, 欄位也設定了固定寬度 而且不能調整.
禁止調整欄位寬度除了在 columns 欄位定義中使用 fixed : true 外, 也可以在 GridPanel 中將 enableColumnResize 屬性設為 false. 另外, 表格的各欄位繪製完成後, 預設是可以用滑鼠拖曳改變排列順序的, 如果要禁止, 可以在 GridPanel 中將 enableColumnMove 屬性設為 false, 如下列範例 4 所示 :
測試範例 4 : http://mybidrobot.allalla.com/extjstest/extjs_grid_4.htm [看原始碼]
Ext.onReady(function() {
//定義表頭欄位
var columns=[{header:"股票名稱",dataIndex:"name"},
{header:"股票代號",dataIndex:"id"},
{header:"收盤價 (元)",dataIndex:"close"},
{header:"成交量 (張)",dataIndex:"volumn"}
];
//定義原始資料
var data=[["台積電","2330",123.0,25119],
["中華電","2412",96.4,5249],
["中碳","1723",192.5,918],
["創見","2451",108.0,733],
["華擎","3515",118.5,175],
["訊連","5203",97.0,235]
];
//轉成 Store 物件
var store=Ext.create("Ext.data.ArrayStore", {
data:data,
fields:[
{name:"name"},
{name:"id"},
{name:"close"},
{name:"volumn"}]
});
store.load();
//建立 GridPanel
var grid=Ext.create("Ext.grid.Panel",{
columns:columns,
store:store,
renderTo:"grid",
width:450,
enableColumnMove:false,
enableColumnResize:false
});
}); //end of onReady
可見不但欄位無法移動, 寬度也無法調整了. 欄位寬度設定還有一個 flex 屬性, 這是用來分配剩餘寬度比率的, 亦即, 當扣除全部有設 width 屬性的欄位寬度以及無 width 的預設 100px 後, 剩下的寬度就由 flex 所佔比率來瓜分, 比率就是該欄的 flex 除以全部 flex 總和, 如下面範例 4-1 所示 :
測試範例 4-1 : http://mybidrobot.allalla.com/extjstest/extjs_grid_4_1.htm [看原始碼]
Ext.onReady(function() {
//定義表頭欄位
var columns=[{header:"股票名稱",dataIndex:"name",width:80},
{header:"股票代號",dataIndex:"id",flex:1},
{header:"收盤價 (元)",dataIndex:"close",flex:1},
{header:"成交量 (張)",dataIndex:"volumn"}
];
//定義原始資料
var data=[["台積電","2330",123.0,25119],
["中華電","2412",96.4,5249],
["中碳","1723",192.5,918],
["創見","2451",108.0,733],
["華擎","3515",118.5,175],
["訊連","5203",97.0,235]
];
//轉成 Store 物件
var store=Ext.create("Ext.data.ArrayStore", {
data:data,
fields:[
{name:"name"},
{name:"id"},
{name:"close"},
{name:"volumn"}]
});
store.load();
//建立 GridPanel
var grid=Ext.create("Ext.grid.Panel",{
title:"台股",
columns:columns,
store:store,
renderTo:"grid",
frame:true,
width:450
});
}); //end of onReady
此例中, 第一欄有設 width 為 80px, 第四欄沒有設, 故預設 100px, 剩下 450-80-100=270px 就由中間兩個欄位均分 (各佔 135px), 因為這兩欄的 flex 均為 1.
GridPanel 還有一個功能是, 可在表格中顯示列號, 當表格資料多時, 可以很快地定位某筆資料, 也可以知道現在總共顯示了幾筆資料. 這可以在 columns 欄位定義中, 呼叫 Ext.grid 的 RowNumberer() 方法產生一個列編號物件, 如下列範例 5 所示 :
測試範例 5 : http://mybidrobot.allalla.com/extjstest/extjs_grid_5.htm [看原始碼]
Ext.onReady(function() {
//定義表頭欄位
var columns=[Ext.create("Ext.grid.RowNumberer"),
{header:"股票名稱",dataIndex:"name"},
{header:"股票代號",dataIndex:"id"},
{header:"收盤價 (元)",dataIndex:"close"},
{header:"成交量 (張)",dataIndex:"volumn"}
];
//定義原始資料
var data=[["台積電","2330",123.0,25119],
["中華電","2412",96.4,5249],
["中碳","1723",192.5,918],
["創見","2451",108.0,733],
["華擎","3515",118.5,175],
["訊連","5203",97.0,235]
];
//轉成 Store 物件
var store=Ext.create("Ext.data.ArrayStore", {
data:data,
fields:[
{name:"name"},
{name:"id"},
{name:"close"},
{name:"volumn"}]
});
store.load();
//建立 GridPanel
var grid=Ext.create("Ext.grid.Panel",{
columns:columns,
store:store,
renderTo:"grid",
width:450
});
}); //end of onReady
注意, 此例中的列編號欄的標題預設是空白, 其實 Ext.grid.RowNumberer 有 text 與 width 兩個屬性可以設定, 如果將範例 5 改成如下, 就會出現標題了 :
Ext.create("Ext.grid.RowNumberer",{text:"編號",width:50});
如下列範例 5-0 所示 :
測試範例 5-0 : http://mybidrobot.allalla.com/extjstest/extjs_grid_5_0.htm [看原始碼]
在下面範例 5-1 中, 我們要測試表格的列選取模式. GridPanel 預設是單列選取, 故上面的每個範例都沒辦法按住 Ctrl 或 Shift 鍵用滑鼠做多選動作. 要控制選取模式必須設定 GirdPanel 的 selModel (或用簡寫 sm 亦可), 其值為一個 Ext.selection.RowModel 類別的實體物件.
ExtJS 4 的 selection 套件包含 5 個選取類別 : Model, CellModel, RowModel, CheckboxModel, 以及 TreeModel, 詳見 :
# http://docs.sencha.com/extjs/4.2.2/#!/api/Ext.selection.RowModel
# http://docs.sencha.com/extjs/4.2.2/#!/api/Ext.selection.CheckboxModel
# http://docs.sencha.com/extjs/4.2.2/#!/api/Ext.selection.CellModel
# http://docs.sencha.com/extjs/4.2.2/#!/api/Ext.selection.TreeModel
# http://docs.sencha.com/extjs/4.2.2/#!/api/Ext.selection.Model
這裡要用到的是 RowModel 類別, 其 mode 屬性可用來控制表格的列選取模式, 其值為字串 :
- "SINGLE" : 單選
- "SIMPLE" : 複選, 但只能一個一個選
- "MULTI" : 複選, 可配合 Ctrl 或 Shift 做區域複選
測試範例 5-1 : http://mybidrobot.allalla.com/extjstest/extjs_grid_5_1.htm [看原始碼]
測試範例 5-2 : http://mybidrobot.allalla.com/extjstest/extjs_grid_5_2.htm [看原始碼]
測試範例 5-3 : http://mybidrobot.allalla.com/extjstest/extjs_grid_5_3.htm [看原始碼]
Ext.onReady(function() {
//定義表頭欄位
var columns=[Ext.create("Ext.grid.RowNumberer"),
{header:"股票名稱",dataIndex:"name"},
{header:"股票代號",dataIndex:"id"},
{header:"收盤價 (元)",dataIndex:"close"},
{header:"成交量 (張)",dataIndex:"volumn"}
];
//定義原始資料
var data=[["台積電","2330",123.0,25119],
["中華電","2412",96.4,5249],
["中碳","1723",192.5,918],
["創見","2451",108.0,733],
["華擎","3515",118.5,175],
["訊連","5203",97.0,235]
];
//轉成 Store 物件
var store=Ext.create("Ext.data.ArrayStore", {
data:data,
fields:[
{name:"name"},
{name:"id"},
{name:"close"},
{name:"volumn"}]
});
store.load();
//建立 GridPanel
var grid=Ext.create("Ext.grid.Panel",{
columns:columns,
store:store,
renderTo:"grid",
width:450,
selModel:Ext.create("Ext.selection.RowModel",{mode:"SINGLE"})
});
grid.on("itemclick",function(){
var selected=grid.getSelectionModel().selected;
var sel=[];
for (var i=0; i<selected.getCount(); i++){
var r=selected.get(i);
var msg=r.get("name") + "," + r.get("id") + "," +
r.get("close") + "," + r.get("volumn");
sel.push(msg);
}
Ext.Msg.alert("訊息","您選取的內容:<br>" + sel.join("<br>"));
});
}); //end of onReady
可見 SINGLE 是每次只能選一列, 點另一列時原先選取者會自動取消; 而 SIMPLE 是不會取消, 除非再點選一下已被選取者 (toggle); 而 MULTI 則是可按住 Ctrl 或 Shift 一次選一段區間.
注意, ExtJS 3 時所使用的 rowclick 事件在 4 版時已無作用, 必須使用 itemclick 事件.
除了放置列編號外, 也可以放置核取方塊欄, 這要將 GridPanel 的 selModel 設置為一個 CheckboxModel 物件 (預設選擇模式為 SINGLE), 如下列範例 6 所示 :
測試範例 6 : http://mybidrobot.allalla.com/extjstest/extjs_grid_6.htm [看原始碼]
Ext.onReady(function() {
//定義表頭欄位
var columns=[Ext.create("Ext.grid.RowNumberer"),
{header:"股票名稱",dataIndex:"name"},
{header:"股票代號",dataIndex:"id"},
{header:"收盤價 (元)",dataIndex:"close"},
{header:"成交量 (張)",dataIndex:"volumn"}
];
//定義原始資料
var data=[["台積電","2330",123.0,25119],
["中華電","2412",96.4,5249],
["中碳","1723",192.5,918],
["創見","2451",108.0,733],
["華擎","3515",118.5,175],
["訊連","5203",97.0,235]
];
//轉成 Store 物件
var store=Ext.create("Ext.data.ArrayStore", {
data:data,
fields:[
{name:"name"},
{name:"id"},
{name:"close"},
{name:"volumn"}]
});
store.load();
//建立 GridPanel
var grid=Ext.create("Ext.grid.Panel",{
columns:columns,
store:store,
renderTo:"grid",
width:450,
selModel:Ext.create("Ext.selection.CheckboxModel")
});
}); //end of onReady
表格標頭上的核取方塊是全選/全取消動作, 已經內建不須自行處理. 我們將範例 6 加上 itemclick 事件監聽器, 分別設定 mode 為 SINGLE (預設), SIMPLE, 與 MULTI, 如下列範例 6-1, 6-2, 6-3 所示 :
測試範例 6-1 : http://mybidrobot.allalla.com/extjstest/extjs_grid_6_1.htm [看原始碼]
測試範例 6-2 : http://mybidrobot.allalla.com/extjstest/extjs_grid_6_2.htm [看原始碼]
測試範例 6-3 : http://mybidrobot.allalla.com/extjstest/extjs_grid_6_3.htm [看原始碼]
Ext.onReady(function() {
//定義表頭欄位
var columns=[Ext.create("Ext.grid.RowNumberer"),
{header:"股票名稱",dataIndex:"name"},
{header:"股票代號",dataIndex:"id"},
{header:"收盤價 (元)",dataIndex:"close"},
{header:"成交量 (張)",dataIndex:"volumn"}
];
//定義原始資料
var data=[["台積電","2330",123.0,25119],
["中華電","2412",96.4,5249],
["中碳","1723",192.5,918],
["創見","2451",108.0,733],
["華擎","3515",118.5,175],
["訊連","5203",97.0,235]
];
//轉成 Store 物件
var store=Ext.create("Ext.data.ArrayStore", {
data:data,
fields:[
{name:"name"},
{name:"id"},
{name:"close"},
{name:"volumn"}]
});
store.load();
//建立 GridPanel
var grid=Ext.create("Ext.grid.Panel",{
columns:columns,
store:store,
renderTo:"grid",
width:450,
selModel:Ext.create("Ext.selection.CheckboxModel",{mode:"MULTI"})
});
grid.on("itemclick",function(){
var selected=grid.getSelectionModel().selected;
var sel=[];
for (var i=0; i<selected.getCount(); i++){
var r=selected.get(i);
var msg=r.get("name") + "," + r.get("id") + "," +
r.get("close") + "," + r.get("volumn");
sel.push(msg);
}
Ext.Msg.alert("訊息","您選取的內容:<br>" + sel.join("<br>"));
});
}); //end of onReady
既然我們已可選取表格中的各列, 那麼也可以將選取之列從表格中刪除, 實際也就是從 Store 中移除選取的列, 再更新顯示的 view 即可, 如下列範例 7 所示 :
測試範例 7 : http://mybidrobot.allalla.com/extjstest/extjs_grid_7.htm [看原始碼]
<div id="grid"></div>
<input type="button" id="delete" value="刪除">
Ext.onReady(function() {
//定義表頭欄位
var columns=[Ext.create("Ext.grid.RowNumberer"),
{header:"股票名稱",dataIndex:"name"},
{header:"股票代號",dataIndex:"id"},
{header:"收盤價 (元)",dataIndex:"close"},
{header:"成交量 (張)",dataIndex:"volumn"}
];
//定義原始資料
var data=[["台積電","2330",123.0,25119],
["中華電","2412",96.4,5249],
["中碳","1723",192.5,918],
["創見","2451",108.0,733],
["華擎","3515",118.5,175],
["訊連","5203",97.0,235]
];
//轉成 Store 物件
var store=Ext.create("Ext.data.ArrayStore", {
data:data,
fields:[
{name:"name"},
{name:"id"},
{name:"close"},
{name:"volumn"}]
});
store.load();
//建立 GridPanel
var grid=Ext.create("Ext.grid.Panel",{
columns:columns,
store:store,
renderTo:"grid",
width:450,
selModel:Ext.create("Ext.selection.RowModel",{mode:"SIMPLE"})
});
Ext.get("delete").on("click",function(){
var selected=grid.getSelectionModel().selected;
var sel=[];
for (var i=0; i<selected.getCount(); i++){
var r=selected.get(i);
sel.push(r);
}
store.remove(sel);
grid.view.refresh();
});
}); //end of onReady
當然, CheckboxModel 也可以這麼做, 如範例 8 所示 :
測試範例 8 : http://mybidrobot.allalla.com/extjstest/extjs_grid_8.htm [看原始碼]
<div id="grid"></div>
<input type="button" id="delete" value="刪除">
<script type="text/javascript">
Ext.onReady(function() {
//定義表頭欄位
var columns=[Ext.create("Ext.grid.RowNumberer"),
{header:"股票名稱",dataIndex:"name"},
{header:"股票代號",dataIndex:"id"},
{header:"收盤價 (元)",dataIndex:"close"},
{header:"成交量 (張)",dataIndex:"volumn"}
];
//定義原始資料
var data=[["台積電","2330",123.0,25119],
["中華電","2412",96.4,5249],
["中碳","1723",192.5,918],
["創見","2451",108.0,733],
["華擎","3515",118.5,175],
["訊連","5203",97.0,235]
];
//轉成 Store 物件
var store=Ext.create("Ext.data.ArrayStore", {
data:data,
fields:[
{name:"name"},
{name:"id"},
{name:"close"},
{name:"volumn"}]
});
store.load();
//建立 GridPanel
var grid=Ext.create("Ext.grid.Panel",{
columns:columns,
store:store,
renderTo:"grid",
width:450,
selModel:Ext.create("Ext.selection.CheckboxModel",{mode:"SIMPLE"})
});
Ext.get("delete").on("click",function(){
var selected=grid.getSelectionModel().selected;
var sel=[];
for (var i=0; i<selected.getCount(); i++){
var r=selected.get(i);
sel.push(r);
}
store.remove(sel);
grid.view.refresh();
});
}); //end of onReady
</script>
接下來是此次測試的重點, 也就是分頁功能. ExtJS 4 提供了 Ext.PagingToolbar 類別來處理分頁功能, 前面提到 GridPanel 可以用 tbar 與 bbar 屬性來放置工具列, 我們試著用上面的本機陣列資料來測試 ExtJS 的前台分頁功能, 如下面範例 9 所示 :
測試範例 9 : http://mybidrobot.allalla.com/extjstest/extjs_grid_9.htm [看原始碼]
Ext.QuickTips.init(); //啟動分頁按鈕提示功能
Ext.onReady(function() {
//定義表頭欄位
var columns=[Ext.create("Ext.grid.RowNumberer"),
{header:"股票名稱",dataIndex:"name"},
{header:"股票代號",dataIndex:"id"},
{header:"收盤價 (元)",dataIndex:"close"},
{header:"成交量 (張)",dataIndex:"volumn"}
];
//定義原始資料
var data=[["台積電","2330",123.0,25119],
["中華電","2412",96.4,5249],
["中碳","1723",192.5,918],
["創見","2451",108.0,733],
["華擎","3515",118.5,175],
["訊連","5203",97.0,235]
];
//轉成 Store 物件
var store=Ext.create("Ext.data.ArrayStore", {
data:data,
fields:[
{name:"name"},
{name:"id"},
{name:"close"},
{name:"volumn"}]
});
//建立 GridPanel
var grid=Ext.create("Ext.grid.Panel",{
columns:columns,
store:store,
renderTo:"grid",
width:450,
bbar:Ext.create("Ext.PagingToolbar",{
pageSize:2,
store:store,
displayInfo:true,
displayMsg:"顯示第 {0} 列到第 {1} 列紀錄,共 {2} 列",
emptyMsg:"沒有資料"
})
});
store.load();
}); //end of onReady
可見, 雖然設定了 pageSize=2, 但前台分頁功能並未實現, 而是一次顯示了全部六筆資料. 這說明 ExtJS 的 ArrayStore 是不支援前台分頁的. 如果要實現前台分頁, 必須使用 PagingMemoryProxy 這個擴充功能, 此擴充函式位置在 ExtJS 解壓縮目錄下的 examples\ux\data\PagingMemoryProxy.js, 如果是自備 ExtJS 函式庫, 建議在 extjs 目錄下建一個 ux 目錄, 然後將 PagingMemoryProxy.js 複製到 ux 下. 然後於網頁中匯入此 JS 檔即可, 例如 :
<script type="text/javascript" src="../extjs/ux/PagingMemoryProxy.js"></script>
注意, 分頁工具列上的按鈕提示必須用 Ext.QuickTips.init() 指令予以啟動才會出現.
如下列範例 10 所示 :
測試範例 10 : http://mybidrobot.allalla.com/extjstest/extjs_grid_10.htm [看原始碼]
Ext.onReady(function() {
//定義表頭欄位
var columns=[Ext.create("Ext.grid.RowNumberer"),
{header:"股票名稱",dataIndex:"name"},
{header:"股票代號",dataIndex:"id"},
{header:"收盤價 (元)",dataIndex:"close"},
{header:"成交量 (張)",dataIndex:"volumn"}
];
//定義原始資料
var data=[["台積電","2330",123.0,25119],
["中華電","2412",96.4,5249],
["中碳","1723",192.5,918],
["創見","2451",108.0,733],
["華擎","3515",118.5,175],
["訊連","5203",97.0,235]
];
//轉成 Store 物件
var store=Ext.create("Ext.data.Store", {
pageSize:2,
proxy:{
type:"pagingmemory",
data:data,
reader:{type:"array"}
},
fields:[
{name:"name"},
{name:"id"},
{name:"close"},
{name:"volumn"}
]
});
//建立 GridPanel
var grid=Ext.create("Ext.grid.Panel",{
columns:columns,
store:store,
renderTo:"grid",
width:450,
bbar:Ext.create("Ext.PagingToolbar",{
store:store,
displayInfo:true,
displayMsg:"顯示第 {0} 列到第 {1} 列紀錄,共 {2} 列",
emptyMsg:"沒有資料"
})
});
store.load(); //初始載入 store (必須!)
}); //end of onReady
可見使用 PagingMemoryProxy 就能達成前台分頁功能了. 參考 :
# understanding Ext.Loader.setPath('Ext.ux', '../ux/')
分頁工具列 PagingToolbar 的 xtype 為 pagingtoolbar, 所以也可以直接使用 xtype (ExtJS 4 建議盡量使用 xtype), 如下列範例 10-1 :
測試範例 10-1 : http://mybidrobot.allalla.com/extjstest/extjs_grid_10_1.htm [看原始碼]
Ext.QuickTips.init(); //啟動分頁按鈕提示功能
Ext.onReady(function() {
//定義表頭欄位
var columns=[Ext.create("Ext.grid.RowNumberer"),
{header:"股票名稱",dataIndex:"name"},
{header:"股票代號",dataIndex:"id"},
{header:"收盤價 (元)",dataIndex:"close"},
{header:"成交量 (張)",dataIndex:"volumn"}
];
//定義原始資料
var data=[["台積電","2330",123.0,25119],
["中華電","2412",96.4,5249],
["中碳","1723",192.5,918],
["創見","2451",108.0,733],
["華擎","3515",118.5,175],
["訊連","5203",97.0,235]
];
//轉成 Store 物件
var store=Ext.create("Ext.data.Store", {
pageSize:2,
proxy:{
type:"pagingmemory",
data:data,
reader:{type:"array"}
},
fields:[
{name:"name"},
{name:"id"},
{name:"close"},
{name:"volumn"}
]
});
//建立 GridPanel
var grid=Ext.create("Ext.grid.Panel",{
columns:columns,
store:store,
renderTo:"grid",
width:520,
bbar:{
xtype:"pagingtoolbar",
store:store,
displayInfo:true,
displayMsg:"顯示第 {0} 列到第 {1} 列紀錄,共 {2} 列",
emptyMsg:"沒有資料"
}
});
store.load();
}); //end of onReady
表格分頁工具列除了可用 bbar 屬性設定外, 還可以使用 dockerItems 屬性, 使用方式幾乎一樣, 只是必須另外指定 dock 屬性, 而且其值為一個陣列, 如下列範例 10-2 所示 :
測試範例 10-2 : http://mybidrobot.allalla.com/extjstest/extjs_grid_10_2.htm [看原始碼]
Ext.QuickTips.init(); //啟動分頁按鈕提示功能
Ext.onReady(function() {
//定義表頭欄位
var columns=[Ext.create("Ext.grid.RowNumberer"),
{header:"股票名稱",dataIndex:"name"},
{header:"股票代號",dataIndex:"id"},
{header:"收盤價 (元)",dataIndex:"close"},
{header:"成交量 (張)",dataIndex:"volumn"}
];
//定義原始資料
var data=[["台積電","2330",123.0,25119],
["中華電","2412",96.4,5249],
["中碳","1723",192.5,918],
["創見","2451",108.0,733],
["華擎","3515",118.5,175],
["訊連","5203",97.0,235]
];
//轉成 Store 物件
var store=Ext.create("Ext.data.Store", {
pageSize:2,
proxy:{
type:"pagingmemory",
data:data,
reader:{type:"array"}
},
fields:[
{name:"name"},
{name:"id"},
{name:"close"},
{name:"volumn"}
]
});
//建立 GridPanel
var grid=Ext.create("Ext.grid.Panel",{
columns:columns,
store:store,
renderTo:"grid",
width:520,
dockedItems:[{
xtype:"pagingtoolbar",
store:store,
dock:"bottom",
displayInfo:true,
displayMsg:"顯示第 {0} 列到第 {1} 列紀錄,共 {2} 列",
emptyMsg:"沒有資料"
}]
});
store.load();
}); //end of onReady
此例中的 ExtJS 來源改用 Sencha 公司的 CDN 供檔, 注意, Sencha 已不再提供 4.2.1 的 CDN, 最高只到 4.2.0 版. CDN 提供了完整的 ExtJS 資源, 包括前端記憶體分頁程式 PagingMemoryProxy.js, 位置在 /examples/ux/data 下 :
<link rel="stylesheet" href="http://cdn.sencha.com/ext-4.2.0-gpl/resources/css/ext-all.css">
<script type="text/javascript" src="http://cdn.sencha.com/ext-4.2.0-gpl/ext-all.js"></script>
<script type="text/javascript" src="http://cdn.sencha.com/ext-4.2.0-gpl/examples/ux/data/PagingMemoryProxy.js"></script>
<script type="text/javascript" src="http://cdn.sencha.com/ext-4.2.0-gpl/locale/ext-lang-zh_TW.js"></script>
上面所有的範例, 資料來源都是近端的陣列資料, 接下來我們要測試遠端資料, 亦即利用 Ajax 非同步技術取得後端資料, 最常用的是 JSON 格式的資料, PHP 在此方面有 json_encode() 函式支援, 非常方便.
此處參考 "jQuery 套件 DataTables 的測試" 這篇舊作的範例 8 作法來稍作修改, 從 MySQL 資料庫的 stocks_list 資料表取的台股上市公司名稱與代號. 資料表 stocks_list 的製作方法詳見 "jQuery UI 的自動完成器 autocomplete 測試" 中的範例 4 說明.
首先來看產生 JSON 檔的 PHP 程式 :
<?php
header('Content-Type: text/html;charset=UTF-8');
$host="abc.xyz.com";
$username="test";
$password="123";
$database="testdb";
$conn=mysql_connect($host, $username, $password); //建立連線
mysql_query("SET NAMES 'utf8'"); //設定查詢所用之字元集為 utf-8
mysql_select_db($database, $conn); //開啟資料庫
$SQL="SELECT COUNT(*) FROM `stocks_list`";
$RS=mysql_query($SQL, $conn);
list($total)=mysql_fetch_row($RS); //紀錄總筆數
$SQL="SELECT * FROM `stocks_list`";
$result=mysql_query($SQL, $conn); //執行 SQL 指令
$stock=array();
for ($i=0; $i<mysql_numrows($result); $i++) { //走訪紀錄集 (列)
$row=mysql_fetch_array($result); //取得列陣列
$stock_name=$row["stock_name"];
$stock_id=$row["stock_id"];
$stock[$i]=array("stock_name" => $stock_name,
"stock_id" => $stock_id);
} //end of for
$arr=array("totalProperty" => $total, "root" => $stock);
echo json_encode($arr); //將陣列轉成 JSON 資料格式傳回
?>
此程式會輸出如下 ExtJS 表格所需要 JSON 檔 :
{"totalProperty":"865","root":[{"stock_name":"\u5bcc\u90a6","stock_id":"0015"},
...., {"stock_name":"\u65fa\u65fa\u4fdd","stock_id":"2816"}]}
測試範例 11 : http://mybidrobot.allalla.com/extjstest/extjs_grid_11.htm [看原始碼]
Ext.onReady(function() {
//定義表頭欄位
var columns=[Ext.create("Ext.grid.RowNumberer",{text:"編號",width:50}),
{header:"股票名稱",dataIndex:"stock_name"},
{header:"股票代號",dataIndex:"stock_id"}
];
//轉成 Store 物件
var store=Ext.create("Ext.data.Store", {
pageSize:20,
proxy:{
type:"ajax",
url:"get_stocks_list_all.php",
reader:{
type:"json",
totalProperty:"totalProperty",
root:"root",
idProperty:"stock_id"
}
},
fields:[
{name:"stock_name"},
{name:"stock_id"}
]
});
//建立 GridPanel
var grid=Ext.create("Ext.grid.Panel",{
columns:columns,
store:store,
renderTo:"grid",
width:450,
forceFit:true,
bbar:Ext.create("Ext.PagingToolbar",{
store:store,
displayInfo:true,
displayMsg:"顯示第 {0} 列到第 {1} 列紀錄,共 {2} 列",
emptyMsg:"沒有資料"
})
});
store.load({params:{start:0,limit:20}}); //傳送起始參數
}); //end of onReady
可見即使設定了 Store 的 pageSize=20, 但由於 PHP 總是輸出全部 865 筆資料, 所以表格的分頁功能也不會有效果, 雖然分頁工具列顯示分成 44 頁, 每頁 20 列, 事實上第一次載入時卻一次顯示全部資料, 按下一頁會顯示 21~865 列, 再按顯示 41~865 頁 ... 這不是我們預期的結果. 事實上, ExtJS 4 的 PagingToolBar 分頁功能會向後台傳送 page (頁次), start (起始列索引), 以及 limit (讀取列數) 三個參數, 後台的 PHP 程式必需利用此三參數輸出指定頁之資料, 不應該一次輸出全部資料, 要不然不管 Store 的 pageSize 設多少都不會如預期的只顯示指定頁之內容.
其次, 發現從遠端擷取時, 分頁工具列卻沒有完全中文化, 這是因為我沒有將中文化檔案 ext-lang-zh_TW.js 上傳到伺服器之故, 而 PagingToolbar 中我們只設定了 displayMsg 與 emptyMsg 兩個屬性而已, 所以前面的 "第~頁,共~頁" 就變成預設的英文了. 欲完全中文化, 可將 ExtJS 4.2 解壓縮 locale 目錄下的 ext-lang-zh_TW.js 上傳到伺服器, 或者加入下列屬性 :
beforePageText:"第", //取代 "page"
afterPageText:"頁, 共{0}頁", //取代 of~, 其中 {0} 為總頁數
正確的 PHP 後台分頁程式如下, 先讀取 ExtJS 4 的 PagingToolBar 傳送的 start 與 limit 兩個參數, 再用 MySQL 的 LIMIT start,limit 語法讀取資料表中的指定頁次資料 :
<?php
header('Content-Type: text/html;charset=UTF-8');
$host="abc.xyz.com";
$username="test";
$password="123";
$database="testdb";
$conn=mysql_connect($host, $username, $password); //建立連線
mysql_query("SET NAMES 'utf8'"); //設定查詢所用之字元集為 utf-8
mysql_select_db($database, $conn); //開啟資料庫
$start=$_GET['start']; //擷取 ExtJS 傳出參數 'start'
$limit=$_GET['limit']; //擷取 ExtJS 傳出參數 'limit'
$SQL="SELECT COUNT(*) FROM `stocks_list`";
$RS=mysql_query($SQL, $conn);
list($total)=mysql_fetch_row($RS); //紀錄總筆數
$SQL="SELECT * FROM `stocks_list` LIMIT ".$start.",".$limit;
$result=mysql_query($SQL, $conn); //執行 SQL 指令
$stock=array();
for ($i=0; $i<mysql_numrows($result); $i++) { //走訪紀錄集 (列)
$row=mysql_fetch_array($result); //取得列陣列
$stock_name=$row["stock_name"];
$stock_id="<a href='http://tw.stock.yahoo.com/q/q?s=".
$row["stock_id"]."' target='_blank'>".
$row["stock_id"]."</a>";
$stock[$i]=array("stock_name" => $stock_name,
"stock_id" => $stock_id);
} //end of for
$arr=array("totalProperty" => $total, "root" => $stock);
echo json_encode($arr); //將陣列轉成 JSON 資料格式傳回
?>
測試範例 12 : http://mybidrobot.allalla.com/extjstest/extjs_grid_12.htm [看原始碼]
Ext.QuickTips.init(); //啟動分頁按鈕提示
Ext.onReady(function() {
//定義表頭欄位
var columns=[Ext.create("Ext.grid.RowNumberer",{text:"編號",width:50}),
{header:"股票名稱",dataIndex:"stock_name"},
{header:"股票代號",dataIndex:"stock_id"}
];
//轉成 Store 物件
var codebase="http://mybidrobot.allalla.com/extjstest/";
var store=Ext.create("Ext.data.Store", {
pageSize:20,
proxy:{
type:"ajax",
url:"get_stocks_list_page.php",
reader:{
type:"json",
totalProperty:"totalProperty",
root:"root",
idProperty:"stock_id"
}
},
fields:[
{name:"stock_name"},
{name:"stock_id"}
]
});
//建立 GridPanel
var grid=Ext.create("Ext.grid.Panel",{
columns:columns,
store:store,
renderTo:"grid",
width:520,
forceFit:true,
limitParam:undefined,
bbar:Ext.create("Ext.PagingToolbar",{
store:store,
displayInfo:true
beforePageText:"第",
afterPageText:"頁, 共{0}頁",
displayMsg:"顯示第 {0} 列到第 {1} 列紀錄, 共 {2} 列",
emptyMsg:"沒有資料"
})
});
store.load({params:{start:0,limit:20}}); //傳送起始參數
}); //end of onReady
在此例中, 我們在後端 PHP 程式輸出 stock_id 時做了些改變, 加上 Yahoo 的超連結. 其次, 我們既匯入了中文化檔案 ext-lang-zh_TW.js, 也設置了 PagingToolbar 中文化屬性, 這時在後面的分頁工具列設定會蓋過前面匯入的中文化檔案, 如果刪除 PagingToolbar 的中文化設定, 則會採用中文化檔案.
注意, 使用後端分頁後, 若按一直下一頁, 則紀錄編號會顯示連續編號; 但若按了欄位標題進行過前台排序後, 再按下一頁就不會再顯示連續編號了, 固定顯示 1~20.
在上面的程式中, 我們在最後一行呼叫 store 的 load() 方法將遠端傳回之資料載入 Store 中, 並向伺服器傳送兩個參數 start 與 limit 之初始值, 這可以在 Store 中用 autoLoad 屬性取代, 並傳入 true 或含有 start 與 limit 屬性的物件 (即使傳入空物件 {}, 預設也是送出 start=0, limit=pageSize), 這與呼叫 load() 方法作用是一樣的, 如下列範例 12-1 所示 :
測試範例 12-1 : http://mybidrobot.allalla.com/extjstest/extjs_grid_12_1.htm [看原始碼]
Ext.onReady(function() {
//定義表頭欄位
var columns=[Ext.create("Ext.grid.RowNumberer"),
{header:"股票名稱",dataIndex:"stock_name"},
{header:"股票代號",dataIndex:"stock_id"}
];
//轉成 Store 物件
var codebase="http://mybidrobot.allalla.com/extjstest/";
var store=Ext.create("Ext.data.Store", {
autoLoad:{start:0,limit:20}, //改用自動載入屬性指定參數初始值
pageSize:20,
proxy:{
type:"ajax",
url:"get_stocks_list_page.php",
reader:{
type:"json",
totalProperty:"totalProperty",
root:"root",
idProperty:"stock_id"
}
},
fields:[
{name:"stock_name"},
{name:"stock_id"}
]
});
//建立 GridPanel
var grid=Ext.create("Ext.grid.Panel",{
columns:columns,
store:store,
renderTo:"grid",
width:520,
forceFit:true,
limitParam:undefined,
bbar:Ext.create("Ext.PagingToolbar",{
store:store,
displayInfo:true
})
});
}); //end of onReady
可見中文化檔案的顯示文字較短. 事實上 ext-lang-zh_TW.js 對分頁工具列的處理如下 :
Ext.define("Ext.locale.zh_TW.toolbar.Paging", {
override: "Ext.PagingToolbar",
beforePageText: "第",
afterPageText: "頁,共{0}頁",
firstText: "第一頁",
prevText: "上一頁",
nextText: "下一頁",
lastText: "最後頁",
refreshText: "重新整理",
displayMsg: "顯示{0} - {1}筆,共{2}筆",
emptyMsg: '沒有任何資料'
});
另外要特別注意, pageSize 要設在 Store 中, 不是設在 PagingToolbar 中 (可設也可不設), 如果只設於 PagingToolbar 中, 分頁筆數只有初始載入時有效, 按下一頁時每頁筆數將會變成預設 25 筆, 如下列範例 12-2 所示 :
測試範例 12-2 : http://mybidrobot.allalla.com/extjstest/extjs_grid_12_2.htm [看原始碼]
//定義表頭欄位
var columns=[Ext.create("Ext.grid.RowNumberer",{text:"編號",width:50}),
{header:"股票名稱",dataIndex:"stock_name"},
{header:"股票代號",dataIndex:"stock_id"}
];
//轉成 Store 物件
var codebase="http://mybidrobot.allalla.com/extjstest/";
var store=Ext.create("Ext.data.Store", {
proxy:{
type:"ajax",
url:"get_stocks_list_page.php",
reader:{
type:"json",
totalProperty:"totalProperty",
root:"root",
idProperty:"stock_id"
}
},
fields:[
{name:"stock_name"},
{name:"stock_id"}
]
});
//建立 GridPanel
var grid=Ext.create("Ext.grid.Panel",{
columns:columns,
store:store,
renderTo:"grid",
width:520,
forceFit:true,
limitParam:undefined,
bbar:Ext.create("Ext.PagingToolbar",{
pageSize:20, //此設定沒有分頁作用 (要在 Store 中設定)
store:store,
displayInfo:true
})
});
store.load({params:{start:0,limit:20}});
}); //end of onReady
此例只在 PagingToolbar 設定 pageSize 屬性, 結果只有網頁初始化時正確顯示 20 筆, 按下一頁卻顯示 26~50 筆, 而非預期的 21~40 筆. 原因就是當 Store 中沒有設定 pageSize 屬性時, ExtJS 預設就是 limit=25, 即每頁 25 筆. 因此要正確達成後台分頁功能有兩個條件, 一是前台必須在 Store 設定 pageSize, 二是後台必須用 SQL 輸出指定頁數之資料.
分頁工具列除了放在表格下方外, 也可以放在表格上方, 或者上下都放 (tbar 與 bbar 屬性均設定), 上下兩個分頁工具列會連動, 如下列範例 12-3 所示 :
測試範例 12-3 : http://mybidrobot.allalla.com/extjstest/extjs_grid_12_3.htm [看原始碼]
Ext.onReady(function() {
//定義表頭欄位
var columns=[Ext.create("Ext.grid.RowNumberer",{text:"編號",width:50}),
{header:"股票名稱",dataIndex:"stock_name"},
{header:"股票代號",dataIndex:"stock_id"}
];
//轉成 Store 物件
var codebase="http://mybidrobot.allalla.com/extjstest/";
var store=Ext.create("Ext.data.Store", {
pageSize:20,
proxy:{
type:"ajax",
url:"get_stocks_list_page.php",
reader:{
type:"json",
totalProperty:"totalProperty",
root:"root",
idProperty:"stock_id"
}
},
fields:[
{name:"stock_name"},
{name:"stock_id"}
]
});
//建立 GridPanel
var grid=Ext.create("Ext.grid.Panel",{
columns:columns,
store:store,
renderTo:"grid",
width:520,
forceFit:true,
limitParam:undefined,
tbar:Ext.create("Ext.PagingToolbar",{
store:store,
displayInfo:true
}),
bbar:Ext.create("Ext.PagingToolbar",{
store:store,
displayInfo:true
})
});
store.load({params:{start:0,limit:20}});
}); //end of onReady
PagingToolbar 類別還有一個屬性 plugins 可以指定擴充組件, 這裡要來測試一下 ProgressBarPager 這個擴充元件, 此元件會在分頁工具列右側放置一個進度條, 並把 displayInfo 訊息顯示在上面. 這個 Ext.ux.ProgressBarPager 類別放在 ExtJS 4 解壓縮目錄 examples 下面, 我將其上傳到伺服器 extjs/ux/ 目錄下, 然後再匯入此 js 檔 :
<script type="text/javascript" src="../extjs/ux/ProgressBarPager.js"></script>
如下面範例 12-4 所示 :
測試範例 12-4 : http://mybidrobot.allalla.com/extjstest/extjs_grid_12_4.htm [看原始碼]
Ext.onReady(function() {
//定義表頭欄位
var columns=[Ext.create("Ext.grid.RowNumberer",{text:"編號",width:50}),
{header:"股票名稱",dataIndex:"stock_name"},
{header:"股票代號",dataIndex:"stock_id"}
];
//轉成 Store 物件
var store=Ext.create("Ext.data.Store", {
autoLoad:{start:0,limit:20},
pageSize:20,
proxy:{
type:"ajax",
url:"get_stocks_list_page.php",
reader:{
type:"json",
totalProperty:"totalProperty",
root:"root",
idProperty:"stock_id"
}
},
fields:[
{name:"stock_name"},
{name:"stock_id"}
]
});
//建立 GridPanel
var grid=Ext.create("Ext.grid.Panel",{
columns:columns,
store:store,
renderTo:"grid",
width:520,
forceFit:true,
bbar:Ext.create("Ext.PagingToolbar",{
store:store,
displayInfo:true,
plugins:Ext.create("Ext.ux.ProgressBarPager")
})
});
}); //end of onReady
動畫效果看起來還不錯.
最後我們來看一下後端排序. 前面我們已經利用 Store 類別的 sorters 屬性或 sort() 方法測過前端排序功能, 不論資料為本地陣列或後端所提供, GridView 所呈現的順序都是資料載入Store 後利用 sorters 屬性或 sort() 方法排序的結果. 亦即, 若資料由後端提供, 那麼後端程式可能已經用 SQL 的 ORDER BY 語法先對資料表擷取出來的紀錄排序, 傳送到前端後 Store 又會再排序一次, 這樣很複雜. 這裡所謂的後端排序是要禁止前端排序, 完全由後端來排序, 但可以由前端表格傳送排序參數給後端以控制排序欄位與方向.
GridPanel 的後端排序是透過將 Store 類別的 remoteSort 屬性設為 true 達成的 (預設為 false), 這樣會關閉前端排序功能 (按欄位標題無效), 開啟後端排序, 如下列範例 12-5 所示 :
測試範例 12-5 : http://mybidrobot.allalla.com/extjstest/extjs_grid_12_5.htm [看原始碼]
Ext.onReady(function() {
//定義表頭欄位
var columns=[Ext.create("Ext.grid.RowNumberer",{text:"編號",width:50}),
{header:"股票名稱",dataIndex:"stock_name"},
{header:"股票代號",dataIndex:"stock_id"}
];
//轉成 Store 物件
var store=Ext.create("Ext.data.Store", {
autoLoad:{start:0,limit:20},
pageSize:20,
proxy:{
type:"ajax",
url:"get_stocks_list_page.php",
reader:{
type:"json",
totalProperty:"totalProperty",
root:"root",
idProperty:"stock_id"
}
},
fields:[
{name:"stock_name"},
{name:"stock_id"}
],
remoteSort:true
});
//建立 GridPanel
store.sort([{property:"close",direction:"desc"}]);
var grid=Ext.create("Ext.grid.Panel",{
columns:columns,
store:store,
renderTo:"grid",
width:520,
forceFit:true,
remoteSort:true,
bbar:Ext.create("Ext.PagingToolbar",{
store:store,
displayInfo:true
})
});
}); //end of onReady
可見只要啟動了 remoteSort, 就會關閉前端排序功能了. 但光是將 remoteSort 設為 true 還無法達成後端排序, 所謂後端排序是指利用後端伺服器執行 SQL 的 ORDER BY 指令來排序. 上面範例 12-5 使用的後端程式為 get_stocks_list_page.php, 其 SQL 指令並未包含 ORDER BY, 而且我們也沒有向後端傳送排序參數, 就算我們修改 get_stocks_list_page.php 程式, 加入 ORDER BY 語法, 那也只是固定式的後端排序, 不是前端可控制的後端排序 (改排序欄位或方向時需修改後端程式).
但是我們要如何向後端傳送排序參數呢? 當使用 remoteSort 啟動後端排序時, ExtJS 的 Store/Proxy 會以 GET 方式向伺服器送出 page, start, limit 三個參數, 但是若要從前端控制後端的排序, 還必須送出 sort 與 dir 這兩個排序參數. 我在 "ExtJS 開發之練" 這本書的 P373 看到 store.load({params:{start:0,limit:5}}) 的用法, 亦即, 只要在 params 中添加 sort 與 dir, 應該就可以傳遞排序參數了. 這 load() 也可以在 Store 的 autoLoad 屬性中以 params 參數設定 :
autoLoad:{
params:{start:0,
limit:20,
sort:"stock_id",
dir:"desc"}
}
而在後端必須修改 PHP 程式, 擷取 sort, dir, start, limit 這四個參數, 製作後端排序用之 SQL 指令, PHP 程式 get_stocks_list_sort.php 如下 :
<?php
header('Content-Type: text/html;charset=UTF-8');
$host="abc.xyz.com";
$username="test";
$password="123";
$database="testdb";
$conn=mysql_connect($host, $username, $password); //建立連線
mysql_query("SET NAMES 'utf8'"); //設定查詢所用之字元集為 utf-8
mysql_select_db($database, $conn); //開啟資料庫
$start=$_GET['start']; //擷取 ExtJS 傳出參數 'start'
$limit=$_GET['limit']; //擷取 ExtJS 傳出參數 'limit'
$sort=$_GET['sort']; //擷取 ExtJS 傳出參數 'sort'
$dir=$_GET['dir']; //擷取 ExtJS 傳出參數 'dir'
$SQL="SELECT COUNT(*) FROM `stocks_list`";
$RS=mysql_query($SQL, $conn);
list($total)=mysql_fetch_row($RS); //紀錄總筆數
$SQL="SELECT * FROM `stocks_list` ORDER BY ".$sort." ".$dir." ".
"LIMIT ".$start.",".$limit;
$result=mysql_query($SQL, $conn); //執行 SQL 指令
$stock=array();
for ($i=0; $i<mysql_numrows($result); $i++) { //走訪紀錄集 (列)
$row=mysql_fetch_array($result); //取得列陣列
$stock_name=$row["stock_name"];
$stock_id="<a href='http://tw.stock.yahoo.com/q/q?s=".
$row["stock_id"]."' target='_blank'>".
$row["stock_id"]."</a>";
$stock[$i]=array("stock_name" => $stock_name,
"stock_id" => $stock_id);
} //end of for
$arr=array("totalProperty" => $total, "root" => $stock);
echo json_encode($arr); //將陣列轉成 JSON 資料格式傳回
?>
# PHP isset() vs empty() vs is_null()
測試範例 12-6 : http://mybidrobot.allalla.com/extjstest/extjs_grid_12_6.htm [看原始碼]
Ext.onReady(function() {
//定義表頭欄位
var columns=[Ext.create("Ext.grid.RowNumberer",{text:"編號",width:50}),
{header:"股票名稱",dataIndex:"stock_name"},
{header:"股票代號",dataIndex:"stock_id"}
];
//轉成 Store 物件
var store=Ext.create("Ext.data.Store", {
autoLoad:{
params:{start:0,
limit:20,
sort:"stock_id",
dir:"desc"}
},
pageSize:20,
proxy:{
type:"ajax",
url:"get_stocks_list_sort.php",
reader:{
type:"json",
totalProperty:"totalProperty",
root:"root",
idProperty:"stock_id"
}
},
fields:[
{name:"stock_name"},
{name:"stock_id"}
],
remoteSort:true
});
//建立 GridPanel
var grid=Ext.create("Ext.grid.Panel",{
columns:columns,
store:store,
renderTo:"grid",
width:520,pageSize:20,
forceFit:true,
bbar:Ext.create("Ext.PagingToolbar",{
store:store,
displayInfo:true
})
});
}); //end of onReady
但是結果卻 NG! 很奇怪的是, 按下一頁時, 分頁工具列顯示正常, 但 GridView 卻不會換頁, 永遠顯示以 stock_id 倒序的第一頁, WHY? 用 Chrome 的 "工具/Javascript 工具台" (Cntl+Shift+J) 觀察 Network 部分, 發現 Store 的 Proxy 向伺服器發出的 QueryString 中, 初始載入時有正常發出 page, start, limit, sort, dir 四個參數, 但當按下一頁時, 卻只發出 page, start, limit 三個, sort 與 dir 不見了 :
初始載入時
按下一頁時
remoteSort:true,
sorters:[{property:"stock_id",direction:"DESC"}]
Store 的 Proxy 會將 sorters 內的物件陣列以字串形式全部放在 sort 參數中送出 , 因此我們的後端程式必須對 sort 參數進行處理, 才能取出其中的排序參數. 處理的方式是先用 trim() 函式去除左右的陣列中括號, 剩下的 JSON 字串再用 json_decode() 函式轉成物件, 即可取出其中的屬性了. 修改後的 PHP 程式為 get_stocks_list_sort_new.php 如下 :
<?php
header('Content-Type: text/html;charset=UTF-8');
$host="abc.xyz.com";
$username="test";
$password="123";
$database="testdb";
$conn=mysql_connect($host, $username, $password); //建立連線
mysql_query("SET NAMES 'utf8'"); //設定查詢所用之字元集為 utf-8
mysql_select_db($database, $conn); //開啟資料庫
$start=$_GET['start']; //擷取 ExtJS 傳出參數 'start'
$limit=$_GET['limit']; //擷取 ExtJS 傳出參數 'limit'
$sort=$_GET['sort']; //擷取 ExtJS 傳出參數 'sort'
$sort=trim($sort,"["); //去除左括號
$sort=trim($sort,"]"); //去除右括號
$obj=json_decode($sort); //轉成物件
$property=$obj->property; //取出排序欄位
$dir=$obj->direction; //取出排序方向
$SQL="SELECT COUNT(*) FROM `stocks_list`";
$RS=mysql_query($SQL, $conn);
list($total)=mysql_fetch_row($RS); //紀錄總筆數
$SQL="SELECT * FROM `stocks_list` ORDER BY ".$property." ".$dir." ".
"LIMIT ".$start.",".$limit;
$result=mysql_query($SQL, $conn); //執行 SQL 指令
$stock=array();
for ($i=0; $i<mysql_numrows($result); $i++) { //走訪紀錄集 (列)
$row=mysql_fetch_array($result); //取得列陣列
$stock_name=$row["stock_name"];
$stock_id="<a href='http://tw.stock.yahoo.com/q/q?s=".
$row["stock_id"]."' target='_blank'>".
$row["stock_id"]."</a>";
$stock[$i]=array("stock_name" => $stock_name,
"stock_id" => $stock_id);
} //end of for
$arr=array("totalProperty" => $total, "root" => $stock);
echo json_encode($arr); //將陣列轉成 JSON 資料格式傳回
?>
測試範例 12-7 : http://mybidrobot.allalla.com/extjstest/extjs_grid_12_7.htm [看原始碼]
Ext.onReady(function() {
//定義表頭欄位
var columns=[Ext.create("Ext.grid.RowNumberer",{text:"編號",width:50}),
{header:"股票名稱",dataIndex:"stock_name"},
{header:"股票代號",dataIndex:"stock_id"}
];
//轉成 Store 物件
var store=Ext.create("Ext.data.Store", {
autoLoad:{start:0,limit:20},
pageSize:20,
proxy:{
type:"ajax",
url:"get_stocks_list_sort_new.php",
reader:{
type:"json",
totalProperty:"totalProperty",
root:"root",
idProperty:"stock_id"
}
},
fields:[
{name:"stock_name"},
{name:"stock_id"}
],
remoteSort:true,
sorters:[{property:"stock_id",direction:"DESC"}]
});
//建立 GridPanel
var grid=Ext.create("Ext.grid.Panel",{
columns:columns,
store:store,
renderTo:"grid",
width:520,pageSize:20,
forceFit:true,
bbar:Ext.create("Ext.PagingToolbar",{
store:store,
displayInfo:true
})
});
}); //end of onReady
初始載入時
按下一頁時
按欄位標題 "股票名稱"
從 Chrome 觀察 Network 部分可知, 不論初始載入還是換頁, 或者按欄位標題, Store 都會送出 sorters 屬性值, 不會像範例 12-6 那樣無法換頁. 可見經過如此處理, 就能利用前端的 sorters 屬性設定控制後端的排序了, 亦即, 如果要改排序欄位或方向, 只要修改前端網頁即可, 不須修改後端程式. 此例僅控制一個欄位, 若要同時控制多欄排序, 則 sorters 就要添加多個排序參數, 而後端程式也必須隨同修改以處理所傳送的物件陣列, 擷取多組排序參數.
其次, 上面最後一張圖也顯示, 使用 sorters 屬性的好處是, 它不會因為 remoteSort 設為 true 而關閉前端排序, 按欄位標題時仍會傳送該欄位的排序字串, 也就是說, 不須修改網頁中的 sorters 屬性, 只要按欄位標題就能改變排序參數.
參考資料 :
# PHP 讓 json_encode() 指定回傳格式
# extjs4.0 分页问题 刷新后都是正确的 但是点击下一页后 limit就变成25了
# limit in Store requests always 25
# extjs4.0分页时数据列表中总是显示所有的数据
沒有留言 :
張貼留言