08 March 2011

说来惭愧,从来没写过分页的代码,知道是怎么回事儿,但没干过总归差了这么一点儿。

MongoDB和传统的数据库类似,提供了skip和limit,前者用来跳过一批记录,后者用来选择多少条。怎么用这里有很详细的解释:http://www.mongodb.org/display/DOCS/Advanced+Queries,就不多说了。重点讨论Lift里面怎么用。

分页通常有这么几个条件:排序字段、排序方式(升序还是降序)、每页显示条数、当前页面。这些参数可以通过URL传递,譬如:sort-by=linkId&sort-order=-1&perpage=3&page=2,然后HTTP GET发送。

对于这种per request的参数,当然可以用的时候直接从著名的“S”里面取,也可以用RequestVar:

1
2
3
4
5
object page extends RequestVar[Int](S.param("page").openOr("1").toInt)
object perpage extends RequestVar[Int](S.param("perpage").openOr("10").toInt)
object offset extends RequestVar[Int]((page - 1) * perpage)
object sortBy extends RequestVar[String](S.param("sort-by").openOr(ShortenedUrl.linkId.name))
object sortOrder extends RequestVar[Int](S.param("sort-order").openOr("-1").toInt)

这其中offset表示当前页的起始条目,-1表示降序,默认排序字段为linkId。

只有这些还不过瘾,我们再加上搜索条件:

  1. 关键字
  2. 点击次数
1
2
3
4
object search extends RequestVar[String](S.param("search").openOr(""))
object searchIn extends RequestVar[String](S.param("search-in").openOr(ShortenedUrl.originUrl.name))
object clickFilter extends RequestVar[String](S.param("click-filter").openOr("gte"))
object clickLimit extends RequestVar[String](S.param("click-limit").openOr(""))

默认搜索originUrl,点击次数大于等于某个值。

有过数据库搜索设计经验的人一般都会这么干:

1
select * from table where 1=1

然后在后面拼搜索条件:

1
and originUrl like '%google%' and clickCount >= 10 sort by linkId skip 2 limit 10

我们如法炮制,不过稍微改变一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
object clickObject extends RequestVar[JObject](
    if (clickLimit.isEmpty) JObject(Nil)
    else (ShortenedUrl.clickCount.name -> (("$" + clickFilter) -> clickLimit.is.toInt))
)
 
object searchObject extends RequestVar[JObject](
  if (search.isEmpty) JObject(Nil)
  else ("$where" -> ("this." + searchIn + ".indexOf('" + search + "') != -1"))
)
 
object findOptions extends RequestVar[List[FindOption]](
  List(Skip(offset), Limit(perpage))
)
 
shortenedUrl.findAll(clickObject.is ~ searchObject.is,
    (sortBy.is -> sortOrder.is), findOptions: _*)

如果没有搜索条件,返回Nil表示空的List;否则(x -> y)构建一个JObject(这里是Scala的语法糖衣,背后发生了很多);$where那里纯粹是MongoDB特有的,根本就是js;Skip和Limit跟SQL的含义完全相同。这样对最后一个statement的解读也就很自然了。

数据的事情搞定了,剩下就是页面处理,Lift再次表示了自己的牛逼:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
    def generateNav: NodeSeq = {
      def generateHref(page: Int) = "/?search=" + search + "&sort-by=" + sortBy + "&sort-order=" + sortOrder +
              "&search-in=" + searchIn + "&click-filter=" + clickFilter + "&click-limit=" + clickLimit +
              "&perpage=" + perpage + "&page=" + page
 
      val left = if (page.is != 1) <a title="{"«" href="{generateHref(page">«</a>
      else Nil
 
      val pages = (1 to totalPages).toList.map {
        x =>
          if (x == page.is)
            <strong>
              {"[" + x + "]"}
            </strong>
          else
            <a title="{"Page" href="{generateHref(x)}">
              {x}
            </a>
      }
 
      val right = if (page.is != totalPages) <a title="{"Go" href="{generateHref(page.is">»</a>
      else Nil
 
      left ++ pages ++ right
    }

我写得比较烂,也就这么几行。效果就是这样:

 



blog comments powered by Disqus