alvinalexander.com | career | drupal | java | mac | mysql | perl | scala | uml | unix  

Lift Framework example source code file (Paginator.scala)

This example Lift Framework source code file (Paginator.scala) is included in the DevDaily.com "Java Source Code Warehouse" project. The intent of this project is to help you "Learn Java by Example" TM.

Java - Lift Framework tags/keywords

boolean, c, c, int, list, long, long, nodeseq, nodeseq, paginator, sortstate, string, text, text

The Lift Framework Paginator.scala source code

/*
 * Copyright 2008-2011 WorldWide Conferencing, LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package net.liftweb
package http

import xml.{NodeSeq, Text}

import common.Loggable
import util.Helpers._
import S.{?, ??}


/**
 * Base class for things that require pagination. Implements a contract 
 * for supplying the correct number of browsable pages etc
 *
 * @tparam T the type of item being paginated
 * @author nafg and Timothy Perrett
 */
trait Paginator[T] extends Loggable {
  /**
   * The total number of items
   */
  def count: Long
  /**
   * How many items to put on each page
   */
  def itemsPerPage = 20
  /**
   * The record number this page starts at. Zero-based.
   */
  def first = 0L
  /**
   * The items displayed on the current page
   */
  def page: Seq[T]
  /**
   * Calculates the number of pages the items will be spread across
   */
  def numPages =
    (count/itemsPerPage).toInt +
  (if(count % itemsPerPage > 0) 1 else 0)
  /**
   * Calculates the current page number, based on the value of 'first.'
   */
  def curPage = (first / itemsPerPage).toInt
  /**
   * Returns a list of page numbers to be displayed in 'zoomed' mode, i.e.,
   * as the page numbers get further from the current page, they are more sparse.
   */
  def zoomedPages = (
    List(curPage - 1020, curPage - 120, curPage - 20) ++
    (curPage-10 to curPage+10) ++
    List(curPage + 20, curPage + 120, curPage + 1020)
  ) filter { n=>
            n >= 0 && n < numPages
          }
}

/**
 * In many situations you'll want to sort things in your paginated view. 
 * <code>SortedPaginator is a specialized paginator for doing such tasks.
 * 
 * T: The type of the elements, accessed via def page within the listing snippet
 * C: The type of the columns, used to specify sorting
 *
 * @author nafg and Timothy Perrett
 */
trait SortedPaginator[T, C] extends Paginator[T] {
  /**
   * Pair of (column index, ascending)
   */
  type SortState = (Int, Boolean)
    /**
     * The sort headers: pairs of column labels, and column identifier objects of type C.
     */
    def headers: List[(String, C)]

  protected var _sort = (0, true)
    /**
     * Get the current sort state: Pair of (column index, ascending?)
     */
    def sort: SortState = _sort
  /**
   * Set the current sort state: Pair of (column index, ascending?)
   */
  def sort_=(s: SortState) = _sort = s

  /**
   * Returns a new SortState based on a column index.
   * If the paginator is already sorted by that column, it
   * toggles the direction; otherwise the direction is ascending.
   * Note that this method does not alter the sort state in the
   * paginator; it only calculates the direction toggle.
   * Example usage:
   * sortedPaginator.sort = sortedPaginator.sortedBy(columns.indexOf(clickedColumn))
   */
  def sortedBy(column: Int): SortState = sort match {
    case (`column`, true) =>  // descending is only if it was already sorted ascending
      (column, false)
      case _ =>
        (column, true)
  }
}

/**
 * This is the paginator snippet. It provides page
 * navigation and column sorting links.
 * View XHTML is as follows: 
 * nav prefix (prefix is configurable by overriding def navPrefix)
 *  - <nav:first/> - a link to the first page
 *  - <nav:prev/> - a link to the previous page
 *  - <nav:allpages/> - individual links to all pages. The contents of this node are used to separate page links.
 *  - <nav:next/> - a link to the next page
 *  - <nav:last/> - a link to the last page
 *  - <nav:records/> - a description of which records are currently being displayed
 *  - <nav:recordsFrom/> - the first record number being displayed
 *  - <nav:recordsTo/> - the last record number being displayed
 *  - <nav:recordsCount/> - the total number of records on all pages
 *
 * @author nafg and Timothy Perrett
 */
trait PaginatorSnippet[T] extends Paginator[T] {
  /**
   * The "previous page" link text
   */
  def prevXml: NodeSeq = Text(?("<"))
  /**
   * The "next page" link text
   */
  def nextXml: NodeSeq = Text(?(">"))
  /**
   * The "first page" link text
   */
  def firstXml: NodeSeq = Text(?("<<"))
  /**
   * The "last page" link text
   */
  def lastXml: NodeSeq = Text(?(">>"))
  
  /**
   * How to display the page's starting record
   */
  def recordsFrom: String = (first+1 min count) toString
  /**
   * How to display the page's ending record
   */
  def recordsTo: String = ((first+itemsPerPage) min count) toString
  /**
   * The status displayed when using <nav:records/> in the template.
   */
  def currentXml: NodeSeq = 
    if(count==0)
      Text(??("paginator.norecords"))
    else
      Text(??("paginator.displayingrecords", 
              Array(recordsFrom, recordsTo, count).map(_.asInstanceOf[AnyRef]) : _*))

  /**
   * The template prefix for general navigation components
   */
  def navPrefix = "nav"
  /**
   * The URL query parameter to propagate the record the page should start at
   */
  def offsetParam = "offset"

  protected var _first = 0L
  /**
   * Overrides the super's implementation so the first record can be overridden by a URL query parameter.
   */
  override def first = S.param(offsetParam).map(toLong) openOr _first max 0
  /**
   * Sets the default starting record of the page (URL query parameters take precedence over this)
   */
  def first_=(f: Long) = _first = f max 0 min (count-1)
  /**
   * Returns a URL used to link to a page starting at the given record offset.
   */
  def pageUrl(offset: Long): String = appendParams(S.uri, List(offsetParam -> offset.toString))
  /**
   * Returns XML that links to a page starting at the given record offset, if the offset is valid and not the current one.
   * @param ns The link text, if the offset is valid and not the current offset; or, if that is not the case, the static unlinked text to display
   */
  def pageXml(newFirst: Long, ns: NodeSeq): NodeSeq =
    if(first==newFirst || newFirst < 0 || newFirst >= count)
      ns
    else
      <a href={pageUrl(newFirst)}>{ns}

  /**
   * Generates links to multiple pages with arbitrary XML delimiting them.
   */
  def pagesXml(pages: Seq[Int], sep: NodeSeq): NodeSeq =
    pages.toList map {n =>
      pageXml(n*itemsPerPage, Text(n+1 toString))
                    } match {
                      case one :: Nil => one
                      case first :: rest => rest.foldLeft(first) {
                        case (a,b) => a ++ sep ++ b
                      }
                      case Nil => Nil
                    }
  
  /**
   * This is the method that binds template XML according according to the specified configuration.
   * You can reference this as a snippet method directly in your template; or you can call it directly
   * as part of your binding code.
   */
  def paginate(xhtml: NodeSeq) = {
    bind(navPrefix, xhtml,
         "first" -> pageXml(0, firstXml),
         "prev" -> pageXml(first-itemsPerPage max 0, prevXml),
         "allpages" -> {(n:NodeSeq) => pagesXml(0 until numPages, n)},
         "zoomedpages" -> {(ns: NodeSeq) => pagesXml(zoomedPages, ns)},
         "next" -> pageXml(first+itemsPerPage min itemsPerPage*(numPages-1) max 0, nextXml),
         "last" -> pageXml(itemsPerPage*(numPages-1), lastXml),
         "records" -> currentXml,
         "recordsFrom" -> Text(recordsFrom),
         "recordsTo" -> Text(recordsTo),
         "recordsCount" -> Text(count.toString)
       )
  }
}

/**
 * This trait adds snippet functionality for sorted paginators.
 * You can place bind points in your template for column headers, and it turns them into links
 * That you can click to sort by that column. Simply write, e.g.,
 * <th><sort:name/></th><th><sort:email/></th> etc.
 */
trait SortedPaginatorSnippet[T, C] extends SortedPaginator[T, C] with PaginatorSnippet[T] {
  /**
   * The prefix to bind the sorting column headers
   */
  def sortPrefix = "sort"
  /**
   * The URL query parameter to specify the sort column
   */
  def sortParam = "sort"
  /**
   * The URL query parameter to specify the sort direction
   */
  def ascendingParam = "asc"
  /**
   * Calculates the page url taking sorting into account.
   */
  def sortedPageUrl(offset: Long, sort: (Int, Boolean)) = sort match {
    case (col, ascending) =>
      appendParams(super.pageUrl(offset), List(sortParam->col.toString, ascendingParam->ascending.toString))
  }
  /**
   * Overrides pageUrl and delegates to sortedPageUrl using the current sort
   */
  override def pageUrl(offset: Long) = sortedPageUrl(offset, sort)
  /**
   * Overrides sort, giving the URL query parameters precedence
   */
  override def sort = super.sort match {
    case (col, ascending) => (
      S.param("sort").map(toInt) openOr col,
      S.param("asc").map(toBoolean) openOr ascending
    )
  }
  /**
   * This is the snippet method, which you can reference in your template or call directly.
   */
  override def paginate(xhtml: NodeSeq): NodeSeq =
    bind(sortPrefix, super.paginate(xhtml),
         headers.zipWithIndex.map {
           case ((binding, _), colIndex) =>
             FuncBindParam(binding, (ns:NodeSeq) => <a href={sortedPageUrl(first, sortedBy(colIndex))}>{ns} )
         }.toSeq : _*
       )
}

/**
 * Sort your paginated views by using lifts functions mapping. 
 * The only down side with this style is that your links are session 
 * specific and non-bookmarkable.
 * If you mix this trait in to a StatefulSnippet, it should work out the box.
 * Otherwise, implement 'registerThisSnippet.'
 * @author nafg and Timothy Perrett
 */
trait StatefulSortedPaginatorSnippet[T, C] extends SortedPaginatorSnippet[T, C] {
  /**
   * This method is called before the new page is served, to set up the state in advance.
   * It is implemented by StatefulSnippet so you can just mix in StatefulSortedPaginatorSnippet to one;
   * or you can implement it yourself, using things like S.mapSnippet.
   */
  def registerThisSnippet: Unit
  /**
   * Overrides to use Lift state rather than URL query parameters.
   */
  override def sortedPageUrl(offset: Long, sort: (Int, Boolean)) =
    S.fmapFunc(S.NFuncHolder(() => registerThisSnippet)){ name =>
      appendParams(super.sortedPageUrl(offset,sort), List(name -> "_"))
                                                       }
}

Other Lift Framework examples (source code examples)

Here is a short list of links related to this Lift Framework Paginator.scala source code file:

... this post is sponsored by my books ...

#1 New Release!

FP Best Seller

 

new blog posts

 

Copyright 1998-2021 Alvin Alexander, alvinalexander.com
All Rights Reserved.

A percentage of advertising revenue from
pages under the /java/jwarehouse URI on this website is
paid back to open source projects.