website/pkg/models/pagination.go
Noah Petherbridge 0db69983fe Revise Pagination Widget
The pager widget will now show a dropdown menu of overflow pages in the
middle. This allows easy access to the First and Last pages and an
ability to select from any of the middle pages to jump to quickly.
2024-07-07 12:45:42 -07:00

158 lines
3.5 KiB
Go

package models
import (
"fmt"
"math"
"math/rand"
"net/http"
"strconv"
"code.nonshy.com/nonshy/website/pkg/config"
"code.nonshy.com/nonshy/website/pkg/encryption"
)
// Pagination result object.
type Pagination struct {
Page int // provide <0 to mean "last page"
PerPage int
Total int64
Sort string
// privates
lastPage bool
}
// Load the page from form or query parameters.
func (p *Pagination) ParsePage(r *http.Request) {
raw := r.FormValue("page")
a, err := strconv.Atoi(raw)
if err == nil {
if a <= 0 {
p.lastPage = true
a = 1
}
p.Page = a
} else {
p.Page = 1
}
}
// Page for Iter.
type Page struct {
Page int // label to show in the button
IsCurrent bool // highlight the currently selected page button
// this "button" is a drop-down menu of page numbers in the middle
IsOverflow bool
overflowFrom int
overflowTo int
}
// Iter the pages, for templates.
//
// If there are fewer than 7 pages (configurable) this returns a simple slice of buttons
// for each of the 7 pages to be drawn. If there are a LOT of pages, the middle button of
// the pager should be a drop-down menu to pick from the pages within.
func (p *Pagination) Iter() []Page {
var (
pages = []Page{}
total = p.Pages()
)
for i := 1; i <= total; i++ {
pages = append(pages, Page{
Page: i,
IsCurrent: i == p.Page,
})
}
// Do we have A LOT of pages?
if len(pages) > config.PagerButtonLimit+1 {
// The left half of the buttons should be pages 1 thru N.
// The right half are the final pages M thru Last.
// In the middle will be the overflow drop-down of middle pages.
var (
endLength = config.PagerButtonLimit / 2
start = endLength + 1
end = len(pages) - endLength
overflow = Page{
Page: start,
IsCurrent: p.Page >= start && p.Page <= end,
IsOverflow: true,
overflowFrom: start,
overflowTo: end,
}
result = []Page{}
)
// If we are currently selected on an overflow page, set the label to match.
if overflow.IsCurrent {
overflow.Page = p.Page
}
result = pages[:endLength]
result = append(result, overflow)
result = append(result, pages[end:]...)
return result
}
return pages
}
// IterOverflow: if the Page represents an overflow drop-down menu, iterate the members of the menu
// for easy template integration.
func (p Page) IterOverflow() []Page {
var result = []Page{}
for i := p.overflowFrom; i <= p.overflowTo; i++ {
result = append(result, Page{
Page: i,
IsCurrent: p.Page == i,
})
}
return result
}
// UniqueSerialID will return a unique JavaScript ID.
//
// It is used in front-end pages such as for Pagination drop-down menus, which may appear multiple times
// on a page and each use needs a unique ID attribute to connect the button to the dropdown.
func (p *Pagination) UniqueSerialID() string {
return encryption.Hash([]byte(fmt.Sprintf("%d", rand.Intn(9000000))))
}
func (p Pagination) Pages() int {
if p.PerPage == 0 {
return 0
}
return int(math.Ceil(float64(p.Total) / float64(p.PerPage)))
}
func (p *Pagination) GetOffset() int {
// Are we looking for the FINAL page?
if p.lastPage && p.Pages() >= 1 {
p.Page = p.Pages()
}
return (p.Page - 1) * p.PerPage
}
func (p *Pagination) HasNext() bool {
return p.Page < p.Pages()
}
func (p *Pagination) HasPrevious() bool {
return p.Page > 1
}
func (p *Pagination) Next() int {
if p.Page >= p.Pages() {
return p.Pages()
}
return p.Page + 1
}
func (p *Pagination) Previous() int {
if p.Page > 1 {
return p.Page - 1
}
return 1
}