FineUIPro控件庫深度解析


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來處理。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM