From 0db69983feb572e95a3f6612b0b246f1abb983b4 Mon Sep 17 00:00:00 2001 From: Noah Petherbridge Date: Sun, 7 Jul 2024 12:45:42 -0700 Subject: [PATCH] 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. --- pkg/models/pagination.go | 97 ++++++++++++++++-------- web/static/js/bulma.js | 8 ++ web/templates/partials/simple_pager.html | 28 ++++++- 3 files changed, 100 insertions(+), 33 deletions(-) diff --git a/pkg/models/pagination.go b/pkg/models/pagination.go index 51aa30e..737a5d9 100644 --- a/pkg/models/pagination.go +++ b/pkg/models/pagination.go @@ -1,11 +1,14 @@ 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. @@ -19,12 +22,6 @@ type Pagination struct { lastPage bool } -// Page for Iter. -type Page struct { - Page int - IsCurrent bool -} - // Load the page from form or query parameters. func (p *Pagination) ParsePage(r *http.Request) { raw := r.FormValue("page") @@ -40,50 +37,88 @@ func (p *Pagination) ParsePage(r *http.Request) { } } +// 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{} - pageIdx int - total = p.Pages() + pages = []Page{} + total = p.Pages() ) for i := 1; i <= total; i++ { pages = append(pages, Page{ Page: i, IsCurrent: i == p.Page, }) - - if i == p.Page { - pageIdx = i - } } // Do we have A LOT of pages? - if len(pages) > config.PagerButtonLimit { - // We return a slide only N pages long. Where is our current page in the offset? - if pageIdx <= config.PagerButtonLimit/2 { - // We are near the front, return the first N pages. - return pages[:config.PagerButtonLimit+1] - } - - // Are we near the end? - if pageIdx > len(pages)-(config.PagerButtonLimit/2) { - // We are near the end, return the last N pages. - return pages[len(pages)-config.PagerButtonLimit-1:] - } - - // We are somewhere in the middle. - var result = []Page{} - for i := pageIdx - (config.PagerButtonLimit / 2) - 1; i < pageIdx+(config.PagerButtonLimit/2); i++ { - if i >= 0 && i < len(pages) { - result = append(result, pages[i]) + 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 diff --git a/web/static/js/bulma.js b/web/static/js/bulma.js index 9296c08..5558eeb 100644 --- a/web/static/js/bulma.js +++ b/web/static/js/bulma.js @@ -82,6 +82,14 @@ document.addEventListener('DOMContentLoaded', () => { })); })(); + // Dropdown menus. + (document.querySelectorAll(".dropdown") || []).forEach(node => { + const button = node.querySelector("button"); + button.addEventListener("click", (e) => { + node.classList.toggle("is-active"); + }) + }); + // Common event handlers for bulma modals. (document.querySelectorAll(".modal-background, .modal-close, .photo-modal") || []).forEach(node => { const target = node.closest(".modal"); diff --git a/web/templates/partials/simple_pager.html b/web/templates/partials/simple_pager.html index 2d426a9..941dc28 100644 --- a/web/templates/partials/simple_pager.html +++ b/web/templates/partials/simple_pager.html @@ -13,18 +13,42 @@ See also: template_funcs.go for the SimplePager wrapper function. {{if .Pager.Pages}}