2019-07-02 01:21:45 +03:00
|
|
|
// Copyright 2019 The Go Authors. All rights reserved.
|
|
|
|
// Use of this source code is governed by a BSD-style
|
|
|
|
// license that can be found in the LICENSE file.
|
|
|
|
|
|
|
|
package frontend
|
|
|
|
|
2019-07-02 20:36:59 +03:00
|
|
|
import (
|
|
|
|
"net/http"
|
|
|
|
"net/url"
|
|
|
|
"strconv"
|
2019-09-23 22:58:18 +03:00
|
|
|
|
2020-04-21 23:51:29 +03:00
|
|
|
"golang.org/x/pkgsite/internal/log"
|
2019-07-02 20:36:59 +03:00
|
|
|
)
|
|
|
|
|
2019-08-17 15:55:22 +03:00
|
|
|
// pagination holds information related to paginated display. It is intended to
|
|
|
|
// be part of a view model struct.
|
|
|
|
//
|
|
|
|
// Given a sequence of results with offsets 0, 1, 2 ... (typically from a
|
|
|
|
// database query), we paginate it by dividing it into numbered pages
|
|
|
|
// 1, 2, 3, .... Each page except possibly the last has the same number of results.
|
2019-07-02 01:21:45 +03:00
|
|
|
type pagination struct {
|
2019-08-17 15:55:22 +03:00
|
|
|
baseURL *url.URL // URL common to all pages
|
|
|
|
limit int // the maximum number of results on a page
|
|
|
|
ResultCount int // number of results on this page
|
|
|
|
TotalCount int // total number of results
|
2019-09-26 20:18:09 +03:00
|
|
|
Approximate bool // whether or not the total count is approximate
|
2019-08-17 15:55:22 +03:00
|
|
|
Page int // number of the current page
|
|
|
|
PrevPage int // " " " previous page, usually Page-1 but zero if Page == 1
|
|
|
|
NextPage int // " " " next page, usually Page+1, but zero on the last page
|
|
|
|
Offset int // offset of the first item on the current page
|
|
|
|
Pages []int // consecutive page numbers to be displayed for navigation
|
2019-07-02 01:21:45 +03:00
|
|
|
}
|
|
|
|
|
2019-08-17 15:55:22 +03:00
|
|
|
// PageURL constructs a URL that displays the given page.
|
|
|
|
// It adds a "page" query parameter to the base URL.
|
2019-07-02 20:36:59 +03:00
|
|
|
func (p pagination) PageURL(page int) string {
|
2019-08-17 15:55:22 +03:00
|
|
|
newQuery := p.baseURL.Query()
|
2019-07-02 20:36:59 +03:00
|
|
|
newQuery.Set("page", strconv.Itoa(page))
|
2019-08-17 15:55:22 +03:00
|
|
|
p.baseURL.RawQuery = newQuery.Encode()
|
|
|
|
return p.baseURL.String()
|
2019-07-02 20:36:59 +03:00
|
|
|
}
|
|
|
|
|
2019-08-17 15:55:22 +03:00
|
|
|
// newPagination constructs a pagination. Call it after some results have been
|
|
|
|
// obtained.
|
|
|
|
// resultCount is the number of results in the current page.
|
|
|
|
// totalCount is the total number of results.
|
2019-07-02 20:36:59 +03:00
|
|
|
func newPagination(params paginationParams, resultCount, totalCount int) pagination {
|
2019-07-02 01:21:45 +03:00
|
|
|
return pagination{
|
2019-08-17 15:55:22 +03:00
|
|
|
baseURL: params.baseURL,
|
2019-07-02 01:21:45 +03:00
|
|
|
TotalCount: totalCount,
|
|
|
|
ResultCount: resultCount,
|
2019-07-02 20:36:59 +03:00
|
|
|
Offset: params.offset(),
|
2019-08-17 15:55:22 +03:00
|
|
|
limit: params.limit,
|
2019-07-02 20:36:59 +03:00
|
|
|
Page: params.page,
|
|
|
|
PrevPage: prev(params.page),
|
|
|
|
NextPage: next(params.page, params.limit, totalCount),
|
2019-08-17 15:55:22 +03:00
|
|
|
Pages: pagesToLink(params.page, numPages(params.limit, totalCount), defaultNumPagesToLink),
|
2019-07-02 20:36:59 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// paginationParams holds pagination parameters extracted from the request.
|
|
|
|
type paginationParams struct {
|
2019-08-17 15:55:22 +03:00
|
|
|
baseURL *url.URL
|
|
|
|
page int // the number of the page to display
|
|
|
|
limit int // the maximum number of results to display on the page
|
2019-07-02 20:36:59 +03:00
|
|
|
}
|
|
|
|
|
2019-08-17 15:55:22 +03:00
|
|
|
// offset returns the offset of the first result on the page.
|
2019-07-02 20:36:59 +03:00
|
|
|
func (p paginationParams) offset() int {
|
|
|
|
return offset(p.page, p.limit)
|
|
|
|
}
|
|
|
|
|
|
|
|
// newPaginationParams extracts pagination params from the request.
|
|
|
|
func newPaginationParams(r *http.Request, defaultLimit int) paginationParams {
|
|
|
|
positiveParam := func(key string, dflt int) (val int) {
|
|
|
|
var err error
|
|
|
|
if a := r.FormValue(key); a != "" {
|
|
|
|
val, err = strconv.Atoi(a)
|
|
|
|
if err != nil {
|
2019-12-18 16:38:16 +03:00
|
|
|
log.Errorf(r.Context(), "strconv.Atoi(%q) for page: %v", a, err)
|
2019-07-02 20:36:59 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
if val < 1 {
|
|
|
|
val = dflt
|
|
|
|
}
|
2019-08-17 15:55:22 +03:00
|
|
|
return val
|
2019-07-02 20:36:59 +03:00
|
|
|
}
|
|
|
|
return paginationParams{
|
2019-08-17 15:55:22 +03:00
|
|
|
baseURL: r.URL,
|
2019-07-02 20:36:59 +03:00
|
|
|
page: positiveParam("page", 1),
|
|
|
|
limit: positiveParam("limit", defaultLimit),
|
2019-07-02 01:21:45 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-08-26 20:19:27 +03:00
|
|
|
const defaultNumPagesToLink = 5
|
2019-07-02 01:21:45 +03:00
|
|
|
|
|
|
|
// pagesToLink returns the page numbers that will be displayed. Given a
|
|
|
|
// page, it returns a slice containing numPagesToLink integers in ascending
|
|
|
|
// order and optimizes for page to be in the middle of that range. The max
|
|
|
|
// value of an integer in the return slice will be less than numPages.
|
|
|
|
func pagesToLink(page, numPages, numPagesToLink int) []int {
|
|
|
|
var pages []int
|
|
|
|
start := page - (numPagesToLink / 2)
|
|
|
|
if (numPages - start) < numPagesToLink {
|
|
|
|
start = numPages - numPagesToLink + 1
|
|
|
|
}
|
|
|
|
if start < 1 {
|
|
|
|
start = 1
|
|
|
|
}
|
|
|
|
|
|
|
|
for i := start; (i < start+numPagesToLink) && (i <= numPages); i++ {
|
|
|
|
pages = append(pages, i)
|
|
|
|
}
|
|
|
|
return pages
|
|
|
|
}
|
|
|
|
|
|
|
|
// numPages is the total number of pages needed to display all the results,
|
2019-08-17 15:55:22 +03:00
|
|
|
// given the specified maximum page size and the total number of results.
|
|
|
|
func numPages(pageSize, totalCount int) int {
|
|
|
|
return (totalCount + pageSize - 1) / pageSize
|
2019-07-02 01:21:45 +03:00
|
|
|
}
|
|
|
|
|
2019-08-17 15:55:22 +03:00
|
|
|
// offset returns the offset of the first result on page, assuming all previous
|
|
|
|
// pages were of size limit.
|
2019-07-02 01:21:45 +03:00
|
|
|
func offset(page, limit int) int {
|
2019-08-17 15:55:22 +03:00
|
|
|
if page <= 1 {
|
2019-07-02 01:21:45 +03:00
|
|
|
return 0
|
|
|
|
}
|
|
|
|
return (page - 1) * limit
|
|
|
|
}
|
|
|
|
|
2019-08-17 15:55:22 +03:00
|
|
|
// prev returns the number of the page before the given page, or zero if the
|
|
|
|
// given page is 1 or smaller.
|
2019-07-02 01:21:45 +03:00
|
|
|
func prev(page int) int {
|
2019-08-17 15:55:22 +03:00
|
|
|
if page <= 1 {
|
2019-07-02 01:21:45 +03:00
|
|
|
return 0
|
|
|
|
}
|
|
|
|
return page - 1
|
|
|
|
}
|
|
|
|
|
2019-08-17 15:55:22 +03:00
|
|
|
// next returns the number of the page after the given page, or zero if page is is the last page or larger.
|
|
|
|
// limit and totalCount are used to calculate the last page (see numPages).
|
|
|
|
func next(page, limit, totalCount int) int {
|
|
|
|
if page >= numPages(limit, totalCount) {
|
2019-07-02 01:21:45 +03:00
|
|
|
return 0
|
|
|
|
}
|
|
|
|
return page + 1
|
|
|
|
}
|