-
Notifications
You must be signed in to change notification settings - Fork 11
feilongDisplay pager
#1 文档说明
##1.1 概述 分页功能是商城乃至整个互联网应用最常用的功能之一,好的分页标签或者方案可以大大缩短工程师的开发时间,提高生产力.
##1.2 阅读对象 面向具有一定的网站开发能力,了解 JAVA、JSP 等开发语言的网站开发、维护和管理人员
##1.3 专业术语
术语 | 解释 |
---|---|
taglib | 自定义标签 |
pager | 分页 |
velocity | 来自apache基金会的一个基于java的模板引擎(template engine),参见 http://velocity.apache.org/ |
Jsp | 全名为Java Server Pages,是由Sun Microsystems公司倡导、许多公司参与一起建立的一种动态网页技术标准,参见 jsp index |
properties | properties文件是java中的一种配置文件,主要用于表达配置信息,文件类型为*.properties,格式为文本文件,文件的内容是格式是"键=值"的格式 |
I18n | 来源是英文单词 internationalization的首末字符i和n,18为中间的字符数)是“国际化”的简称. |
Ajax | 即“Asynchronous Javascript And XML”(异步JavaScript和XML),是指一种创建交互式网页应用的网页开发技术 |
#2 特性介绍
##2.1 支持皮肤切换 内置26种皮肤 (需要引入feilong-pager.css), 如果您的网站有自己特殊的样式,可以重新修改vm模板结构或者按照皮肤结构自定义css特殊的皮肤(建议选择后者) 内置皮肤样式参见附录部分
##2.2 支持自定义velocity分页模版 见自定义vm部分
##2.3 自动识别是否是forwoad 页面连接
##2.4 分页页码,当前页码永远居中
##2.5 分页页码支持根据页码数字自动显示分页码个数 见参数说明里面的 maxIndexPages参数
##2.6 支持国际化 since (1.0.5 new feature)
##2.7 内置文本框页码输入快速跳转 since (1.0.5 new feature)
如可实现以下功能:
##2.8 支持类似于淘宝最大分页码100 这样的控制 如 可实现以下功能:
见参数 maxShowPageNo (1.0.5 new feature)
##2.9 经过大型项目检验,通用严格的安全扫描测试 Esprit,Nike,Underarmour,blanja等大型项目都有使用该标签,并通过严格的安全扫描测试
##2.10 支持Ajax分页 since (1.4.0 new feature)
#3 演示
##3.1 演示 示例: 当前页面路径
http://127.0.0.1:8088/pager?page=8&name=%E9%A3%9E%E5%A4%A9%E5%A5%94%E6%9C%88
带皮肤的页面效果:
#4 跳转分页使用步骤
##4.1 步骤1.JSP引用自定义标签
<%@ taglib prefix="feilongDisplay" uri="http://java.feilong.com/tags-display"%>
##4.2 步骤2.JSP使用自定义标签
###4.2.1 精简参数写法
<feilongDisplay:pager count="${pagination.count}"/>
此时其余参数缺省,均使用默认值
###4.2.2 完整参数写法
<feilongDisplay:pager count="${pagination.count}"
charsetType="utf-8"
maxIndexPages="3"
pageParamName="page"
pageSize="10"
locale="${requestScope['org.springframework.web.servlet.i18n.CookieLocaleResolver.LOCALE']}"
vmPath="velocity/feilong-default-pager.vm"
skin="scott"
pagerHtmlAttributeName="feilongPagerHtml1" />
每个参数的含义,请参见下面参数表格部分
#5 feilongDisplay:pager标签参数说明
##5.1 Required Parameters:
参数 | 说明 | 类型 | Since | 默认值 | 是否支持动态参数 |
---|---|---|---|---|---|
count | 数据总数 | Integer | 1.0 | - | true |
##5.2 Optional Parameters:
参数 | 说明 | 类型 | Since | 默认值 | 是否支持动态参数 |
---|---|---|---|---|---|
pageSize | 每一页显示多少个数据 | Integer | 1.0 | 20 | true |
vmPath | 分页解析的vm模版 | String | 1.0 | velocity/feilong-default-pager.vm | true |
skin | 皮肤,内置24种常用皮肤 *需要引入feilong-pager.css |
String | 1.0 | digg | true |
pageParamName | url分页页码参数 | String | 1.0 | pageNo | true |
maxIndexPages(deprecated) | 最多显示多少个导航页码, 如果maxIndexPages 是0或者null,那么根据allpageNo,采用自动调节长度功能, 因为,如果页码大于1000的时候,如果还是10页码的显示(1001,1002,1003,1004,1005,1006,1007,1008,1009,1010) 显示上面会很长 ,会打乱页面布局 • 当大于1000的页码 显示6个 即 1001,1002,1003,1004,1005,1006 类似于这样的 • 当大于100的页码 显示8个 即 101,102,103,104,105,106,107,108 类似于这样的 • 其余,默认显示10条 deprecated 参数名字取得不好,在将来的版本会更改替换,不建议使用这个参数 |
Integer | 1.0 | - | true |
locale | 国际化语言 | Locale | 1.0.5 | request.getLocale() | true |
charsetType | url编码 | String | 1.0.5 | UTF-8 | true |
pagerHtmlAttributeName | vm被解析出来的文本,会被存在在这个变量中,作用域为pageContext,以便重复使用, 比如某些页面,上面下面都要显示同样的分页,方便用户操作此外,此变量名称允许变更,以便实现,同一页页面不同功能的的分页. |
String | 1.0.5 | feilongPagerHtml | true |
maxShowPageNo | 最多显示页数,(-1或者不设置,默认显示所有页数)类似于淘宝不管搜索东西多少,最多显示100页 这是一种折中的处理方式,空间换时间。 数据查询越往后翻,对服务器的压力越大,速度越低,而且从业务上来讲商品质量也越差,所以就没有必要给太多了。 新浪微博的时间轴也只给出了10页,同样的折中处理。 |
Integer | 1.0.5 | -1 | true |
##5.3 关于locale
locale,如果使用 ${requestScope['org.springframework.web.servlet.i18n.CookieLocaleResolver.LOCALE']} 但是如果不切换语言那么取不到值
临时方案:
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%>
<spring:eval var="locale" expression="T(org.springframework.web.servlet.support.RequestContextUtils).getLocale(pageContext.request)"></spring:eval>
<feilongDisplay:pager
...
locale="${locale}"
.../>
#6 自定义velocity pager分页模板
默认的velocity\feilong-default-pager.vm ,很大程度上已经包含了大部分需求功能,但是可能实际项目中有特殊的需求,因此可以使用自定义pager vm 模板
在Pager vm模板中可以使用的参数有 6.1 PagerVMParam对象 以及 6.2 i18nMap对象 (国际化使用的文案)
##6.1 PagerVMParam对象
变量名称 | 说明 | 类型 | since | 示例数据 |
---|---|---|---|---|
totalCount | 数据总数 | int | 1.0 | 1024 |
skin | 设定的皮肤 | String | 1.0 | digg |
currentPageNo | 当前页码 | int | 1.0 | 12 |
allPageNo | 总页数 | int | 1.0 | 103 |
prePageNo | 上一页页码 | int | 1.4.0 | 11 |
nextPageNo | 下一页页码 | int | 1.4.0 | 13 |
pagerType | 分页类型 | PagerType | 1.4.0 | REDIRECT |
preUrl | 上一页链接 | String | 1.0 | /pager?page=7&name=%E9%A3%9E%E5%A4%A9%E5%A5%94%E6%9C%88 |
nextUrl | 下一页链接 | String | 1.0 | /pager?page=9&name=%E9%A3%9E%E5%A4%A9%E5%A5%94%E6%9C%88 |
firstUrl | 第一页的链接 | String | 1.0 | /pager?page=1&name=%E9%A3%9E%E5%A4%A9%E5%A5%94%E6%9C%88 |
lastUrl | 最后一页的链接 | String | 1.0 | /pager?page=1000&name=%E9%A3%9E%E5%A4%A9%E5%A5%94%E6%9C%88 |
hrefUrlTemplate | 链接的模板,以便前端js替换 | 1.0 | /pager?page=-8888888&name=%E9%A3%9E%E5%A4%A9%E5%A5%94%E6%9C%88 | |
startIteratorIndex | 开始迭代索引编号 | int | 1.0 | 7 |
endIteratorIndex | 结束迭代索引编号 | int | 1.0 | 9 |
iteratorIndexMap | 循环 迭代索引map key是编号,value 是页面链接 | LinkedHashMap<Integer, String> | 1.0 | "7": "/pager?page=7&name=%E9%A3%9E%E5%A4%A9%E5%A5%94%E6%9C%88", "8": "/pager?page=8&name=%E9%A3%9E%E5%A4%A9%E5%A5%94%E6%9C%88", "9": "/pager?page=9&name=%E9%A3%9E%E5%A4%A9%E5%A5%94%E6%9C%88" |
pageParamName | 分页参数名称 | String | 1.0.6 | pageNo |
pagerUrlTemplate | 分页链接模板 | PagerUrlTemplate | 1.0.6 | "pagerUrlTemplate": { "href": "/pager?page=-88888888&name=%E9%A3%9E%E5%A4%A9%E5%A5%94%E6%9C%88", "templateValue": -88888888 }, |
##6.2 i18nMap对象 (国际化使用的文案) 将messages/feilong-pager.properties配置文件的key和value转成了i18nMap对象,可以在vm模板中直接使用配置文件中的参数
示例参见 8.3.messages/feilong-pager.properties #7 Ajax分页使用步骤
##7.1 步骤1 JS发送Ajax分页请求
function loadPageData(pageNo) {
console.clear();
var url = _contextPath + "/item/commentAjax/" + itemId + ".json";
console.log(url);
loxia.asyncXhrPost(url, {
"pageNo" : pageNo
}, {
success : function(pagerAndContent) {
console.log(pagerAndContent);
$("#commentPager").html(pagerAndContent.content);
//do your logic,maybe use handlebars render result
}
}, {
error : function(data) {
console.log(data);
}
});
}
##7.2 步骤2 Controller接收请求并处理
@ClientCache(value = TimeInterval.SECONDS_PER_MINUTE * 30)
@ResponseBody
@RequestMapping(value={"/item/commentAjax/{itemId}.json"},method=RequestMethod.POST,headers= HEADER_WITH_AJAX_SPRINGMVC)
public CommentPagerAndContent doHandler2(@PathVariable("itemId") Long itemId,HttpServletRequest request){
Pagination<RateCommand> rateCommandPagination = getRateCommandPagination(itemId, request);
if (Validator.isNullOrEmpty(rateCommandPagination)){
return null;
}
Pager<CommentViewCommand> pager = toCommentViewCommandPager(request, rateCommandPagination);
//***********************************************************************************
CommentPagerAndContent commentPagerAndContent = new CommentPagerAndContent();
commentPagerAndContent.setPager(pager);
commentPagerAndContent.setContent(getPagerContent(Integer.parseInt("" + pager.getCount()), request));
commentPagerAndContent.setItemRateFitTypeAndValueMap(commentManager.calcSizeCommentScale(itemId));
return commentPagerAndContent;
}
private Pagination<RateCommand> getRateCommandPagination(Long itemId,HttpServletRequest request){
Page page = constructPage(request);
Sort[] sorts = Sort.parse("tpir.create_time desc");
Pagination<RateCommand> rateCommandPagination = sdkItemManager.findItemRateListByItemId(page, itemId, sorts);
if (Validator.isNullOrEmpty(rateCommandPagination) || Validator.isNullOrEmpty(rateCommandPagination.getItems())){
LOGGER.info("when itemId:[{}],rateCommandPagination isNullOrEmpty or isNullOrEmpty(rateCommandPagination.getItems()) ", itemId);
return null;
}
if (LOGGER.isDebugEnabled()){
LOGGER.debug("rateCommandPagination:{}", JsonUtil.format(rateCommandPagination));
}
return rateCommandPagination;
}
private Pager<CommentViewCommand> toCommentViewCommandPager(HttpServletRequest request,Pagination<RateCommand> rateCommandPagination){
List<RateCommand> items = rateCommandPagination.getItems();
String itemCode = items.get(0).getItemCode();
//理论上来说,只有 下架或者 上架状态的商品 才会进行load 评论数据
ResultAndViewCommand buildWithCache = stdItemBuilder.buildWithCache(itemCode);
StdItemViewCommand stdItemViewCommand = (StdItemViewCommand) buildWithCache.getViewCommand();
OrderLineSalesPropertyNameAndValueMapResolver orderLineSalesPropertyNameAndValueMapResolver = new OrderLineSalesPropertyNameAndValueMapResolverImpl(
sdkOrderLineManager,
stdItemViewCommand);
List<CommentViewCommand> commentViewCommandList = (List<CommentViewCommand>) CollectionUtils
.collect(items, new CommentViewCommandTransformer(orderLineSalesPropertyNameAndValueMapResolver));
Pager<CommentViewCommand> pager = new Pager<CommentViewCommand>(
getPageNoParam(request),
perPageSize(),
Integer.parseInt("" + rateCommandPagination.getCount()));
pager.setItemList(commentViewCommandList);
if (LOGGER.isDebugEnabled()){
LOGGER.debug(JsonUtil.format(pager));
}
return pager;
}
private String getPagerContent(Integer totalCount,HttpServletRequest request){
PagerParams pagerParams = new PagerParams(totalCount, PagerType.NO_REDIRECT);
pagerParams.setVmPath("velocity/pdp-pager-for-comment.vm");//评论的分页模板
pagerParams.setPageSize(perPageSize());
pagerParams.setCurrentPageNo(getPageNoParam(request));
return PagerBuilder.buildPagerContent(pagerParams);
}
上面方法中,关键参数是 PagerType.NO_REDIRECT
,标识渲染出来的html代码不需要跳转
##7.3 步骤3 解析Ajax返回结果 拿到Ajax 返回结果后, 你可以使用
$("#commentPager").html(pagerAndContent.content);
将分页结果内容渲染到某div 或者p标签容器内
##7.4 原理解析
当PagerType
参数类型设置为 PagerType.NO_REDIRECT
,解析出来的分页的链接将使用 javascript:void(0);
替代
下面是渲染出来的html
##7.5 步骤4 点击页码,取到分页码值发送新的Ajax请求
当点击分页码,需要取到分页码的数值,发送新的Ajax请求到controller,可以在上面的原理图中看出,每个非当前页码上的A标签均有一个pageNoValue
自定义属性,因此,你可以使用下面的方式取到该值,发送新页码Ajax请求:
$("#commentPager a:not(.btn-disabled)").live("click", function() {
loadPageData($(this).attr("pageNoValue"));
});
##7.6 关于自定义Ajax velocity/pdp-pager-for-comment.vm 分页模板
##第一页不显示首页和上一页
#if(1 != ${pagerVMParam.currentPageNo})
<a class="next" pageNoValue="${pagerVMParam.prePageNo}" title="${i18nMap.get('feilong-pager.text.goto.pre')}" href="${pagerVMParam.preUrl}">${i18nMap.get('feilong-pager.text.prev')}</a>
##// 如果导航编号里面没有首页 则添加首页
##// 导航里面 是否有第一页, 如果从开始1索引 则已经包含的首页包含
#if (${pagerVMParam.startIteratorIndex} != 1)
<a pageNoValue="1" title="${i18nMap.get('feilong-pager.text.goto.first')}" href="${pagerVMParam.firstUrl}">1</a>
#end
#end
##开始迭代索引不等于1,并且开始迭代索引不等于2,显示3点
#if (${pagerVMParam.startIteratorIndex} != 1 && ${pagerVMParam.startIteratorIndex} != 2)
<span class="color_666">...</span>
#end
##循环所有的页码 显示导航编号
#foreach( ${entry} in ${pagerVMParam.iteratorIndexMap.entrySet()} )
##当前 直接是数字编号
#if(${entry.key}==${pagerVMParam.currentPageNo})
<a class="btn-disabled" href="${entry.value}">${entry.key}</a>
#else
##不是当前页面
<a pageNoValue="${entry.key}" title="${entry.key}${i18nMap.get('feilong-pager.text.pager')}" href="${entry.value}">${entry.key}</a>
#end
#end
##如果最后一个迭代索引不等于总页数,且最后一个迭代索引不等于总也是-1,那么 显示3点
#set($allPageNoTo1=${pagerVMParam.allPageNo} - 1)
#if (${pagerVMParam.endIteratorIndex} != ${pagerVMParam.allPageNo} && ${pagerVMParam.endIteratorIndex} != $allPageNoTo1)
<span class="color_666">...</span>
#end
##最后一页不显示下一页和末页
#if(${pagerVMParam.allPageNo}!=${pagerVMParam.currentPageNo})
## 如果导航编号里面没有尾页 则添加尾页
##导航里面是否有最后一页, 如果结束的位置是allPageNo 则已经包含的尾页
#if(${pagerVMParam.endIteratorIndex} != ${pagerVMParam.allPageNo})
##跳转到最后一页
<a pageNoValue="$!{pagerVMParam.allPageNo}" title="${i18nMap.get('feilong-pager.text.goto.last')}" href="${pagerVMParam.lastUrl}">$!{pagerVMParam.allPageNo}</a>
#end
##跳转到下一页
<a class="next" pageNoValue="${pagerVMParam.nextPageNo}" title="${i18nMap.get('feilong-pager.text.goto.next')}" href="${pagerVMParam.nextUrl}">${i18nMap.get('feilong-pager.text.next')}</a>
#end
#8 附录
##8.1 内置皮肤样式说明 如:
div.digg a {border:1px solid #aad;padding:2px 5px;margin:2px;color:#009;text-decoration:none}
div.digg a:link {color:#009;}
div.digg a:visited {color:#009;}
div.digg a:hover {border:1px solid #009;color:#000;}
div.digg a:active {border:1px solid #009;color:#000;}
div.digg span.current {border:1px solid #009;padding:2px 5px;margin:2px;font-weight:bold;color:#fff;background:#009}
div.digg span.disabled {border:1px solid #999;padding:2px 5px;margin:2px;color:#777;}
其中 digg
是 皮肤名称,你也可以根据这样的层级结构,建个自己的皮肤
下图是内置皮肤 默认样式,您可以指定皮肤名字使用
##8.2 默认的feilong-default-pager.vm代码
#**
这是个示例或者默认的模板,通过这个模板,可以看到哪些变量可以使用
每个商城可以使用这个模板,也可以自定义模板来使用
该VM 可以取到两个变量:
pagerVMParam : 包含各种显示数字/链接 参数
i18nMap : 包含国际化信息
@author <a href="mailto:venusdrogon@163.com">金鑫</a>
@version 1.0.0 2010-2-3 下午01:03:14
@version 1.0.5 May 3, 2014 1:44:08 PM
@since 1.0.0
*#
<div class="div_feilongPager ${pagerVMParam.skin}" style="text-align:left">
##总数
<span class="span_pagerInfo">${i18nMap.get('feilong-pager.text.totalCount')}${pagerVMParam.totalCount}</span>
##当前第${pagerVMParam.currentPageNo}页/共${pagerVMParam.allPageNo}页
<span class="span_pagerInfo" title="${i18nMap.get('feilong-pager.text.current')}${pagerVMParam.currentPageNo}${i18nMap.get('feilong-pager.text.pager')}/${i18nMap.get('feilong-pager.text.total')}${pagerVMParam.allPageNo}${i18nMap.get('feilong-pager.text.pager')}">${pagerVMParam.currentPageNo}/${pagerVMParam.allPageNo}</span>
##第一页不显示首页和上一页
#if(1 != ${pagerVMParam.currentPageNo})
<a pageNoValue="${pagerVMParam.prePageNo}" title="${i18nMap.get('feilong-pager.text.goto.pre')}" href="${pagerVMParam.preUrl}">${i18nMap.get('feilong-pager.text.prev')}</a>
##// 如果导航编号里面没有首页 则添加首页
##// 导航里面 是否有第一页, 如果从开始1索引 则已经包含的首页包含
#if (${pagerVMParam.startIteratorIndex} != 1)
<a pageNoValue="1" title="${i18nMap.get('feilong-pager.text.goto.first')}" href="${pagerVMParam.firstUrl}">1</a>
#end
#end
##开始迭代索引不等于1,并且开始迭代索引不等于2,显示3点
#if (${pagerVMParam.startIteratorIndex} != 1 && ${pagerVMParam.startIteratorIndex} != 2)
<span class="color_666">...</span>
#end
##循环所有的页码 显示导航编号
#foreach( ${entry} in ${pagerVMParam.iteratorIndexMap.entrySet()} )
##当前 直接是数字编号
#if(${entry.key}==${pagerVMParam.currentPageNo})
<span class="current">${entry.key}</span>
#else
##不是当前页面
<a pageNoValue="${entry.key}" title="${i18nMap.get('feilong-pager.text.goto')}${entry.key}${i18nMap.get('feilong-pager.text.pager')}" href="${entry.value}">${entry.key}</a>
#end
#end
##如果最后一个迭代索引不等于总页数,且最后一个迭代索引不等于总也是-1,那么 显示3点
#set($allPageNoTo1=${pagerVMParam.allPageNo} - 1)
#if (${pagerVMParam.endIteratorIndex} != ${pagerVMParam.allPageNo} && ${pagerVMParam.endIteratorIndex} != $allPageNoTo1)
<span class="color_666">...</span>
#end
##最后一页不显示下一页和末页
#if(${pagerVMParam.allPageNo}!=${pagerVMParam.currentPageNo})
## 如果导航编号里面没有尾页 则添加尾页
##导航里面是否有最后一页, 如果结束的位置是allPageNo 则已经包含的尾页
#if(${pagerVMParam.endIteratorIndex} != ${pagerVMParam.allPageNo})
##跳转到最后一页
<a pageNoValue="$!{pagerVMParam.allPageNo}" title="${i18nMap.get('feilong-pager.text.goto.last')}" href="${pagerVMParam.lastUrl}">$!{pagerVMParam.allPageNo}</a>
#end
##跳转到下一页
<a pageNoValue="${pagerVMParam.nextPageNo}" title="${i18nMap.get('feilong-pager.text.goto.next')}" href="${pagerVMParam.nextUrl}">${i18nMap.get('feilong-pager.text.next')}</a>
#end
<input type="text" value="${pagerVMParam.currentPageNo}" class="feilongGotoInput" pagerUrlTemplateHref="${pagerVMParam.pagerUrlTemplate.href}" templateValue="${pagerVMParam.pagerUrlTemplate.templateValue}" pageParamName="${pagerVMParam.pageParamName}"/>/${pagerVMParam.allPageNo}<button value="go">Go</button>
</div>
<script type="text/javascript">
$(function() {
##回车事件
$(".feilongGotoInput").keydown( function() {
if (event.keyCode == 13) {
var pageNoValue=$(this).val();
if(""!=pageNoValue&&pageNoValue>0){
var templateValue=$(this).attr("templateValue");
var pageParamName=$(this).attr("pageParamName");
var pagerUrlTemplateHref=$(this).attr("pagerUrlTemplateHref");
location.href=pagerUrlTemplateHref.replace(pageParamName+"="+templateValue,pageParamName+"="+pageNoValue);
}
}
});
});
</script>
##8.3 messages/feilong-pager.properties