FineUIPro控件庫
FineUIPro是一套基於jQuery的專業ASP.NET控件庫,始於2008年的開源版FineUI控件庫。
當年為了提升項目的開發效率,降低代碼復雜度,減少對CSS和JavaScript的依賴,我們提出了"No JavaScript, No CSS, No UpdatePanel,No ViewState,No WebServices"的口號,現在看起來仍然激動人心。
首先,JavaScript靈活性與復雜性使得大型項目的開發備受挑戰,FineUIPro嘗試使用服務器端的強類型語言(C#,VB.NET)來代替大部分的JavaScript實現,不僅可以利用IDE的強大功能(智能提示,代碼重構),而且強類型語言的編譯時錯誤檢查也是一個加分項。
其次,FineUIPro提供統一的控件集合和頁面主題,使得我們無需在代碼中自定義CSS樣式,不僅減少編碼和調試CSS的工作量,而且能夠保持整個項目中頁面風格的統一和美觀。
最后,FineUIPro內置了AJAX的交互支持,使得我們無需寫一行JavaScript代碼,就能把整個頁面的回發變為AJAX過程。另外,FineUIPro也內置了IFrame支持,有助於在頁面層級對代碼進行解耦合。
那么,FineUIPro是如何工作的呢?FineUIPro的控件使用和原生的ASP.NET控件有哪些異同點?FineUIPro的AJAX交互過程又是什么樣子的呢?
為了回答這些問題,我們將分別使用FineUIPro和ASP.NET控件來實現一個服務器端分頁的表格頁面。
ASP.NET的表格控件
首先來看下ASP.NET的原生GridView控件定義:
<asp:GridView ID="Grid1" Title="表格" Width="800px" DataKeyNames="Id,Name" ShowBorder="true"
runat="server" EnableCheckBoxSelect="True" AutoGenerateColumns="False">
<Columns>
<asp:BoundField DataField="Name" DataFormatString="{0}" HeaderText="姓名" />
<asp:TemplateField HeaderText="性別">
<ItemTemplate>
<asp:Label ID="Label2" runat="server" Text='<%# GetGender(Eval("Gender")) %>'></asp:Label>
</ItemTemplate>
</asp:TemplateField>
<asp:BoundField DataField="EntranceYear" HeaderText="入學年份" />
<asp:CheckBoxField DataField="AtSchool" HeaderText="是否在校" />
<asp:HyperLinkField HeaderText="所學專業" DataTextField="Major"
DataTextFormatString="{0}" DataNavigateUrlFields="Major" DataNavigateUrlFormatString="http://gsa.ustc.edu.cn/search?q={0}"
Target="_blank" />
<asp:ImageField DataImageUrlField="Group" DataImageUrlFormatString="~/res/images/16/{0}.png"
HeaderText="分組">
</asp:ImageField>
</Columns>
</asp:GridView>
由於GridView並不支持服務器端分頁,因此我們沒有設置表格的AllowPaging和PageSize屬性,而是自定義了兩個按鈕來實現服務器端分頁:
<asp:Button ID="btnPrevious" CommandName="Previous" runat="server" OnCommand="OnPageButtonClick" Text="Previous" /> <asp:Button ID="btnNext" runat="server" CommandName="Next" OnCommand="OnPageButtonClick" Text="Next" /> Page <asp:Label runat="server" ID="lblCurrentPage"></asp:Label> of <asp:Label runat="server" ID="lblTotalPages"></asp:Label>
頁面第一次打開時需要加載表格數據:
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
BindGrid();
}
}
private void BindGrid()
{
// 1.設置總項數
int recordCount = GetTotalCount();
// 2.獲取當前分頁數據
DataTable table = GetPagedDataTable(CurrentPageIndex, PAGE_SIZE);
// 3.綁定到Grid
Grid1.DataSource = table;
Grid1.DataBind();
UpdatePageControls(recordCount);
}
綁定表格數據分為如下幾個步驟:
1. 獲取總記錄數
2. 獲取當前分頁數據
3. 綁定分頁數據到表格
其實,表格對象對當前分頁狀態一無所知(第幾頁,總共有幾頁),我們需要自己在頁面上保存這些數據:
private int CurrentPageIndex
{
get
{
var pageIndexState = ViewState["CurrentPageIndex"];
if (pageIndexState == null)
{
return 0;
}
else
{
return Convert.ToInt32(pageIndexState);
}
}
set
{
ViewState["CurrentPageIndex"] = value;
}
}
private const int PAGE_SIZE = 5;
private int CalculatePageCount(int recordCount)
{
int pageCount = recordCount / PAGE_SIZE;
if (recordCount % PAGE_SIZE != 0)
{
pageCount++;
}
return pageCount;
}
將當前表格分頁索引CurrentPageIndex保存到ViewState中,以便在后續的頁面回發中獲取分頁索引。
總頁數可以根據當前分頁索引和每頁記錄數計算而來,我們將其邏輯封裝到CalculatePageCount方法中。
最后,來看下UpdatePageControls方法:
private void UpdatePageControls(int recordCount)
{
int pageCount = CalculatePageCount(recordCount);
lblTotalPages.Text = pageCount.ToString();
lblCurrentPage.Text = (CurrentPageIndex + 1).ToString();
if (CurrentPageIndex == 0)
{
btnPrevious.Enabled = false;
if (pageCount > 0)
{
btnNext.Enabled = true;
}
else
{
btnNext.Enabled = false;
}
}
else
{
btnPrevious.Enabled = true;
if (CurrentPageIndex == pageCount - 1)
{
btnNext.Enabled = false;
}
else
{
btnNext.Enabled = true;
}
}
}
根據當前表格分頁索引和總頁面設置分頁按鈕的狀態。
此時運行頁面,顯示效果:

點擊Next按鈕時,會發起一個頁面回發到后台事件:
protected void OnPageButtonClick(object sender, CommandEventArgs e)
{
switch (e.CommandName)
{
case "Previous":
CurrentPageIndex--;
break;
case "Next":
CurrentPageIndex++;
break;
}
BindGrid();
}
在分頁按鈕的點擊事件中,首先根據e.CommandName來判斷點擊了哪個按鈕,然后從ViewState中讀取當前表格分頁索引,最后重新綁定表格數據。
點擊Next后頁面截圖如下:

此時頁面的回發是Form表單的POST過程,因此會導致整個頁面的刷新,用戶體驗並不好。
FineUIPro的表格控件
FineUIPro中的大部分實現代碼和GridView的實現代碼一樣。
不過由於FineUIPro表格默認支持服務器端分頁,因此無需在后台通過ViewState保存表格分頁索引,也無需自己動手更新分頁按鈕的狀態,因此代碼要簡單的多。
<f:PageManager ID="PageManager1" AjaxLoadingType="Mask" runat="server" />
<f:Grid ID="Grid1" Title="表格" Width="800px" DataKeyNames="Id,Name" ShowBorder="true" ShowHeader="true"
AllowPaging="true" IsDatabasePaging="true" PageSize="5" runat="server" EnableCheckBoxSelect="True"
OnPageIndexChange="Grid1_PageIndexChange">
<Columns>
<f:RowNumberField />
<f:BoundField DataField="Name" DataFormatString="{0}" HeaderText="姓名" />
<f:TemplateField HeaderText="性別">
<ItemTemplate>
<asp:Label ID="Label2" runat="server" Text='<%# GetGender(Eval("Gender")) %>'></asp:Label>
</ItemTemplate>
</f:TemplateField>
<f:BoundField DataField="EntranceYear" HeaderText="入學年份" />
<f:CheckBoxField RenderAsStaticField="true" DataField="AtSchool" HeaderText="是否在校" />
<f:HyperLinkField HeaderText="所學專業" DataTextField="Major"
DataTextFormatString="{0}" DataNavigateUrlFields="Major" DataNavigateUrlFormatString="http://gsa.ustc.edu.cn/search?q={0}" UrlEncode="true"
Target="_blank" ExpandUnusedSpace="True" />
<f:ImageField DataImageUrlField="Group" DataImageUrlFormatString="~/res/images/16/{0}.png"
HeaderText="分組">
</f:ImageField>
</Columns>
</f:Grid>
這個表格定義和之前的GridView很類似,有幾點不同的地方:
1. PageManager是每一個使用FineUIPro控件的頁面都需要的,其中的AjaxLoadingType用來定義AJAX回發的提示類型。
2. 表格控件的AllowPaging,IsDatabasePaging,PageSize用來指定服務器端分頁和分頁記錄大小,這樣就無需自己維護分頁信息了。
3. 表格控件的PageIndexChanged用來定義服務器端分頁事件。
表格列還有一些特定的屬性,實現不同的顯示效果:
4.1. 表格列定義了RowNumberField,用來顯示行序號。
4.2 CheckBoxField的RenderAsStaticField用來指定復選框的顯示樣式。
4.3 HyperLinkField的ExpandUnusedSpace用來定義本列寬度占據所有未使用空間。
后台數據綁定代碼很簡單:
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
BindGrid();
}
}
private void BindGrid()
{
// 1.設置總項數
Grid1.RecordCount = GetTotalCount();
// 2.獲取當前分頁數據
DataTable table = GetPagedDataTable(Grid1.PageIndex, Grid1.PageSize);
// 3.綁定到Grid
Grid1.DataSource = table;
Grid1.DataBind();
}
此時頁面顯示效果:

由於FineUIPro內置了很多主題,因此我們可以在Web.config中設置不同的主題,得到不同的顯示效果:



分頁事件處理函數也很簡單:
protected void Grid1_PageIndexChange(object sender, GridPageEventArgs e)
{
BindGrid();
}
由於FineUIPro表格自行管理分頁信息,因此我們只需要重新綁定數據即可。
此時點擊下一頁,頁面截圖:

此時的回發是AJAX POST過程,整個頁面不會刷新,在回發過程中,FineUIPro會顯示一個回發提示動畫:

如果僅從代碼和運行效果對比,我們可以看出FineUIPro的表格控件相比ASP.NET原生控件,有如下優點:
1. 代碼有90%和原生控件保持一致
2. 代碼更少(得益於FineUIPro表格對服務器端分頁的內置支持)
3. 頁面顯示效果更美觀大方,並且可以通過全局配置切換不同的顯示樣式
4. 分頁過程是AJAX部分刷新,並內置了提示動畫
另外,全部示例代碼沒有一行JavaScript和CSS代碼,但是實際上FineUIPro卻是嚴重依賴JavaScript和CSS來實現頁面效果和交互。
下面我們會深入分析兩個示例的異同。
頁面渲染的對比
雖然兩個示例的大部分ASPX和C#代碼一模一樣,但是從一開始兩者的實現方式就完全不同。
ASP.NET的表格控件
首先來看下ASP.NET表格控件生成的頁面HTML代碼:

簡化后看的更清楚:
<table>
<tr>
<th scope="col">姓名</th>
<th scope="col">性別</th>
<th scope="col">入學年份</th>
<th scope="col">是否在校</th>
<th scope="col">所學專業</th>
<th scope="col">分組</th>
</tr>
<tr>
<td>陳萍萍</td>
<td><span id="Grid1_ctl02_Label2">女</span></td>
<td>2000</td>
<td><input type="checkbox" checked="checked" disabled="disabled" /></td>
<td><a href="http://gsa.ustc.edu.cn/search?q=計算機應用技術" target="_blank">計算機應用技術</a></td>
<td><img src="../res/images/16/1.png" /></td>
</tr>
</table>
<input type="button" name="btnPrevious" value="Previous" id="btnPrevious" disabled="disabled" />
<input type="button" name="btnNext" value="Next" onclick="javascript:__doPostBack('btnNext','')" id="btnNext" />
Page
<span id="lblCurrentPage">1</span>
of
<span id="lblTotalPages">5</span>
可以看出:
1. ASP.NET表格渲染到頁面上是<table>標簽,並且包含了當前頁的全部數據
2. 分頁按鈕最終調用的__doPostBack函數,這個函數我們並不陌生,幾乎每個頁面都包含這樣一個默認的定義
<script type="text/javascript">
var theForm = document.forms['form1'];
if (!theForm) {
theForm = document.form1;
}
function __doPostBack(eventTarget, eventArgument) {
if (!theForm.onsubmit || (theForm.onsubmit() != false)) {
theForm.__EVENTTARGET.value = eventTarget;
theForm.__EVENTARGUMENT.value = eventArgument;
theForm.submit();
}
}
</script>
毫無疑問,調用此回發函數,其實就是對頁面上全局表單對象的提交(theForm.submit()),這將會是整個頁面的刷新。
FineUIPro的表格控件
FineUIPro表格控件生成的頁面HTML代碼:

簡化一下:
<div id="Grid1_wrapper">
<div id="Grid1_tpls" class="f-grid-tpls f-hidden">
<div class="f-grid-tpl" id="Grid1_ftpl_frow0_2">
<span id="Grid1_ftpl_frow0_2_Label2">女</span>
</div>
...
</div>
</div>
<script type="text/javascript">
F.load(function() {
new F.Grid({
renderTo: '#Grid1_wrapper',
title: '表格',
data: [{
"f0": ["", "陳萍萍", "#@TPL@#ftpl_frow0_2", "2000", "<i class=\"f-icon f-iconfont f-grid-static-checkbox f-checked\"></i>", "<a href=\"http://gsa.ustc.edu.cn/search?q=%e8%ae%a1%e7%ae%97%e6%9c%ba%e5%ba%94%e7%94%a8%e6%8a%80%e6%9c%af\" target=\"_blank\">計算機應用技術</a>", "<img src=\"/res/images/16/1.png\" class=\"f-grid-imagefield\"></img>"],
"f1": [101, "陳萍萍"],
"f6": "frow0"
}],
paging: true,
databasePaging: true,
pageSize: 5,
pageIndex: 0,
recordCount: 22,
listeners: {
paging: function(event, pageIndex, oldPageIndex) {
__doPostBack('Grid1', 'Page$' + pageIndex + '$' + oldPageIndex);
}
}
});
});
</script>
可以看出:
1. 表格數據在JavaScript代碼中,並渲染到頁面上一個容器(Grid1_wrapper)
2. 分頁事件同樣觸發的是__doPostBack事件
兩相對比,我們可以得出如下結論:
1. ASP.NET表格控件直接渲染為table標簽(包含數據)
2. FineUIPro表格控件會在頁面上生成一個div占位符,然后通過JavaScript來渲染出表格控件
FineUIPro的做法更加靈活,並且可以實現更加復雜的顯示效果,看下生成的DOM結構:

只所以有這么多的層次結構,是有很多原因的,簡單來說:
1. FineUIPro中表格是從面板繼承下來的,所以最外層的div節點是面板相關的
div.f-panel
->div.f-panel-header
->div.f-panel-bodyct
->div.f-panel-body
2. f-panel-body里面的層次才是表格的特定結構
div.f-panel-body
->div.f-grid-inner
->div.f-grid-headerct
->div.f-grid-bodyct
->table.f-grid-table
表格的這個特定DOM層次結構在啟用列鎖定時會變的更加復雜,如下所示:

啟用列鎖定時,f-grid-inner里面會分裂成兩部分,分別對應於鎖定表格和主表格,FineUIPro會負責這兩部分的同步工作。
由此可知,ASP.NET表格控件直接渲染table節點和數據的方式僅適合於簡單的形式,而FineUIPro為了更加好看的界面效果和更加復雜的邏輯實現,必須通過JavaScript來渲染界面和數據。而這一切對於開發人員都是透明的,FineUIPro開發人員只需要寫ASPX表格和C#代碼即可,剩下的交給我們。
頁面回發的對比
前面分析可知,ASP.NET表格和FineUIPro的分頁回發都是調用的__doPostBack函數,為什么一個是整個頁面刷新,而另一個是AJAX部分刷新?
這是因為FineUIPro耍了個小把戲,重寫了__doPostBack函數,翻開FineUIPro的客戶端JavaScript源代碼:
function _fjs_doPostBack(eventTarget, eventArgument, options) {
$.ajax({
type: 'POST',
url: url,
data: formDataBeforeAJAX,
dataType: 'text',
headers: {
'X-FineUI-Ajax': true
},
success: function (data) {
},
error: function (xhr, textStatus) {
},
complete: function (xhr, textStatus) {
ajaxComplete(xhr.responseText, textStatus, xhr);
}
});
}
(function() {
if (!isUND(__doPostBack)) {
__originalDoPostBack = __doPostBack;
__doPostBack = _fjs_doPostBack;
}
})();
這是簡化后的代碼,可以看到FineUIPro重新賦值:__doPostBack=_fjs_doPostBack;
而在_fjs_doPostBack中,調用了jQuery.ajax來發起AJAX請求,當然實際的實現要復雜的多,FineUIPro讓這一切變得透明起來,開發人員甚至不用寫一行JavaScript代碼就能享受jQuery.ajax的無刷新回發。
ASP.NET表格的回發(整個頁面刷新)
瀏覽器中F12,打開Network選項卡,觀察ASP.NET表格的分頁回發過程:

可以看出:
1. ASP.NET表格頁面回發是整個頁面刷新,返回的是完整的HTML標簽(包含html,head,body....)
2. 由於是頁面重新渲染,所以頁面資源會重新加載,比如common.css文件
FineUIPro表格的回發(AJAX部分刷新)
瀏覽器中F12,打開Network選項卡,觀察FineUIPro表格的分頁回發過程:

可以看出,請求參數中包含X-Requested-With=XMLHttpRequest參數,說明這是一個AJAX部分刷新過程
返回的響應正文如下所示:

這是一段JavaScript代碼,其中包含表格當前頁的數據,並通過表格的客戶端API函數來重現加載表格數據。
由於是部分刷新,頁面資源無需重新加載,整個頁面DOM節點也無需重建,而且響應正文的大小也要小很多。
源代碼下載
下載后放到FineUIPro官網示例源代碼中即可:
https://files.cnblogs.com/files/sanshi/fineuipro_database_paging.zip
小結
經過上述分析,我們可以得知,FineUIPro使用JavaScript來渲染頁面,並且使用jQuery.ajax來更新頁面控件。
對於開發人員來說這一切都是透明的,開發人員只需要關注ASPX和C#代碼,關注自己的業務既可以了,剩下的都丟給FineUIPro來處理。
