scala版本的grails分页代码


大家平时见过这样的分页么?见过的话知道怎么写么?没见过的话有思路么?

Previous 1 .. 3 4 (5) 6 7 8 .. 10 Next

就本人的话,在用grails时见到了这个强大的分页功能,但是对于里面的代码和思路一知半解。
今天心血来潮想要重新理解下原理,所以花了点时间分析了下实现,最终在回家的地铁上基本明白了其中的逻辑,特此写文分享。

在展示最终的scala代码之前,先说下分页中的几个概念,同时为了方便,这里取用grails中的变量名:

currentStep:当前页
lastStep:最后页
(firstStep):第一页,隐式常量
beginStep: 这种分页的起始页,可以认为是..之后的数字
endStep:这种分页的结束页,可以认为是第二个..之前的数字
maxSteps:控制这种分页的beginStep到endStep之间的数量的参数

有了这些参数之后,先看简单的部分:
previous:currentStep > firstStep的话就显示
1: beiginStep > firstStep的话就显示
第一个..:beginStep > (firstStep + 1)的话就显示
第二个..:endStep < (lastStep - 1)的话就显示 lastStep:endStep < lastStep的话就显示 next:currentStep < lastStep的话就显示 其实有了这些,简单的

First Previous Next Last

就可以做了,不过grails的这个分页强大的地方在于maxSteps、beginStep和endStep。以下是beginStep和endStep的groovy代码,也是一直困扰我的地方。

int beginstep = currentstep - Math.round(maxsteps / 2) + (maxsteps % 2) 
int endstep = currentstep + Math.round(maxsteps / 2) - 1

if (beginstep < firststep) { 
  beginstep = firststep 
  endstep = maxsteps
} 

if (endstep > laststep) {
  beginstep = laststep - maxsteps + 1 
  if (beginstep < firststep) { 
    beginstep = firststep 
  } 
  endstep = laststep 
}

这里在做什么操作呢?经过一段时间思考,我认为这段是在剪裁beginStep..endStep的区间。剪裁的理由显而易见,当前页可能就是第一页或者最后一页。遇到这种情况,beginStep肯定不能从负数开始,endStep页不能超过最后一页。但是grails这段代码是怎么剪裁的呢?

|************|       |************|
  |----------|  =>   |----------|

|************|     |************|      |**********|
|----------|    =>   |----------|  =>  |----------|

首先是判断左边,做向右移动操作,其次是判断右边,做向左移动操作,同时判断是否超过左边界了,如果超过了剪裁。
你可能会问,为什么判断右边时剪裁了而判断左边时没剪?个人认为可能是因为“重复”而被去掉了,那么是否会出问题呢?看上图,需要剪裁的场景没出问题。
另外个人认为判断左边时剪裁可以做,多做没啥问题,因为之后直接跳过右边的判断了。还有需要注意的是判断左边时没办法判定那种左边在范围内,右边在范围外的情况,所以单独的右边界判断是必须的。

虽然这段逻辑运行起来没太大问题,但是有点难以理解。我在用scala模拟时用的是另一个方法:

private def trim(beginStep: Int, endStep: Int, lastSteps: Int): (Int, Int) = {
  if(beginStep >= FIRST_STEP) {
    if(endStep <= lastSteps) (beginStep, endStep)
    else (scala.math.max(FIRST_STEP, beginStep - endStep + lastSteps), lastSteps)
  } else {
    if(endStep > lastSteps) (FIRST_STEP, lastSteps)
    else (FIRST_STEP, scala.math.min(lastSteps, endStep + FIRST_STEP - beginStep)) 
  }
}

我把剪裁分为四种情况(左边和左边界判定有两种情况,右边和右边界判定有两种情况),剪裁结果如下

左边判定 右边判定 处理
firstStep <= beginStep endStep <= lastStep 无剪裁
firstStep <= beginStep endStep > lastStep 向左边移动,存在剪裁可能
firstStep > beginStep endStep > lastStep 全剪裁
firstStep > beginStep endStep <= lastStep 向右边移动,存在剪裁可能

虽然比起前面代码可能要多一些,不过更直观。
最后是scala代码:

object Paginate {

  def main(args: Array[String]): Unit = {
    if(args.length < 3) println("usage <currentStep> <lastStep> <maxSteps>")
    else paginate(args(0).toInt, args(1).toInt, args(2).toInt)
  }

  private final val FIRST_STEP = 1

  private def paginate(currentStep: Int, lastStep: Int, maxSteps: Int): Unit = {
    val beginStep = currentStep - maxSteps / 2 + 1 - (maxSteps % 2)
    val endStep = currentStep + maxSteps / 2

    val (fromStep, toStep) = trim(beginStep, endStep, lastStep)
    if(currentStep > FIRST_STEP) print("Previous ")
    if(fromStep > FIRST_STEP) print("1 ")
    if(fromStep > (FIRST_STEP + 1)) print(".. ")
    (fromStep to toStep).foreach{step =>
      if(step == currentStep) print("("+ step + ") ")
      else print(step + " ")
    }
    if(endStep < lastStep - 1) print(".. ")
    if(endStep < lastStep) print(lastStep + " ")
    if(currentStep < lastStep) print("Next")

    println
  }

  private def trim(beginStep: Int, endStep: Int, lastSteps: Int): (Int, Int) = {
    if(beginStep >= FIRST_STEP) {
      if(endStep <= lastSteps) (beginStep, endStep)
      else (scala.math.max(FIRST_STEP, beginStep - endStep + lastSteps), lastSteps)
    } else {
      if(endStep > lastSteps) (FIRST_STEP, lastSteps)
      else (FIRST_STEP, scala.math.min(lastSteps, endStep + FIRST_STEP - beginStep)) 
    }
  }

}
,