##
# Paginator
# Simple and versatile Pagination utility class for PHP, Python, Node.js / Browser Javascript
#
# @version: 1.0.0
# https://github.com/foo123/Paginator
#
##
import math, re
html_esc_re = re.compile(r'[&<>\'"]')
def htmlspecialchars_replace( match ):
m = match.group(0)
if m == '&': return '&'
elif m == '<': return '<'
elif m == '>': return '>'
elif m == '"': return '"'
else: return m
def htmlspecialchars( s ):
global html_esc_re
return re.sub(html_esc_re, htmlspecialchars_replace, str(s))
class Paginator:
VERSION = '1.0.0'
def __init__( self, totalItems=0, itemsPerPage=0, currentPage=1 ):
self._maxPagesToShow = 10
self._placeholder = '(:page)'
self._urlPattern = '?page=' + self._placeholder
self._previousText = '« Previous'
self._nextText = 'Next »'
self._ellipsis = '...'
self._view = 'list'
self._numPages = 0
self._totalItems = int(totalItems)
self._itemsPerPage = int(itemsPerPage)
self._currentPage = int(currentPage)
self.computeNumPages()
def computeNumPages( self ):
self._numPages = 0 if 0 >= self._itemsPerPage or 0 >= self._totalItems else math.ceil(self._totalItems/self._itemsPerPage)
return self
def numPages( self ):
return self._numPages
def totalItems( self, *args ):
if len(args):
self._totalItems = int(args[0])
return self.computeNumPages()
else:
return self._totalItems
def itemsPerPage( self, *args ):
if len(args):
self._itemsPerPage = int(args[0])
return self.computeNumPages()
else:
return self._itemsPerPage
def currentPage( self, *args ):
if len(args):
self._currentPage = int(args[0])
return self
else:
return self._currentPage
def maxPagesToShow( self, *args ):
if len(args):
maxPagesToShow = int(args[0])
if maxPagesToShow < 3:
raise ValueError('maxPagesToShow cannot be less than 3!')
self._maxPagesToShow = maxPagesToShow
return self
else:
return self._maxPagesToShow
def urlPattern( self, *args ):
if len(args):
self._urlPattern = str(args[0])
return self
else:
return self._urlPattern
def placeholder( self, *args ):
if len(args):
self._placeholder = str(args[0])
return self
else:
return self._placeholder
def previousText( self, *args ):
if len(args):
self._previousText = str(args[0])
return self
else:
return self._previousText
def nextText( self, *args ):
if len(args):
self._nextText = str(args[0])
return self
else:
return self._nextText
def ellipsis( self, *args ):
if len(args):
self._ellipsis = str(args[0])
return self
else:
return self._ellipsis
def view( self, *args ):
if len(args):
view = str(args[0]).lower()
if 'mobile' == view: view = 'selectbox'
elif 'selectbox' == view: view = 'selectbox'
elif 'select' == view: view = 'selectbox'
else: view = 'list'
self._view = view
return self
else:
return self._view
def pageUrl( self, pageNum ):
return self._urlPattern.replace(self._placeholder, str(pageNum))
def prevPage( self ):
return self._currentPage-1 if self._currentPage > 1 else None
def nextPage( self ):
return self._currentPage+1 if self._currentPage < self._numPages else None
def prevUrl( self ):
return self.pageUrl( self.prevPage( ) ) if self.prevPage( ) else None
def nextUrl( self ):
return self.pageUrl( self.nextPage( ) ) if self.nextPage( ) else None
def currentPageFirstItem( self ):
first = (self._currentPage - 1) * self._itemsPerPage + 1
return None if first > self._totalItems else first
def currentPageLastItem( self ):
first = self.currentPageFirstItem()
if first is None: return None
last = first + self._itemsPerPage - 1
return self._totalItems if last > self._totalItems else last
##
# Get a list of paginated page data.
#
# Example:
# list(
# dict ('num' : 1, 'url' : '/example/page/1', 'isCurrent' : false),
# dict ('num' : '...', 'url' : NULL, 'isCurrent' : false),
# dict ('num' : 3, 'url' : '/example/page/3', 'isCurrent' : false),
# dict ('num' : 4, 'url' : '/example/page/4', 'isCurrent' : true ),
# dict ('num' : 5, 'url' : '/example/page/5', 'isCurrent' : false),
# dict ('num' : '...', 'url' : NULL, 'isCurrent' : false),
# dict ('num' : 10, 'url' : '/example/page/10', 'isCurrent' : false),
# )
#
# @return list
#
def pages( self ):
pages = []
if 1 >= self._numPages: return pages
if self._numPages <= self._maxPagesToShow:
for i in range(1, self._numPages+1):
pages.append(self.createPage(i, i==self._currentPage))
else:
# Determine the sliding range, centered around the current page.
numAdjacents = math.floor((self._maxPagesToShow - 3) / 2)
if self._currentPage + numAdjacents > self._numPages:
slidingStart = self._numPages - self._maxPagesToShow + 2
else:
slidingStart = self._currentPage - numAdjacents
if slidingStart < 2: slidingStart = 2
slidingEnd = slidingStart + self._maxPagesToShow - 3
if slidingEnd >= self._numPages: slidingEnd = self._numPages - 1
# Build the list of pages.
# first
pages.append(self.createPage(1, 1==self._currentPage))
# ellipsis ..
if slidingStart > 2: pages.append(self.createPage(None))
# shown pages
for i in range(slidingStart,slidingEnd+1):
pages.append(self.createPage(i, i==self._currentPage))
# ellipsis ..
if slidingEnd < self._numPages - 1: pages.append(self.createPage(None))
# last
pages.append(self.createPage(self._numPages, self._numPages==self._currentPage))
return pages
def createPage( self, pageNum, isCurrent=False ):
return {
'num' : self._ellipsis,
'url' : None,
'isCurrent' : False
} if pageNum is None else {
'num' : pageNum,
'url' : self.pageUrl(pageNum),
'isCurrent' : bool(isCurrent)
}
def render( self ):
if 1 >= self._numPages: return ''
if 'selectbox' == self._view:
html = '<div class="pagination">'
# previous link
if self.prevUrl( ):
html += '<span class="page-previous"><a href="' + htmlspecialchars(self.prevUrl()) + '">'+ self._previousText +'</a></span>';
html += '<select class="page-select">'
# shown pages by number including first and last
for page in self.pages():
if page['url']:
# actual page with page number
html += '<option value="' + htmlspecialchars(page['url']) + '"' + (' selected' if page['isCurrent'] else '') + '>' + str(page['num']) + '</option>'
else:
# ellipsis, more
html += '<option disabled>' + str(page['num']) + '</option>'
html += '</select>'
# next link
if self.nextUrl():
html += '<span class="page-next"><a href="' + htmlspecialchars(self.nextUrl()) + '">'+ self._nextText +'</a></span>';
html += '</div>'
else:
# possibly should be wrapped around <nav></nav> element when used
html = '<ul class="pagination">'
# previous link
if self.prevUrl( ):
html += '<li class="page-previous"><a href="' + htmlspecialchars(self.prevUrl()) + '">'+ self._previousText +'</a></li>';
# shown pages by number including first and last
for page in self.pages():
if page['url']:
# actual page with page number
html += '<li class="page-item' + (' first' if 1==page['num'] else '') + (' last' if self._numPages==page['num'] else '') + (' active' if page['isCurrent'] else '') + '"><a href="' + htmlspecialchars(page['url']) + '">' + str(page['num']) + '</a></li>'
else:
# ellipsis, more
html += '<li class="page-item disabled"><span>' + str(page['num']) + '</span></li>'
# next link
if self.nextUrl():
html += '<li class="page-next"><a href="' + htmlspecialchars(self.nextUrl()) + '">'+ self._nextText +'</a></li>';
html += '</ul>'
return html
def __str__( self ):
return self.render()
__all__ = ['Paginator']
|