一直以來覺得用JSON和JavaScript在客戶端綁定數(shù)據(jù)給一個(gè)表格或者Grid是件很麻煩的事情。Microsoft ASP.NET Ajax提供了類似Sys.Date.DataTable和Sys.Dat.DataView這樣的類來幫助實(shí)現(xiàn)客戶端綁定,也可以用for循環(huán)來動(dòng)態(tài)構(gòu)建表格,但這些都顯得很麻煩而且很不靈活。jQuery的jTemplates插件實(shí)現(xiàn)了一種靈活的方式來控制顯示,它允許我們定義好一個(gè)顯示模板,jQuery在展現(xiàn)數(shù)據(jù)時(shí)根據(jù)選擇的模板來動(dòng)態(tài)生成。這就類似于ASP.NET中的ItemTemplate,也和XSLT有些類似。通過這樣的方式,你可以很容易的在客戶端通過自定義模板以很靈活的方式展現(xiàn)列表數(shù)據(jù)。
jQuery官方網(wǎng)站給jTemplates的定義是:jTemplates is a template engine 100% in JavaScript.更多的信息可以參考http://jtemplates./。 接下來我們實(shí)現(xiàn)一個(gè)小例子來演示如何使用jTemplate去構(gòu)建一個(gè)RSS閱讀器。
創(chuàng)建 RSS 閱讀器
RSS源通常都位于另外的domain中,而在AJAX中瀏覽器通常禁止cross-domain的訪問,在這里,為了避開這個(gè)問題我們可以在服務(wù)端去取得數(shù)據(jù)。通常我們可以將這些方法做成WebMethod方法放到WebServices中,但這里我們可以將這些方法放到頁面的cs文件中。需要注意的是,這個(gè)方法必須被聲明為Static方法,而且要以WebMethod標(biāo)注。這樣做的目的是,只有在標(biāo)注為WebMethod,客戶端才能正常訪問這個(gè)方法。而Static標(biāo)記標(biāo)識(shí)了這個(gè)方法不與任何這個(gè)頁面的實(shí)例相關(guān),而是而這個(gè)頁面本身相關(guān),對(duì)于任何一個(gè)實(shí)例而言都是相同的。所以在你調(diào)用的時(shí)候,你不需要與任何頁面類的實(shí)例相關(guān)。
[WebMethod]
public static IEnumerable GetFeeds(int PageSize,int PageNumber)
{
string strFeedUrl = System.Configuration.ConfigurationManager.AppSettings["FeedUrl"];
XDocument feedXML = XDocument.Load(strFeedUrl);
var feeds = from feed in feedXML.Descendants("item")
select new
{
Date = DateTime.Parse(feed.Element("pubDate").Value).ToShortDateString(),
Title = feed.Element("title").Value,
Link = feed.Element("link").Value,
Description = feed.Element("description").Value,
};
//paging... (LINQ)
return feeds.Skip((PageNumber - 1) * PageSize).Take(PageSize);
}
|
在上邊的方法中設(shè)定了RSS的地址,并通過LINQ to XML來取得我們想要的屬性。Skip和Take函數(shù)聯(lián)合起來實(shí)現(xiàn)了一個(gè)分頁的功能。
通過 jQuery 調(diào)用 Page Method
jQuery.Ajax方法實(shí)現(xiàn)了用Ajax的方式來請(qǐng)求一個(gè)頁面并設(shè)定回調(diào)函數(shù)來處理相應(yīng)狀態(tài)和結(jié)果。在我們的實(shí)例中,需要請(qǐng)求上邊寫的PageMethod并處理返回結(jié)果。
function DisplayRSS(page) {
$.ajax({
type: "POST",
url: "Default.aspx/GetFeeds",
data: "{'PageSize':'" + pageSize + "', 'PageNumber':'" + page + "'}",
contentType: "application/json; charset=utf-8",
dataType: "json",
success: function(msg) {
//TODO:Show the result as a table.
alert(msg);
}
});
}
|
在success的回調(diào)函數(shù)中我們什么也沒有做,先來看看result到底是個(gè)什么東西。在瀏覽器中設(shè)置允許調(diào)試腳本,定義一個(gè)函數(shù)供回調(diào)函數(shù)中調(diào)用,然后設(shè)定斷點(diǎn)在新的函數(shù)中。
我們看到在服務(wù)端將數(shù)據(jù)以IEnumerable返回后實(shí)際上在result中包含的是一個(gè)JSON表示的數(shù)據(jù)集合。每個(gè)對(duì)象含有Date, Description, Link和Title屬性,這和前邊用LINQ取XML字段時(shí)是相符的。因?yàn)槟阋呀?jīng)擁有了這個(gè)數(shù)據(jù)集合,接下來所要做的就是在客戶端通過某種方式展現(xiàn)出來。你也許會(huì)想到用動(dòng)態(tài)拼接Table的方式來做,但這并不靈活。jTemplates提供了更優(yōu)雅的方式來實(shí)現(xiàn)。
用 jTemplate 來展現(xiàn)數(shù)據(jù)
jTemplate就把數(shù)據(jù)展現(xiàn)的方式和業(yè)務(wù)邏輯分開來,允許你定義好一個(gè)模板,然后再運(yùn)行時(shí)會(huì)根據(jù)模板的內(nèi)容來render. 和ASP.NET中的綁定相似,也有一個(gè)特定的符號(hào)來表示綁定的上下文對(duì)象$T。$T就類似于上圖中的result.d[n]—某一個(gè)業(yè)務(wù)對(duì)象,需要展現(xiàn)哪個(gè)屬性后邊直接跟.PropertyName即可。因?yàn)楸砀竦男惺强勺兊?,所以這里就類似于XSLT中一樣來控制動(dòng)態(tài)生成.
<table id="RSSTable" cellspacing="0">
<thead>
<tr>
<th width="80">Date</th>
<th>Title / Excerpt</th>
</tr>
</thead>
<tbody>
{#foreach $T.d as post}
<tr>
<td rowspan="2">{$T.post.Date}</td>
<td><a href="{$T.post.Link}">{$T.post.Title}</a></td>
</tr>
<tr>
<td>{$T.post.Description}</td>
</tr>
{#/for}
</tbody>
</table>
|
在頁面請(qǐng)求完P(guān)ageMethod并成功返回后可以來應(yīng)用模板展現(xiàn)數(shù)據(jù)了:
$(document).ready(function() {
// On page load, display the first page of results.
DisplayRSS(1);
});
function DisplayRSS(page) {
……
success: function(msg) {
// Render the resulting data, via template.
ApplyTemplate(msg);
// TODO: Update navigation status
}
……
}
function ApplyTemplate(msg) {
$('#Container').setTemplateURL('Template/RSSTable.htm',
null, { filter_data: false });
$('#Container').processTemplate(msg);
}
|
現(xiàn)在為止我們只取得了數(shù)據(jù)并展現(xiàn)了特定的條數(shù)而無法實(shí)現(xiàn)分頁??雌饋硪磺卸己谩朔猪?。
增加客戶端分頁功能
為了實(shí)現(xiàn)分頁首先需要知道頁碼總數(shù),這樣我們可以簡(jiǎn)單的通過Previous | Next來實(shí)現(xiàn)導(dǎo)航。假設(shè)一下在服務(wù)端我們需要什么:總頁數(shù),頁大小,當(dāng)前頁,判斷并控制Previous|Next導(dǎo)航的有效性及其動(dòng)作。ok,明白我們所要做的步驟:
· 獲取頁總數(shù) – 通過Page Method來返回
· 控制頁大小和當(dāng)前頁
· 控制Previous | Next導(dǎo)航的有效性
· 實(shí)現(xiàn)Previous | Next導(dǎo)航動(dòng)作
首先,在Template中增加頁面導(dǎo)航:
<div id="Paging">
<a id="PrevPage" class="paging">« Previous Page</a>
<a id="NextPage" class="paging">Next Page »</a>
</div>
|
其次,聲明獲取頁面總數(shù)或者條目總數(shù)的Page Method. 和前邊獲取數(shù)據(jù)源的方法很類似我們除了不需要返回任何XML的屬性值外僅僅調(diào)用Count方法即可。
[WebMethod]
public static int GetFeedsCount()
{
string strFeedUrl = System.Configuration.ConfigurationManager.AppSettings["FeedUrl"];
XDocument feedXML = XDocument.Load(strFeedUrl);
return feedXML.Descendants("item").Count();
}
|
設(shè)置默認(rèn)的頁面大小,并在顯示數(shù)據(jù)后更新導(dǎo)航欄狀態(tài)。在頁面中我們需要請(qǐng)求這個(gè)PageMethod并來計(jì)算總的頁數(shù)。
var currentPage = 1;
var lastPage = 1;
var pageSize = 5;
function GetRSSItemCount() {
$.ajax({
type: "POST",
url: "Default.aspx/GetFeedsCount",
data: "{}",
contentType: "application/json; charset=utf-8",
dataType: "json",
success: function(msg) {
// msg.d will contain the total number of items, as
// an integer. Divide to find total number of pages.
lastPage = Math.ceil(msg.d / pageSize);
// TODO: Update navigation status
}
});
}
|
接下來就是實(shí)現(xiàn)// TODO: Update navigation status標(biāo)記的內(nèi)容了:更新導(dǎo)航欄的狀態(tài)及其動(dòng)作。當(dāng)前頁的值存儲(chǔ)在currentPage變量中,lastPage告訴我們哪一頁是最后頁,通過這兩個(gè)參數(shù)可以很容易的控制導(dǎo)航狀態(tài)。而因?yàn)樗麄兪侨肿兞浚詧?zhí)行導(dǎo)航時(shí)只需要通過簡(jiǎn)單的++/--操作,最終請(qǐng)求GetFeeds方法時(shí)會(huì)取得相應(yīng)頁的數(shù)據(jù)。
function UpdatePaging() {
// If we're not on the first page, enable the "Previous" link.
if (currentPage != 1) {
$('#PrevPage').attr('href', '#');
$('#PrevPage').click(PrevPage);
}
// If we're not on the last page, enable the "Next" link.
if (currentPage != lastPage) {
$('#NextPage').attr('href', '#');
$('#NextPage').click(NextPage);
}
}
function NextPage(evt) {
// Prevent the browser from navigating needlessly to #.
evt.preventDefault();
// Entertain the user while the previous page is loaded.
DisplayProgressIndication();
// Load and render the next page of results, and
// increment the current page number.
DisplayRSS(++currentPage);
}
function PrevPage(evt) {
// Prevent the browser from navigating needlessly to #.
evt.preventDefault();
// Entertain the user while the previous page is loaded.
DisplayProgressIndication();
// Load and render the previous page of results, and
// decrement the current page number.
DisplayRSS(--currentPage);
}
|
用UpdatePaging函數(shù)替換上邊標(biāo)注的TODO部分,完整的分頁功能即可實(shí)現(xiàn)。當(dāng)然,這里的分頁顯得很粗糙,但你可以通過擴(kuò)展其樣式來做出更豐富的導(dǎo)航欄。另外示例中也提供了loading時(shí)提示用戶,這里不盡具表。一個(gè)很好的啟示是方便的展現(xiàn)LIST并提供簡(jiǎn)單的客戶端分頁。

點(diǎn)擊這里下載實(shí)例項(xiàng)目。(Vs.net 2008)
|