动手改造一款Jquery 分页插件(服务器端分页)
分页几乎是每个外部程序必不可少的东西,在webform时代很多人都用过AspNetPager这个用户控件吧,用的人之多其实就在于它的优点:传给它几个参数就可以生成像样的分页,其实这个也是绝大多数程序员(包括我)的致命缺点:样式..,我对于咱们来说用尝试用CSS把一个模块调美观好看所花的时间远远超过了实现功能所需的时间- -!
现在越来越多做web的开始采用.NET MVC框架,抛开一切服务器控件,回归正宗的B/S编程,享受灵活控制好处之余,也得花费很多功夫,很多地方都得自己去实现,例如分页这样的东西。简单实现功能肯定很简单,但是要做到好用,通用,性能好,易于修改也不是一个很简单的事情,所以各种JS分页控件大显身手,如jquery pager、jquery Pagination等等,调用很方便,生成效果也很好,唯一缺点就是都是内存分页,数据量少点还好,如果成千上万几百上千万数据可就要命了。要是能有一个调用方便还能配合进行服务端分页的前端控件该多好呢:)
说了这么多接下来就给大家看看我利用一个富客户端UI框架(DWZ)中分离改造出来的一个JS分页控件,先看看效果图:
页面代码:
<div class="panelBar"> <div class="pages"> <span>显示</span> @Html.DropDownList("numPerPage", ViewBag.dNumPerPage as SelectList, new { onchange = "PageBreak({numPerPage:this.value})" }) <span>条,共 @Model.TotalCount 条</span> </div> <div class="pagination" totalcount="@Model.TotalCount" numperpage="@Model.NumPerPage" pagenumshown="@Model.PageNumShown" currentpage="@Model.CurrentPage"> </div>
其实这个完全可以写成HtmlHelper调用,例如 Html.Pagination(ViewBag.dNumPerPage,Model.TotalCount,Model.NumPerPage,Model.PageNumShown,Model.CurrentPage)
这样,一条语句就可以。
原理就是用js通过获取 class="pagination" 的div的自定义属性:总页数TotalCount,每页多少条NumPerPage ,显示多少页码 PageNumShown,当前页数CurrentPage,
然后将这四个属性传给分页js,分页js内利用这几个关键属性进行计算,然后将预制好的分页模板进行替换,展现出分页效果。
为了好让美工根据项目整体风格来对分页进行调整,实现好的分工合作的原则,这里分页的js和CSS和分页替换模板是完全分开的,不至于程序看CSS晕,美工看JS晕。
分页js分为两部分,一部分只负责根据参数计算分页(pagination.js),一部分是作之前的准备工作的(core.js),一些参数的预制,在这里其实只有一个就是分页模板的位置参数,还有一些分页内部用到的自定义扩展函数,分页模板为了美工便于修改观察,不混在js里面,而又便于js利用,这里用XML的格式(pagination.xml),先把代码贴出来
core.js:
(function($){ $.extend(String.prototype, { isPositiveInteger:function(){ return (new RegExp(/^[1-9]\d*$/).test(this)); }, replaceAll:function(os, ns) { return this.replace(new RegExp(os,"gm"),ns); } }); $.fn.extend({ hoverClass: function(className){ var _className = className || "hover"; return this.each(function(){ $(this).hover(function(){ $(this).addClass(_className); },function(){ $(this).removeClass(_className); }); }); } }); })(jQuery); var DWZ = { frag: {}, init: function (pageFrag) { $.ajax({ type: 'GET', url: pageFrag, dataType: 'xml', timeout: 50000, cache: false, error: function (xhr) { alert('Error loading XML document: ' + pageFrag + "\nHttp status: " + xhr.status + " " + xhr.statusText); }, success: function (xml) { DWZ.frag["pagination"]= $(xml).find("#pagination").text(); } }); } };
pagination.js:
(function($){ $.fn.pagination = function(opts){ var setting = { first$:"li.j-first", prev$:"li.j-prev", next$:"li.j-next", last$:"li.j-last", nums$:"li.j-num>a", jumpto$:"li.jumpto", pageNumFrag:'<li class="#liClass#"><a href="#">#pageNum#</a></li>' }; return this.each(function(){ var $this = $(this); var pc = new Pagination(opts); var interval = pc.getInterval(); var pageNumFrag = ''; for (var i=interval.start; i<interval.end;i++){ pageNumFrag += setting.pageNumFrag.replaceAll("#pageNum#", i).replaceAll("#liClass#", i==pc.getCurrentPage() ? 'selected j-num' : 'j-num'); } $this.html(DWZ.frag["pagination"].replaceAll("#pageNumFrag#", pageNumFrag).replaceAll("#currentPage#", pc.getCurrentPage())).find("li").hoverClass(); var $first = $this.find(setting.first$); var $prev = $this.find(setting.prev$); var $next = $this.find(setting.next$); var $last = $this.find(setting.last$); if (pc.hasPrev()){ $first.add($prev).find(">span").hide(); _bindEvent($prev, pc.getCurrentPage()-1, pc.targetType()); _bindEvent($first, 1, pc.targetType()); } else { $first.add($prev).addClass("disabled").find(">a").hide(); } if (pc.hasNext()) { $next.add($last).find(">span").hide(); _bindEvent($next, pc.getCurrentPage()+1, pc.targetType()); _bindEvent($last, pc.numPages(), pc.targetType()); } else { $next.add($last).addClass("disabled").find(">a").hide(); } $this.find(setting.nums$).each(function(i){ _bindEvent($(this), i+interval.start, pc.targetType()); }); $this.find(setting.jumpto$).each(function(){ var $this = $(this); var $inputBox = $this.find(":text"); var $button = $this.find(":button"); $button.click(function(event){ var pageNum = $inputBox.val(); if (pageNum && pageNum.isPositiveInteger()) { PageBreak({ pageNum: pageNum }); } }); $inputBox.keyup(function(event){ if (event.keyCode == 13) $button.click(); }); }); }); function _bindEvent(jTarget, pageNum, targetType){ jTarget.bind("click", {pageNum:pageNum}, function(event){ PageBreak({ pageNum: event.data.pageNum }); event.preventDefault(); }); } } var Pagination = function(opts) { this.opts = $.extend({ targetType:"navTab", // navTab, dialog totalCount:0, numPerPage:10, pageNumShown:10, currentPage:1, callback:function(){return false;} }, opts); } $.extend(Pagination.prototype, { targetType:function(){return this.opts.targetType}, numPages:function() { return Math.ceil(this.opts.totalCount/this.opts.numPerPage); }, getInterval:function(){ var ne_half = Math.ceil(this.opts.pageNumShown/2); var np = this.numPages(); var upper_limit = np - this.opts.pageNumShown; var start = this.getCurrentPage() > ne_half ? Math.max( Math.min(this.getCurrentPage() - ne_half, upper_limit), 0 ) : 0; var end = this.getCurrentPage() > ne_half ? Math.min(this.getCurrentPage()+ne_half, np) : Math.min(this.opts.pageNumShown, np); return {start:start+1, end:end+1}; }, getCurrentPage:function(){ var currentPage = parseInt(this.opts.currentPage); if (isNaN(currentPage)) return 1; return currentPage; }, hasPrev:function(){ return this.getCurrentPage() > 1; }, hasNext:function(){ return this.getCurrentPage() < this.numPages(); } }); })(jQuery);
分页模板pagination.xml:
<?xml version="1.0" encoding="utf-8"?> <_AJAX_> <!-- pagination --> <_PAGE_ id="pagination"><![CDATA[ <ul> <li class="j-first"> <a class="first" href="#"><span>首页</span></a> <span class="first"><span>首页</span></span> </li> <li class="j-prev"> <a class="previous" href="#"><span>上一页</span></a> <span class="previous"><span>上一页</span></span> </li> #pageNumFrag# <li class="j-next"> <a class="next" href="#"><span>下一页</span></a> <span class="next"><span>下一页</span></span> </li> <li class="j-last"> <a class="last" href="#"><span>末页</span></a> <span class="last"><span>末页</span></span> </li> <li class="jumpto"><input class="jumptotext" type="text" value="#currentPage#" /><input class="goto" type="button" value="确定" /></li> </ul> ]]></_PAGE_> </_AJAX_>
pagination.css:
@charset "utf-8"; /* CSS Document */ /* public */ ul li,ol li,dt,dd {list-style:none;} a:link{ color:#000000; text-decoration:none; } a:visited{ color:#000000; text-decoration:none; } a:hover{ color:#000000; text-decoration:underline; } /* pagination */ .panelBar{ margin-top:12px; height:26px; line-height:26px; } .panelBar ul{ float:left; } .panelBar ul li{ float:left; } .disabled{ padding:0 6px; } .j-num{ padding:0 4px; } .pages { margin-top:12px; float:left; } .pagination { float:left; padding-left:50px; } .pagination li, .pagination li.hover { padding:0 0 0 5px;} .pagination a, .pagination li.hover a, .pagination li span { float:left; display:block; padding:0 5px 0 0; text-decoration:none; line-height:23px; background-position:100% -150px;} .pagination li.selected a{color:red; font-weight:bold;} .pagination span, .pagination li.hover span { float:left; display:block; height:23px; line-height:23px; cursor:pointer;} .pagination li .first span, .panelBar li .previous span { padding:0 0 0 10px;} .pagination li .next span, .panelBar li .last span { padding:0 10px 0 0;} .pagination li .first span { background:url(images/pagination/pagination_first_a.gif) left 5px no-repeat;} .pagination li .previous span { background:url(images/pagination/pagination_previous_a.gif) left 5px no-repeat;} .pagination li .next span { background:url(images/pagination/pagination_next_a.gif) right 5px no-repeat;} .pagination li .last span { background:url(images/pagination/pagination_last_a.gif) right 5px no-repeat;} .pagination li .last { margin-right:5px;} .pagination li.disabled { background:none;} .pagination li.disabled span, .grid .pagination li.disabled a { background-position:0 100px; cursor:default;} .pagination li.disabled span span { color:#666;} .pagination li.disabled .first span { background:url(images/pagination/pagination_first_span.gif) left 5px no-repeat;} .pagination li.disabled .previous span { background:url(images/pagination/pagination_previous_span.gif) left 5px no-repeat;} .pagination li.disabled .next span { background:url(images/pagination/pagination_next_span.gif) right 5px no-repeat;} .pagination li.disabled .last span { background:url(images/pagination/pagination_last_span.gif) right 5px no-repeat;} .pagination li.disabled .last { margin-right:5px;} .pagination li.jumpto { padding:2px 2px 0 7px; background-position:0 -200px;} .pagination li.jumpto .jumptotext { float:left; width:30px; height:15px; padding:1px; border-color:#acaeaf; background:#ffffff; border:1px solid #9999cc;} .pagination li.jumpto .goto { float:left; display:block; overflow:hidden; width:16px; height:19px; border:0; text-indent:-1000px; background:url(images/pagination/pagination_goto.gif) no-repeat; cursor:pointer;}
我看CSS也头疼,这个是我找一个朋友写的,自己又改了改
接下来讲讲使用方法:
首先 指定自己分页模板的路径 DWZ.init("js/pagination.xml"); 然后调用分页:
$(function(){ $("div.pagination").each(function () { var $this = $(this); $this.pagination({ totalCount: $this.attr("totalCount"), numPerPage: $this.attr("numPerPage"), pageNumShown: $this.attr("pageNumShown"), currentPage: $this.attr("currentPage") }); }); });
这里为什么用each呢,为啥不直接$("div.pagination").pagination({})呢,大家应该想到了,有挺多时候用户喜欢在列表的上下都放分页便于操作,这两块js其实可以单独放一个文件,需要用到分页的文件引用即可,因为程序真正用到的是web和服务器端交换所关心的数据,totalCount numPerPage pageNumShown currentPage ,之前所做的所有都只是实现好用通用的框架。现在无需改动的东西都做好了,怎么实现交互呢,很简单:点击页码或者选择每页显示下拉框或者点击go按钮时候都会触发一个自定义函数 PageBreak ,这个函数内部就大家想实现什么具体去写,例如
function PageBreak(args) { alert(args["pageNum"]||args["numPerPage"]); }