您的位置:首页 > Web前端 > JavaScript

关于jsp中的分页技术

2006-09-13 22:39 183 查看
欢迎转载,转载时注明来自彭仁夔Blog(http://blog.csdn.net/jxnuprk0924/)

1、概述

提起分页技术,基本上可以这样说,每个web系统都会用到。但什么是分页技术?
下面给出一些我经常光顾网站的的分页截图:



这个是www.zhaopin.com的分页的截图。当你点[10]时,你会得到:



看到什么不同了吗?
再给一个www.mycodes.net的一个分页的截图。


在这个例子中,你能发现什么?每个网站或b/s系统都应该有着各种不同的风格。也就是它们的分页的样式各不一样。但他们又有什么相同的地方?那么能不能把它们相同的东西给抽提出来?
在网上搜了一些有关于这方面的资料。没有发现自己满意的。有很多的给出的例子与具体的数据耦合。其实我们要的是仅仅是在页面上用标签去完成一个分页的功能,而不要去设定具体的数据。也就是从后台的数据分离出来。
当然,国外开源一些相关的列表标签也含有分页功能。如displaytag、dotj等。但是有很多时,你在开发系统时会发现用他们的的标签对于列表来说是不适合的。非要自己去实现循环,去控制数据的显示。而且那么标签的分页对于我们中国人的Web系统也是不是很合适的。也就是说不能完全适合于我们的要求(题外话,displaytag真的很好用。还有报表功能)。
国内也有一些人开发了分页的标签,如jpager、pagercontrol.它们都是单纯用来完成分页的功能。把循环列表的实现分离出来。这样,我们还可以用 jstl中的 <c:foreach>标签随心所欲地实现具体的列表功能。当然也可以用到displaytag之类的。

二:常用的分页技术

如果你还处在刀耕火种的的年代,你一般会用到如下的相似的分页:
<%int pages=1;
String mesg = "";
if (request.getParameter("page")!=null && !request.getParameter("page").equals("")) {
String requestpage = request.getParameter("page");
try {
pages = Integer.parseInt(requestpage);
} catch(Exception e) {
mesg = "你要找的页码错误!";
}
book_list.setPage(pages);
}
%>
<table width="778">
<tr>
<td width="150" align="center">
<%@include file="/bookshop/inc/left.inc"%>
</td>
<td width="600">
<p align="center"><b><font color="#0000FF">隽隽电子书店图书<%= TransFormat.GB2unicode(classname) %>列表</font></b></p>
<%if (!keyword.equals("")) out.println("<p ><font color=#ff0000>你要查找关于 " + keyword + " 的图书如下</font></p>"); %>
<table width="100%" border="1" cellspacing="1" cellpadding="1" bordercolor="white">
<tr align="center" bgcolor="#DEF3CE">
<td>图书名称</td>
<td>作者</td>
<td>图书类别</td>
<td>出版社</td>
<td>单价</td>
<td width=110>选择</td>
</tr>
<% if (book_list.book_search(request)) {
if (book_list.getBooklist().size()>0 ){
for (int i=0;i<book_list.getBooklist().size();i++){
book bk = (book) book_list.getBooklist().elementAt(i);%>
<tr>
<td><%=TransFormat.GB2unicode(bk.getBookName()) %></td>
<td align="center"><%= (bk.getAuthor()) %></td>
<td align="center"><%= (bk.getClassname()).getBytes("ISO-8859-1") %></td>
<td align="center"><%= (bk.getPublish()) %></td>
<td align="center"><%= bk.getPrince() %>元</td>
<td align="center"><a href="#" onclick="openScript('buy.jsp?bookid=<%= bk.getId() %>','pur',300,250)" >购买</a> 
<a href="#" onclick="openScript('detail.jsp?bookid=<%= bk.getId() %>','show',400,500)" >详细资料</a></td>
</tr>
<% }
}else {
if (keyword.equals("")){
out.println("<tr><td align='center' colspan=6> 暂时没有此类图书资料</td></tr>");
} else {
out.println("<tr><td align='center' colspan=6> 没有你要查找的 " + keyword + " 相关图书</td></tr>") ;
}
}
} else {%>
<tr>
<td align="center" colspan=6> 数据库出错,请稍后</td>

</tr>
<% } %>
</table>
<table width="90%" border="0" cellspacing="1" cellpadding="1">
<tr>
<td align="right">总计结果为<%= book_list.getRecordCount() %>条,当前页第<%= book_list.getPage() %>页 <a href="booklist.jsp?classid=<%= classid%>&keyword=<%= keyword %>">首页</a> 
<% if (book_list.getPage()>1) {%>
<a href="booklist.jsp?page=<%= book_list.getPage()-1 %>&classid=<%= classid%>&keyword=<%= keyword %>">上一页</a> 
<% } %>
<% if (book_list.getPage()<book_list.getPageCount()-1) {%>
<a href="booklist.jsp?page=<%= book_list.getPage()+1 %>&classid=<%= classid%>&keyword=<%= keyword %>">下一页</a> 
<% } %>
<a href="booklist.jsp?page=<%= book_list.getPageCount() %>&classid=<%= classid%>&keyword=<%= keyword %>">未页</a> </td>
</tr>
</table>
这样有什么不好,当然代码写在页面,那就是一个最大的不好之处。没有采用MVC模式。还有如果分页的部分复杂一点如果像上面图1一样,那么一个页面不是有很多的代码。
下面是另外一个例子:
<TABLE cellSpacing=0 cellPadding=0 width="100%" border=0 bgcolor="#FFFFFF">
<TBODY>
<TR>
<TD>页次:<B><%=intPage%></B>/<B><%=intPageCount%></B> 每页<B><%=intPageSize%></B> 本类软件<B><%=intRowCount%></B>个</TD>
<TD width="50%" align="center">
<%//if (intPage!=1){%>
<A
href="sort.jsp?sortid=<%=classid%>&page=1"><FONT face=Webdings>9</FONT></A><A href="sort.jsp?sortid=<%=classid%>&page=<%=(intPage-1)%>"><FONT face=Webdings>7</FONT></A> 
<%//}
j=intPageCount;
if(j>5) j=5;
for(i=1;i<=j;i++){
%>
<A
href="sort.jsp?sortid=<%=classid%>&page=<%=(i)%>">[<%=i%>]</A>
<%
}
conn.close();
//if (intPage!=intPageCount){%> 
<A title=下一页
href="sort.jsp?sortid=<%=classid%>&page=<%=(intPage+1)%>"><FONT
face=Webdings>8</FONT></A>
<A title=最后页 href="sort.jsp?sortid=<%=classid%>&page=<%=intPageCount%>"><FONT
face=Webdings>:</FONT></A>
<%//}%></TD>
<TD>
<TABLE cellSpacing=0 cellPadding=0>
<FORM method="post">
<TBODY>
<TR> <TD>转到第<SELECT onchange="javascript:goUrl(this.options[this.selectedIndex].value);" name=select>
<% for (i=1;i<=j;i++){%>
<OPTION value=<%=i%> <%if(intPage==i)out.print("selected");%>><%=i%></OPTION>
<%}%>
</SELECT>页
</TD></TR></TBODY>
</FORM>
</TABLE>
</TD></TR>
</TBODY></TABLE>
</TD></TR>
</TBODY>
</TABLE>
</TD></TR>
</TBODY></TABLE></TD>
<TD vAlign=top width=4><IMG height=1
src="images/c.gif" width=4
border=0></TD></TR></TBODY>
</TABLE>
<script language="javascript">
function goUrl(page)
{ window.location.href="sort.jsp?sortid=<%=classid%>&page="+page; }
</script>
这个代码是下载站的一个典型的应用。如上面图2。笔者在一个报销系统中用structs也实现了类似的分页。结果想用到别的系统中,又要修改。原因是这个和具体的数据耦合。而且它们也本质的相同之处:每次分页时都是同一个地址(如a.jsp,a.do等)每次页面的显示都要处理总的记录数,每页的记录数,还有下一页、上一页,当前页等。不同是有的总的记录数叫做“软件数”、“文章总数”之类的。如果能把样式和分页的结构分离出来多好?那样每个网站都能用。
做过系统的人都会这样想,当然也会有人这样做。先不说那些国外的标签,就是我在网上就找了两个这样的标签。一个是jpager、一个是pagecontrol。当然这两个标签并不是很用。如果有时间还不如直接耦合数据。
先说说jpager(http://www.rosipay.com/1/22192.html能下载),它是根据用户到所有的数据传到页面,然后在页面上进行分页,具把相关的数据保存在会话中。这样就是至少有两个问题,一是如果数据非常大,有几百万条,能保存在会话中吗?还有如果一个系统有两种分页的的页面,这中间都用相同的属性名。那么就产生了会话中的分页错误。笔者在一个系统就是碰到这种会话错误。原来是不同页面的属性名相同。还有就是样式不能扩展。就只能是那么一种的样式。如果读者感兴趣,可以去看看源码。
对于pagecontrol,代码很简单,它采用MVC模式,每次传入页面需要的数据,它显示的样式有一定的可扩展性。因为它采用了MessageFormat.format()通过jsp文件来设定样式达到一定的扩展性。但是它对于如果不想采用MVC模式来说也是有一定的困难。
那么有什么好的分页标签?能不能把两者综合起来,笔者自己实现了一个这样的标签。望读者指出错误。

三分页标签

就分页来讲,后台传入的数据无非两种形式,一种就是把所有的数据都传进来,一种是传入本页所需要的数据。对于采用那种方式,那得由用户根据自己的具体业务情况来决定。如果读者对模式很熟悉的话。肯定会脱口而出:典型的策略模式。采用策略模式的好处就不用讲,环境和策略分离。用户需要的只是知道在什么时候用什么策略。



模式的牛人都说模式从接口开始,当然我只能听从牛人的话,首先定义了一个paginateDao接口。环境类只要和用paginateDao接口就可以,那么对于分页来讲,都需要什么方法?看看下面的paginate的UML图:


在分页中,不论是采用哪种传入数据的方式,都应该给页面提供下面几个值(有的值是页面传到Dao,如pagesize,但还是传入之后进行分页的操作。然后传出来):pageSize、dataSize也就是总的记录数,curragePage、totalPages、dataList。piginateDao就是围绕这个来操作。其下几个方法就是Boolean的判断当前页的下一页、上一页有效没有,还有是不是首页、末页等。这个paginateDao是借签ibatis中的分页技术。当然ibatis的分页技术是很差(笔者做了一个系统就是采用了它的分页,它采用分页技术是把所有的数据传进来)。
给了接口,那么就要实现的方法,两种传入数据的方式都有一些共同的特征。可以把它们提到一个父类(抽象类)中。给了它们的Default的实现。从模式的角度来看。abstractPaginate应该是dao和具体实现的中间的Defalult Adapter。这个缺省还定义了抽象方法。从chart 3下面来看,abstractPaginate是一个很标准的模版方法模式。还是先看看:



接下来就是给具体的实现,先说一下FrontPaginateList类吧。它是传入全部的数据,然后由getData(int page)来实现具体的分页。对于这种方法,用户可以根据具体情况将它的实例放在会话中,还是request中。在会话中的话,就会出现上面jpager出现的问题一样。如果放在request、page中,那么每次都要重新生成FrontPaginateList实例。这样好象性能也没有达到最优,如果读者有什么好的方法?Email: jljlpch@sohu.com., FrontPaginateList的实现很简单,当用户调用getData(int page)时,就会重新分页。重新分页就是pageSize、curragePage、totalPages、dataList这几个属性的改变。在这里有些读者可能不明白(源码)比如你调用setPageSize(int)并没有重新分页。其实不要像ibatis中实现的那样,因为你每次set 一些属性值,你最后都要落到getData(int page)方法上去。这是真正的调用分页。也只要在这时实现分页。可能不好理解。

对于AbstractAfterPaginate类,从名字就能看出是一个抽象类。为什么?因为我们的后台调取数据是随着参数变化而变化的。如(getUsers(int firstResult,int maxResult))而这个firstResult,maxResult是要从页面传来。也就是要通过PaginateDao来传到后台(数据库)去取数据。换句话说,当paginateDao的currentPage状态发生变化,那么后台的firstResult,maxResult的状态也要发生变化。这就是一个Observer pattern 的应用。在dom4j就有一个相同的应用。还有在Spring的HibernateDao中的回调函数也是同样的道理。当然就不用说SAX2中的应用啦。在这个例子中,我在想其实用Publish/Subscribe pattern这个词来形容更合适。说白了,观察者模式就是先定义一个抽象方法让用户去实现,这个方法是有状态的,所以是不能用参数的形式来传递的。用户可以采用匿名内部类或外部类的写法去生成一个对象。然后传给源对象。用匿名内部类包装 如下:
PageControlDao dao = new AbstractAfterPaginate (){
public List getPartialData(int start, int max) throws Exception {
return customerManager.getList(begin, max);
}
public int getRecordCount() throws Exception {
return customerManager.getListCount();
} }; H

外部类就是继承AbstractAfterPaginate 类如:
Public class UserListsPaginate extends AbstractAfterPaginate{
public List getPartialData(int start, int max) throws Exception {
return customerManager.getList(begin, max);
}
public int getRecordCount() throws Exception {
return customerManager.getListCount();
}}
这个UML现在应该是很简单的.



传完了数据,现在回到页面:对于页面上的标签应该怎么设计呢?在<C:foreach>类似的循环中都要传入类似的List方式的数据(Set,Map)等。我们要控制分页就是主要控制传入到<C:foreach>类似的循环中的数据。在jpager中,在<C:foreach>类似的循环中还增加一个itemtag。这样去控制循环的个数有点大烦。其实不如像JSTL中的标签一样,把数据传到<page: control var=”传出的数据List” datas=”传入的数据”>在 <C:foreach>类似的循环中用:<c: var=”item” items=”上面传出的数据”> 说实在,在这里用<displaytag>来处理循环是一个比较好的选择。而且<displaytag>分页的功能也不是很强,它实现分页也是传入全部的数据(?)。
在循环下面还有一个navigate。在这个地方,我们要根据具体要求去设定如:上一页、下一页、共多少页、多少记录之类的。表现不同。但内在的本质是一样。在页面上如果分页的话,也只要这个标签相呼应就可以。
知道要什么,那么应该怎么设计,能满足各种业务的需求呢。在displaytag中,用了大量的装饰模式。这里也可以用装饰模式。但这里只抽象一些公共的方法。



在UML中可以看出:只要传入实现AbstractPaginate的子类对象给item就可以。不管是哪一种数据传入方式,都可以把传入对象的当前页面的数据List给var变量。对于pagesize,没有指定,按default来定。PaginateControlTagSupport提供了所有的分页的一个一般的行为。对于每个需要特定的行为的分页可能继承这个类。比如,有的想采用读样式文件的方式去设定分页的页面,可以继承PaginateControlTagSupport类。这个API中也提供了一个default实现。



对于PaginateNavigateTagSupport类,和PaginateControlTagSupport一样。是所有的navigateTag的共同的抽象。不过actionpath传入时还是有点问题。这个和displayTag中的问题是一样。比如用brower的后退功能。不过在PaginateNavigateTagSupport的default 实现中。对于传入的actionpath,还有一点问题,如果传了参数,(如a.do?id=”a”)那么就有错误。当然可以加上一个条件去判断。笔者没有做。

不过,笔者还没有去测试所有的代码。(所有的代码仅参考!)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: