
net.liftweb.http.Paginator.scala Maven / Gradle / Ivy
The newest version!
/*
* 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.
* 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(S.?("paginator.norecords"))
else
Text(S.?("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 = {
def originalUri = S.originalRequest.map(_.uri).openOr(sys.error("No request"))
appendParams(originalUri, 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
{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) => {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 -> "_"))
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy