Skip to content

Commit

Permalink
Add paginator support for page groups
Browse files Browse the repository at this point in the history
  • Loading branch information
bep authored and tychoish committed Aug 13, 2017
1 parent 3724193 commit b542e9a
Show file tree
Hide file tree
Showing 4 changed files with 320 additions and 42 deletions.
7 changes: 6 additions & 1 deletion docs/content/extras/pagination.md
Expand Up @@ -35,12 +35,17 @@ There are two ways to configure and use a `.Paginator`:

For a given **Node**, it's one of the options above. The `.Paginator` is static and cannot change once created.


The global page size setting (`Paginate`) can be overridden by providing a positive integer as the last argument. The examples below will give five items per page:

* `{{ range (.Paginator 5).Pages }}`
* `{{ $paginator := .Paginate (where .Data.Pages "Type" "post") 5 }}`

It is also possible to use the `GroupBy` functions in combination with pagination:

```
{{ range (.Paginate (.Data.Pages.GroupByDate "2006")).PageGroups }}
```

## Build the navigation

The `.Paginator` contains enough information to build a paginator interface.
Expand Down
198 changes: 173 additions & 25 deletions hugolib/pagination.go
Expand Up @@ -31,12 +31,29 @@ type Pager struct {
*paginator
}

type paginatedElement interface {
Len() int
}

func (p Pages) Len() int {
return len(p)
}

func (psg PagesGroup) Len() int {
l := 0
for _, pg := range psg {
l += len(pg.Pages)
}
return l
}

type pagers []*Pager

var paginatorEmptyPages Pages
var paginatorEmptyPageGroups PagesGroup

type paginator struct {
paginatedPages []Pages
paginatedElements []paginatedElement
pagers
paginationURLFactory
total int
Expand All @@ -63,17 +80,71 @@ func (p *Pager) Url() template.HTML {
return p.URL()
}

// Pages returns the elements on this page.
// Pages returns the Pages on this page.
// Note: If this return a non-empty result, then PageGroups() will return empty.
func (p *Pager) Pages() Pages {
if len(p.paginatedPages) == 0 {
if len(p.paginatedElements) == 0 {
return paginatorEmptyPages
}

if pages, ok := p.element().(Pages); ok {
return pages
}

return paginatorEmptyPages
}

// PageGroups return Page groups for this page.
// Note: If this return non-empty result, then Pages() will return empty.
func (p *Pager) PageGroups() PagesGroup {
if len(p.paginatedElements) == 0 {
return paginatorEmptyPageGroups
}

if groups, ok := p.element().(PagesGroup); ok {
return groups
}

return paginatorEmptyPageGroups
}

func (p *Pager) element() paginatedElement {
if len(p.paginatedElements) == 0 {
return paginatorEmptyPages
}
return p.paginatedPages[p.PageNumber()-1]
return p.paginatedElements[p.PageNumber()-1]
}

// page returns the Page with the given index
func (p *Pager) page(index int) (*Page, error) {

if pages, ok := p.element().(Pages); ok {
if pages != nil && len(pages) > index {
return pages[index], nil
}
return nil, nil
}

// must be PagesGroup
// this construction looks clumsy, but ...
// ... it is the difference between 99.5% and 100% test coverage :-)
groups := p.element().(PagesGroup)

i := 0
for _, v := range groups {
for _, page := range v.Pages {
if i == index {
return page, nil
}
i++
}
}
return nil, nil
}

// NumberOfElements gets the number of elements on this page.
func (p *Pager) NumberOfElements() int {
return len(p.Pages())
return p.element().Len()
}

// HasPrev tests whether there are page(s) before the current.
Expand All @@ -91,7 +162,7 @@ func (p *Pager) Prev() *Pager {

// HasNext tests whether there are page(s) after the current.
func (p *Pager) HasNext() bool {
return p.PageNumber() < len(p.paginatedPages)
return p.PageNumber() < len(p.paginatedElements)
}

// Next returns the pager for the next page.
Expand Down Expand Up @@ -124,16 +195,16 @@ func (p *paginator) PageSize() int {

// TotalPages returns the number of pages in the paginator.
func (p *paginator) TotalPages() int {
return len(p.paginatedPages)
return len(p.paginatedElements)
}

// TotalNumberOfElements returns the number of elements on all pages in this paginator.
func (p *paginator) TotalNumberOfElements() int {
return p.total
}

func splitPages(pages Pages, size int) []Pages {
var split []Pages
func splitPages(pages Pages, size int) []paginatedElement {
var split []paginatedElement
for low, j := 0, len(pages); low < j; low += size {
high := int(math.Min(float64(low+size), float64(len(pages))))
split = append(split, pages[low:high])
Expand All @@ -142,6 +213,44 @@ func splitPages(pages Pages, size int) []Pages {
return split
}

func splitPageGroups(pageGroups PagesGroup, size int) []paginatedElement {
var split []paginatedElement

type keyPage struct {
key interface{}
page *Page
}

var flattened []keyPage

for _, g := range pageGroups {
for _, p := range g.Pages {
flattened = append(flattened, keyPage{g.Key, p})
}
}

numPages := len(flattened)

for low, j := 0, numPages; low < j; low += size {
high := int(math.Min(float64(low+size), float64(numPages)))
var pg PagesGroup
var key interface{} = nil
var groupIndex = -1
for k := low; k < high; k++ {
kp := flattened[k]
if key == nil || key != kp.key {
key = kp.key
pg = append(pg, PageGroup{Key: key})
groupIndex++
}
pg[groupIndex].Pages = append(pg[groupIndex].Pages, kp.page)
}
split = append(split, pg)
}

return split
}

// Paginator gets this Node's paginator if it's already created.
// If it's not, one will be created with all pages in Data["Pages"].
func (n *Node) Paginator(options ...interface{}) (*Pager, error) {
Expand Down Expand Up @@ -264,15 +373,21 @@ func paginatePages(seq interface{}, pagerSize int, section string) (pagers, erro
return nil, errors.New("'paginate' configuration setting must be positive to paginate")
}

pages, err := toPages(seq)
if err != nil {
return nil, err
}

section = strings.TrimSuffix(section, ".html")

urlFactory := newPaginationURLFactory(section)
paginator, _ := newPaginator(pages, pagerSize, urlFactory)

var paginator *paginator

if groups, ok := seq.(PagesGroup); ok {
paginator, _ = newPaginatorFromPageGroups(groups, pagerSize, urlFactory)
} else {
pages, err := toPages(seq)
if err != nil {
return nil, err
}
paginator, _ = newPaginatorFromPages(pages, pagerSize, urlFactory)
}

pagers := paginator.Pagers()

return pagers, nil
Expand Down Expand Up @@ -303,6 +418,28 @@ func probablyEqualPageLists(a1 interface{}, a2 interface{}) bool {
return a1 == a2
}

t1 := reflect.TypeOf(a1)
t2 := reflect.TypeOf(a2)

if t1 != t2 {
return false
}

if g1, ok := a1.(PagesGroup); ok {
g2 := a2.(PagesGroup)
if len(g1) != len(g2) {
return false
}
if len(g1) == 0 {
return true
}
if g1.Len() != g2.Len() {
return false
}

return g1[0].Pages[0] == g2[0].Pages[0]
}

p1, err1 := toPages(a1)
p2, err2 := toPages(a2)

Expand All @@ -311,10 +448,6 @@ func probablyEqualPageLists(a1 interface{}, a2 interface{}) bool {
return true
}

if err1 != nil || err2 != nil {
return false
}

if len(p1) != len(p2) {
return false
}
Expand All @@ -326,21 +459,36 @@ func probablyEqualPageLists(a1 interface{}, a2 interface{}) bool {
return p1[0] == p2[0]
}

func newPaginator(pages Pages, size int, urlFactory paginationURLFactory) (*paginator, error) {
func newPaginatorFromPages(pages Pages, size int, urlFactory paginationURLFactory) (*paginator, error) {

if size <= 0 {
return nil, errors.New("Paginator size must be positive")
}

split := splitPages(pages, size)

p := &paginator{total: len(pages), paginatedPages: split, size: size, paginationURLFactory: urlFactory}
return newPaginator(split, len(pages), size, urlFactory)
}

func newPaginatorFromPageGroups(pageGroups PagesGroup, size int, urlFactory paginationURLFactory) (*paginator, error) {

if size <= 0 {
return nil, errors.New("Paginator size must be positive")
}

split := splitPageGroups(pageGroups, size)

return newPaginator(split, pageGroups.Len(), size, urlFactory)
}

func newPaginator(elements []paginatedElement, total, size int, urlFactory paginationURLFactory) (*paginator, error) {
p := &paginator{total: total, paginatedElements: elements, size: size, paginationURLFactory: urlFactory}

var ps pagers

if len(split) > 0 {
ps = make(pagers, len(split))
for i := range p.paginatedPages {
if len(elements) > 0 {
ps = make(pagers, len(elements))
for i := range p.paginatedElements {
ps[i] = &Pager{number: (i + 1), paginator: p}
}
} else {
Expand Down

0 comments on commit b542e9a

Please sign in to comment.