-
Notifications
You must be signed in to change notification settings - Fork 1
/
paginate.go
169 lines (152 loc) · 5.38 KB
/
paginate.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
// 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
import (
"net/http"
"net/url"
"strconv"
"github.com/khulnasoft-lab/godep/internal/log"
)
// 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.
type pagination struct {
baseURL *url.URL // URL common to all pages
Limit int // the number of results requested on a page
DefaultLimit int // the default search limit
MaxLimit int // the maximum number of results allowed for any request
ResultCount int // number of results on this page
TotalCount int // total number of results
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
Limits []int // limits to be displayed
}
// PageURL constructs a URL that displays the given page.
// It adds a "page" query parameter to the base URL.
func (p pagination) PageURL(page int) string {
newQuery := p.baseURL.Query()
newQuery.Set("page", strconv.Itoa(page))
p.baseURL.RawQuery = newQuery.Encode()
return p.baseURL.String()
}
// URL constructs a URL that adds limit and mode query parameters to the base
// URL. Passing a zero value omits the parameter.
func (p pagination) URL(limit int, mode, q string) string {
newQuery := p.baseURL.Query()
if limit != 0 {
newQuery.Set("limit", strconv.Itoa(limit))
}
if mode != "" {
newQuery.Set("m", mode)
}
if q != "" {
newQuery.Set("q", q)
}
p.baseURL.RawQuery = newQuery.Encode()
return p.baseURL.String()
}
// 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.
func newPagination(params paginationParams, resultCount, totalCount int) pagination {
return pagination{
baseURL: params.baseURL,
TotalCount: totalCount,
ResultCount: resultCount,
Offset: params.offset(),
Limit: params.limit,
DefaultLimit: defaultSearchLimit,
MaxLimit: maxSearchPageSize,
Page: params.page,
PrevPage: prev(params.page),
NextPage: next(params.page, params.limit, totalCount),
Pages: pagesToLink(params.page, numPages(params.limit, totalCount), defaultNumPagesToLink),
}
}
// paginationParams holds pagination parameters extracted from the request.
type paginationParams struct {
baseURL *url.URL
page int // the number of the page to display
limit int // the maximum number of results to display on the page
}
// offset returns the offset of the first result on the page.
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 {
log.Errorf(r.Context(), "strconv.Atoi(%q) for page: %v", a, err)
}
}
if val < 1 {
val = dflt
}
return val
}
return paginationParams{
baseURL: r.URL,
page: positiveParam("page", 1),
limit: positiveParam("limit", defaultLimit),
}
}
const defaultNumPagesToLink = 5
// 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,
// given the specified maximum page size and the total number of results.
func numPages(pageSize, totalCount int) int {
return (totalCount + pageSize - 1) / pageSize
}
// offset returns the offset of the first result on page, assuming all previous
// pages were of size limit.
func offset(page, limit int) int {
if page <= 1 {
return 0
}
return (page - 1) * limit
}
// prev returns the number of the page before the given page, or zero if the
// given page is 1 or smaller.
func prev(page int) int {
if page <= 1 {
return 0
}
return page - 1
}
// next returns the number of the page after the given page, or zero if page 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) {
return 0
}
return page + 1
}