Skip to content
This repository has been archived by the owner on Jul 28, 2022. It is now read-only.

feilongDisplay pager

feilong edited this page Oct 18, 2016 · 35 revisions

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 公司倡导、许多公司参与一起建立的一种动态网页技术标准,参见 http://www.oracle.com/technetwork/java/javaee/jsp/index.html
properties properties文件是java中的一种配置文件,主要用于表达配置信息,文件类型为*.properties,格式为文本文件,文件的内容是格式是"键=值"的格式
I18n 来源是英文单词 internationalization 的首末字符i和n,18为中间的字符数 ,是 国际化 的简称.
Ajax Asynchronous Javascript And XML(异步JavaScript和XML),是指一种创建交互式网页应用的网页开发技术

2. 特性介绍

  1. 支持 国际化

  2. 支持 Ajax 分页,see Ajax 分页使用步骤

  3. 分页页码,当前页码永远居中

    center

  4. 内置页码输入文本框,支持快速跳转

    如可实现以下功能: goto

  5. 支持类似于淘宝最大分页码100 这样的控制

    如可实现以下功能:

    maxsize

    见参数 maxShowPageNo

  6. 分页页码支持根据页码数字动态显示页码个数

    比如当当前页码是1001页显示6个页码,当当前页码是101,显示8个页码

  7. 自动识别是否是forwoad 连接

  8. 经过大型项目检验,通用严格的安全扫描测试

    Esprit,Nike,Underarmour,blanja等大型项目都有使用该标签,并通过严格的安全扫描测试

  9. 支持自定义velocity分页模版

    自定义vm部分

  10. 支持皮肤切换

    内置26种皮肤 (需要引入feilong-pager.css), 如果您的网站有自己特殊的样式,可以重新修改vm模板结构或者按照皮肤结构自定义css特殊的皮肤(建议选择后者) 内置皮肤样式参见附录部分

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 

带皮肤的页面效果:

result

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 国际化语言,支持 java.util.Locale 或 String 类型的实例 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']} 但是如果不切换语言那么取不到值

5.3.1 临时方案:

	<%@ 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}" 
		.../>

5.3.2 正常方案(使用 feilong spring el ):

注:since 1.7.2

	<%@ taglib prefix="flsp" uri="http://java.feilong.com/el-spring"%>

	<feilongDisplay:pager 
		...
		locale="${flsp:getLocale()}" 
		.../>

5.3.3 支持 String 类型的实例:

注:since 1.7.2

	<%@ taglib prefix="flsp" uri="http://java.feilong.com/el-spring"%>

	<feilongDisplay:pager 
		...
		locale="zh_CN" 
		.../>

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

render

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是 皮肤名称,你也可以根据这样的层级结构,建个自己的皮肤

下图是内置皮肤 默认样式,您可以指定皮肤名字使用

css

8.2 默认的feilong-default-pager.vm代码

	#**
	    这是个示例或者默认的模板,通过这个模板,可以看到哪些变量可以使用
	    每个商城可以使用这个模板,也可以自定义模板来使用
		
		该VM 可以取到两个变量:
		
	    	pagerVMParam	:	包含各种显示数字/链接 参数
	    	i18nMap			:	包含国际化信息
		
	    @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

i18n

Clone this wiki locally