分页之美
实现网站上的分页功能,其实没有什么难度,只要逻辑不出差错就可以正常工作了。至于代码写的怎样也就很少有人关心了,今天把分页按照思路写了一下,然后再进行优化。当然无论写的功能有多少,我们总是希望要能够复用,下次就不用再写了。每次使用也不会出错,健壮性很强。最后呢就是任何一个新人能够在几分钟内读懂代码,维护性好。同时达到这些要求,那就是完美!
这个分页的功能很一般,分别实现:第一页,最后一页,下一页,上一页,跳到某一页。主要涉及到的参数如下:
- rPerPage:每页记录数
- rCount:总记录数
- dCount:每次显示的可选择的页码(例如当前页为2时,有[1][2][3][4][5][6][7][8][9][10] 十个链接,当前页为14时,有[11]到[20]十个链接)
在实现的时候,只实现最核心的东西,其它都用回调函数去让外部定制。这样每次使用时自由度也会很大。去掉任何与显示有关的东西,只做加减法。下面是Page类代码:
1 function Page(rCount/*total records count*/,dispCount/*page count display each time*/,recordPerPage/*records count per page*/,callback,rcallback){ 2 this.rPerPage=recordPerPage;//records count per page 3 this.pCount= Math.ceil(rCount/this.rPerPage);//total page count 4 this.dCount=dispCount;//display page number count each time 5 this.curIdx = 1;//current page index 6 this.onchange=callback; 7 this.onrender=rcallback; 8 } 9 Page.prototype.next = function(){ 10 this.toPage(this.curIdx+1); 11 }; 12 Page.prototype.pre = function(){ 13 this.toPage(this.curIdx-1); 14 }; 15 Page.prototype.first = function(){ 16 this.toPage(1); 17 }; 18 Page.prototype.last = function(){ 19 this.toPage(this.pCount); 20 }; 21 Page.prototype.toPage=function(idx){ 22 if(idx<1 || idx > this.pCount) return; 23 this.curIdx=idx; 24 if(this.onchange) this.onchange.call(this,idx); 25 this.printp(); 26 }; 27 Page.prototype.printp=function(){ 28 var rg = Math.ceil(this.curIdx/this.dCount); 29 var u=(rg-1)*this.dCount+1, 30 l=Math.min(rg*this.dCount,this.pCount); 31 var d = []; 32 while(u!==l+1) d.push(u++); 33 if(this.onrender) this.onrender.call(this,d); 34 };
我们看到其实做事情的就两个函数,一个是toPage,一个是printp,其中toPage顾明思义就是跳到指定页,其它的如第一页,上一页等等都调用他即可。他在设置当前页索引的同时调用了onchange函数,并把新的页面索引传给它。这个onchange是我们从外面传入的回调函数至于做什么这边也不用关心了,只要让他知道变化后的页码。另外一个函数printp主要是显示页码的链接的。同样也是在每次索引变化后更新它。并把最新的页码数组传给外部的回调函数。
这样把Page做为一个类来使用,就可以达到复用的目的,做个简单的测试:
1 var ctPage = new Page(240,10,10); 2 ctPage.onchange = function(idx){ 3 console.log("current page:"+idx); 4 }; 5 ctPage.onrender = function(pn){ 6 console.log("current display numbers:"+pn); 7 }; 8 var zxPage = new Page(50,12,11,function(idx){ 9 console.log("zx curPage is:"+idx); 10 },function(pn){ 11 console.log("zx numbers:"+pn); 12 });
这样就初始化了两个分另叫做"ctPage"和"zPage"的对象,他们独立且互不影响。通过运行可以在控制台看到结果:
current page:1 current display numbers:1,2,3,4,5,6,7,8,9,10 current page:18 current display numbers:11,12,13,14,15,16,17,18,19,20 current page:22 current display numbers:21,22,23,24 zx curPage is:2 zx numbers:1,2,3,4,5
我相信保持这个类的简单性,可以让他更好的工作。至于使用场景也没限制,例如在onchange时,加入一个具有ajax请求的回调函数,这样就可以使用在ajax分页中。虽然简单的测试可以通过,不知道在实际环境中工作怎样,所以边使用边改进,努力让他成为最美的分页。
这次用了点时间为这个类写了一个小案例,我们先把上面的Page放到js文件pagination.js中,然后在页面中加入引用:
<script src="pagination.js" type="text/javascript"></script>
在页面中放入需要显示的容器:
<div id="pager"></div>
我们对这个分页做一个简单的样式:
#pager a,#pager span{ display: block; float: left; padding: 0.3em 0.5em; margin-right: 5px; margin-bottom: 5px; } #pager a{ text-decoration: none; border: solid 1px #AAE; color: #15B; } #pager .current{ background: #26B; color: #fff; border: solid 1px #AAE; }
下面这段脚本主要把Page类与表现连接起来并绑定交互的事件:
function makeBtn(type,text,clickEvt,scope,attr){ if(!type || type==="") return; var tag = document.createElement(type); if(clickEvt) tag.onclick = function(e){clickEvt.call(scope,e.target.innerHTML);};for(var key in attr) tag.setAttribute(key,attr[key]);
tag.innerHTML = text; return tag; } function dsp(nums){ var curIdx = this.curIdx, cont = document.getElementById("pager"), p = this,preBtn,nextBtn; cont.innerHTML = ""; cont.appendChild(makeBtn("a","first",p.first,p,{href:"#"})); cont.appendChild(makeBtn("a","prev",p.pre,p,{href:"#"})); nums.forEach(function(item){ if(item===curIdx) cont.appendChild(makeBtn("span",item,null,null,{class:"current"})); else cont.appendChild(makeBtn("a",item,p.toPage,p,{href:"#"})); }); cont.appendChild(makeBtn("a","next",p.next,p,{href:"#"})); cont.appendChild(makeBtn("a","last",p.last,p,{href:"#"})); } function handler(idx){ //do something like request data and update dom console.log("send request idx:"+idx); } var p = new Page(240,10,10,handler,dsp); p.first();
效果图,貌似是最大众的样式:
我们也很容易换一种样式:
上面样式的css:
#pager .current { background: #222; color: #FFF; border-color: #000; box-shadow: 0 1px 0 rgba(255,255,255,0.2), 0 0 1px 1px rgba(0, 0, 0, 0.1) inset; cursor: default; } #pager a:hover { text-decoration: none; background: #444; } #pager a{ text-decoration:none; } #pager a,#pager span { float: left; color: #CCC; font-size:14px; line-height:24px; font-weight: normal; text-align: center; border: 1px solid #222; min-width: 14px; padding: 0 7px; margin: 0 5px 0 0; border-radius: 3px; box-shadow: 0 1px 2px rgba(0,0,0,0.2); background: #555; /* Old browsers */ background: -moz-linear-gradient(top, #555 0%, #333 100%); /* FF3.6+ */ background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#555), color-stop(100%,#333)); /* Chrome,Safari4+ */ background: -webkit-linear-gradient(top, #555 0%,#333 100%); /* Chrome10+,Safari5.1+ */ background: -o-linear-gradient(top, #555 0%,#333 100%); /* Opera11.10+ */ background: -ms-linear-gradient(top, #555 0%,#333 100%); /* IE10+ */ background: linear-gradient(top, #555 0%,#333 100%); /* W3C */ } #pager{ padding: 20px 20px 15px 20px; background: #444; border: 1px solid #333; border-radius: 2px; height:35px; font-family:Arial, Helvetica, sans-serif; }
上面代码中的handler作为回调函数,可以做一些与服务器的交互和界面的更新(一般不超过10行啦)。而dsp函数主要是维护页码等按钮,代码不到10行。而最上边的makeBtn用来生成按钮的通用函数,不算在此模块里面边,所以我们直接把Page作为模块引用,只负责数据,接着只需要把Page类与“View”连接起来。那么每次就写20行以下的代码,并且保持了最大的灵活性,更重要的是正确性。Less is more.