昨天在ASP.NET MVC利用PagedList分頁(一)的最后一節提到,一個好的用戶體驗絕對不可能是點擊下一頁后刷新頁面,所以今天來說說利用Ajax+PagedList實現無刷新(個人絕對局部刷新更准確些)的分頁。其實在PagedList.Mvc中早已經為我們提供好了Ajax分頁的各種東東,但是這里我要自己寫下。
實現思想:
1、客戶端發送Ajax請求。2、服務器端響應請求並將響應結果回傳給客戶端。3、客戶端接收響應結果並進行數據綁定。
實現方案:
大多數人都知道這個思想,但是面對一個陌生的環境,我不得不理一下思路然后再討論實現方案:1、利用Jquery Ajax發送分頁請求。2、進行數據划分並利用SataticPagedList<T>(我個人比較喜歡這個,也可以用ToPagedList)綁定。3、利用Jquery Ajax接收數據並進行數據綁定。ok,實現方案出爐,下面直接上代碼:
//View:Views/Person/Ajax <link href="/Content/PagedList.css" type="text/css" rel="Stylesheet" /> ...... <script type="text/javascript" src="/Scripts/jquery-1.5.1.js"></script> <script type="text/javascript"> $(function () { getPersonByAjax(1); }); //Ajax請求 var getPersonByAjax = function (pageNumber) { $.ajax({ url: "/Person/AjaxPage?page=" + pageNumber, type: "POST", dataType: "json", success: function (data) { //接收數據(data)並綁定 var html = ""; $.each(data.persons, function (i, item) { html += "<tr><td>" + item.FirstName + "</td><td>" + item.LastName + "</td><td><a href='/Person/Edit/" + item.PersonID + "'>Edit</a> | <a href='/Person/Details/" + item.PersonID + "'>Details</a> | <a href='/Person/Delete/" + item.PersonID + "'>Delete</a></td></tr>"; }); $("#personList").html(html); }, error: function (result) { alert(result.statusText); }, complete: function (jqXHR) { jqXHR = null; } }); } </script> //Controller:PersonController
//響應Ajax請求 public ActionResult AjaxPage(int? page) { int pageIndex = page ?? 1; int pageSize = 2; int totalCount = 0; var persons = GetPerson(pageIndex, pageSize, ref totalCount); var personsAsIPageList = new StaticPagedList<Person>(persons, pageIndex, pageSize, totalCount); return Json(new { persons = personsAsIPageList},JsonRequestBehavior.AllowGet); } public List<Person> GetPerson(int pageIndex, int pageSize, ref int totalCount) { var persons = (from p in db.Persons orderby p.PersonID descending select p).Skip((pageIndex - 1) * pageSize).Take(pageSize); totalCount = db.Persons.Count(); return persons.ToList(); }
(注:這里運用到了Json在服務器和客戶端之間傳遞的知識額...我非常喜歡Json接收強類型對象,后頭有機會慢慢談下。)
在寫“接收數據(data)並綁定之前”我還在天真的想,personAdIPagedList不會回吧HasPreviousPage、HasNextNextPage也傳遞過來呢?於是我用Fillder服務器端到底會傳遞什么回來,結果如下:

掛了,貌似他至傳回了persons的json數據,那分頁咋辦呢?原來在IPagedList<T>中有個GetMetaData,他會返回一個PagedListMetaData對象,在這里進行了分頁導航相關屬性的分裝:

添加分頁導航
good,這下我可以完善我的代碼了。 如下:
public ActionResult AjaxPage(int? page) { int pageIndex = page ?? 1; int pageSize = 2; int totalCount = 0; var persons = GetPerson(pageIndex, pageSize, ref totalCount); var personsAsIPageList = new StaticPagedList<Person>(persons, pageIndex, pageSize, totalCount); return Json(new { persons = personsAsIPageList, pager = personsAsIPageList.GetMetaData() }, JsonRequestBehavior.AllowGet); }

可以看到這里PagedList傳回了所有的分頁信息。所以可以做分頁導航了:
//創建分頁導航 var pager = data.pager; html = ""; if (pager.HasPreviousPage) { html += "<a href='#' onclick='getPersonByAjax(" + (pager.PageNumber - 1) + ");return false;'><<</a> <a href='#' onclick='getPersonByAjax(1);return false;'>< Prev</a>"; } else { html += "<< < Prev"; } html += " "; if (pager.HasNextPage) { html += "<a href='#' onclick='getPersonByAjax(" + (pager.PageNumber + 1) + ");return false;'>Next ></a> <a href='#' onclick='getPersonByAjax(" + pager.PageCount + ");return false;'>>></a>"; } else { html += "Next > >>"; } $("#pager").html(html);
將上面代碼插入success中,分頁就創建好了。當我正在高興的時候我大概忘記了剛剛做字符串拼接的辛苦。冷靜下來后我和PagedList提供的實例(好像忘了給地址,給一個:https://github.com/TroyGoode/PagedList)進行了對比,對比結果是我無比蛋疼。在其提供實例中用到了jquery.tmpl.js,原來我們真的不用做如此多的字符處啊拼接工作。
優化綁定
jquery.tmpl.js是一個模板插件,能夠用對象或數據等對模板(包含html、css等標簽和綁定表達式。)進行渲染,舉個例子:"<li class='XXX' id='liTemplate'>${name}</li>"這就是一個模板,包含html、css和綁定表達式(${name}),現在我們只需利用:$.template("templateName",$('#liTemplate').html());$.tmpl(templateName,list).appendTo('#liContainer');將模板渲染(tmpl)然后插入容器(liContainer)展示即可。第一句將liTemplate標記為一個模板,名稱為templateName。第二句利用list將templateName進行渲染,然后插入到liContainer中。list可以使一個數據,也可以是一個對象,例如:var list = {"name":"hello world","name":"heihei"}。總之,tmpl可以讓我們不用在寫字符串連接,它的可見性太差了,極其難維護。同時,利用tmpl需要三要素:模板,容器,數據。利用數據渲染模板,將結果插入容器中我們便可以輕松完成js中的數據綁定。
事后,我在其官網(https://github.com/jquery/jquery-tmpl)上查閱了相關治療,發現jquey.tmpl還停留在測試期,並且不再提供升級,被jquery放棄了。。。下一代jquery 模板由JsRender和JsViews承擔,相關資料可以去官網上看(JsRender:https://github.com/BorisMoore/jsrender;JsViews:https://github.com/BorisMoore/jsviews)。JsRedner和tmpl差不多,但是據介紹說,JsRender的渲染速度比tmpl快很多很多,到底快多少我也不知道,而且給我的直觀感受就是,JsRedner比tmpl的語法簡單的寫。。這個我喜歡。JsRender強調純粹的基於字符串的渲染,而且可以不需要dom和jquery的支持,上面我們可以看到tmpl是需要的:$.template("templateName",$('#liTemplate').html());;而JsViews則是建立在JsRender上更高層次的封裝,他強調建立交互式的數據驅動視圖,具體的貌似我目前還不是很了解。
ok,廢話少說,關於JsRender相關的東東在后頭我在慢慢整理出來,下面看看它在項目中間的運用,當時我真的大吃一驚,修改View:Views/Person/Ajax代碼如下:
//View:Views/Person/Ajax ...... //綁定模板 <script type="text/x-jsrender" id="personListTemplate"> {{for persons}} <tr> <td>{{:FirstName}}</td> <td>{{:LastName}}</td> <td> <a href="/Person/Edit/{{:PersonID}}">Edit</a> | <a href="/Person/Details/{{:PersonID}}">Details</a> | <a href="/Person/Delete/{{:PersonID}}">Delete</a> </td> </tr> {{/for}} </script> //分頁模板 <script type="text/x-jsrender" id="pagerTemplate"> {{if HasPreviousPage}} <a href="#" onclick="getPersonByAjax(1);return false;"><<</a> <a href="#" onclick="getPersonByAjax({{:PageNumber - 1}});return false;">< Prev</a> {{else}} << < Prev {{/if}} {{if HasNextPage}} <a href="#" onclick="getPersonByAjax({{:PageNumber + 1}});return false;">Next ></a> <a href="#" onclick="getPersonByAjax({{:PageCount}});return false;">>></a> {{else}} Next > >> {{/if}} </script> ........ <script type="text/javascript" src="/Scripts/jquery-1.5.1.min.js"></script> <script type="text/javascript" src="/Scripts/jsrender.js"></script> <script type="text/javascript"> $(function () { getPersonByAjax(1); }); var getPersonByAjax = function (pageNumber) { $.getJSON("/Person/AjaxJsRenderPage", { "page": pageNumber }) .success(function (data) { $("#personList").empty(); //JsRender渲染、渲染結果(字符串)插入容器
$("#personList").append($("#personListTemplate").render(data)); $("#pager").html($("#pagerTemplate").render(data.pager)); }) .error(function (textStatus) { alert("msg:" + textStatus.statusText); }) .complete(function (jqXHQ) { jqXHQ = null; }); } </script>
少去了太多的連接字符串代碼(codeless),而且可見性很強,非常容易維護。ok,ajax分頁就這么完成了。后頭我們在慢慢談下PagedList的分頁思想和整理下JsRender相關的知識。
補充
前面的代碼在后來發現了兩個問題:
第一、分頁時如何定位到具體頁?
第二、當數據為空時候的處理?
第二個問題很簡單,判斷data.persons.length是否為0既可解決,第二個問題要用到window.location.hash。其實就是錨,不過我可以用“window.location.hash = XX”設置標簽值和使用“window.location.hash”來獲取標簽值罷了。下面來看看如何使用location.hash進行分頁和頁碼定位:
$(function () { getPerson(); }); var getPerson = function () { var pageNumber; if (arguments[0] == null) { var hash = (window.location.hash); if (hash) pageNumber = hash.slice(1); else { pageNumber = 1; window.location.hash = pageNumber; } } else { pageNumber = arguments[0]; window.location.hash = pageNumber; } getPersonByAjax(pageNumber); }
用上面代碼替換$(function () {getPersonByAjax(1);});就ok了。可以看到,這里用window.location.hash來獲取頁面的頁碼和根據頁碼來設置window.location.hash,兩只始終一致,這樣就解決了ajax分頁時的具體頁定位問題。
