Reputation: 20025
I'm trying to create pagination with Go but I'm a bit confused. It's my first time to creating pagination as I used to use laravel's helper class when I was still using PHP.
I tried doing something like:
var totalPages = int(math.Ceil(float64(totalRecords) / float64(recordsPerPage)))
for i := 0; i < totalPages; i++ {
pages[i] = fmt.Sprintf(`<li><a href="?page=%d&limit=%d">%d</a></li>`, i+1, limit, i+1)
}
And that shows all the pages, I want to create something that would look like:
< 1 2 ... 20 24 25 26 27 ... 200 201 >
25 being current page and 201 being the last page.
I also experimented with something like the following but was quirky on some cases like if the page is close to the start or the end:
// pages[0] = fmt.Sprintf(`<li><a href="?page=%d&limit=%d">%d</a></li>`, 1, limit, 1)
// pages[1] = fmt.Sprintf(`<li><a href="?page=%d&limit=%d">%d</a></li>`, 2, limit, 2)
// pages[2] = fmt.Sprintf(`<li><a href="?page=%d&limit=%d">%d</a></li>`, 3, limit, 3)
// pages[3] = `<li><a class="more">…</a></li>`
// pages[4] = fmt.Sprintf(`<li><a href="?page=%d&limit=%d">%d</a></li>`, page, limit, page)
// pages[5] = fmt.Sprintf(`<li><a href="?page=%d&limit=%d">%d</a></li>`, page+1, limit, page+1)
// pages[6] = fmt.Sprintf(`<li><a href="?page=%d&limit=%d">%d</a></li>`, page+2, limit, page+2)
// pages[7] = fmt.Sprintf(`<li><a href="?page=%d&limit=%d">%d</a></li>`, page+3, limit, page+3)
// pages[8] = fmt.Sprintf(`<li><a href="?page=%d&limit=%d">%d</a></li>`, page+4, limit, page+4)
// pages[9] = fmt.Sprintf(`<li><a href="?page=%d&limit=%d">%d</a></li>`, page+5, limit, page+5)
// pages[10] = fmt.Sprintf(`<li><a href="?page=%d&limit=%d">%d</a></li>`, page+6, limit, page+6)
// pages[11] = `<li><a class="more">…</a></li>`
// pages[12] = fmt.Sprintf(`<li><a href="?page=%d&limit=%d">%d</a></li>`, totalPages-1, limit, totalPages-1)
// pages[13] = fmt.Sprintf(`<li><a href="?page=%d&limit=%d">%d</a></li>`, totalPages, limit, totalPages)
So the question is, how do I achieve this? Is there a library? What is the correct logic?
Upvotes: 2
Views: 141
Reputation: 417797
Here's a short implementation, which returns the page numbers you need to render:
func pages(cur, max, around int) (r []int) {
for i := cur - around; i <= cur+around; i++ {
if i >= 1 && i <= max {
r = append(r, i)
}
}
for i := 1; i <= around; i++ {
if i < cur-around {
r = append(r, i)
}
if max+1-i > cur+around {
r = append(r, max+1-i)
}
}
sort.Ints(r)
return
}
You need to pass the current page (cur
), the max page number (max
), and how many neighbors (around
) you want to list around current and at the ends of the list
If 2 page numbers next to each other have difference > 1, you also need to render ...
between them.
Testing it:
fmt.Println(pages(1, 1, 2))
fmt.Println(pages(1, 2, 2))
fmt.Println(pages(1, 3, 2))
fmt.Println(pages(1, 4, 2))
fmt.Println(pages(1, 5, 2))
fmt.Println(pages(1, 9, 3))
fmt.Println(pages(25, 201, 2))
ps := pages(25, 201, 3)
for i, page := range ps {
if i > 0 && ps[i-1]+1 < page {
fmt.Print("... ")
}
fmt.Print(page, " ")
}
Output (try it on the Go Playground):
[1]
[1 2]
[1 2 3]
[1 2 3 4]
[1 2 3 4 5]
[1 2 3 4 7 8 9]
[1 2 23 24 25 26 27 200 201]
1 2 3 ... 22 23 24 25 26 27 28 ... 199 200 201
sort.Ints()
The purpose of sort.Ints()
is to return page numbers in increasing order. It is needed because the 2nd loop adds numbers out of order.
If we can change it so that the 2nd loop keeps the order, sorting won't be needed anymore.
The 2nd loop is responsible to add page numbers from the ends of the list (beginning and end). Appending end is fine (just have to go upward), and we'll add the beginning to another slice, to which the rest will be appended.
Here it is:
func pages(cur, max, around int) (r []int) {
for i := cur - around; i <= cur+around; i++ {
if i >= 1 && i <= max {
r = append(r, i)
}
}
r2 := make([]int, 0, len(r)+4)
for i := 1; i <= around; i++ {
if i < cur-around {
r2 = append(r2, i)
}
if max-around+i > cur+around {
r = append(r, max-around+i)
}
}
return append(r2, r...)
}
Testing and output is the same. Try this variant on the Go Playground.
If you want different number of pages at the ends, you can add 1 additional parameter and use it with 0 complexity added:
func pages(cur, max, around, edge int) (r []int) {
for i := cur - around; i <= cur+around; i++ {
if i >= 1 && i <= max {
r = append(r, i)
}
}
r2 := make([]int, 0, len(r)+2*edge)
for i := 1; i <= edge; i++ {
if i < cur-around {
r2 = append(r2, i)
}
if max-around+i > cur+around {
r = append(r, max-around+i)
}
}
return append(r2, r...)
}
Try this variant on the Go Playground.
Upvotes: 2
Reputation: 891
Break down your problem and you will get it.
What you want is to compute the neighbors.
package main
import (
"fmt"
)
func main() {
pages(8, 2, 13)
}
func pages(n int, around int, count int) {
first := n - around
if first < 1 {
first = 1
}
last := n + around
if last > count {
last = count
}
if first > 1 {
for i := 1; i <= 2 && i < first; i++ {
fmt.Println(i)
}
if 3 < first {
fmt.Println("...")
}
}
for i := first; i <= last; i++ {
fmt.Println(i)
}
if last < count {
if last <= count-3 {
fmt.Println("...")
}
end := count - 1
if end <= last {
end = last + 1
}
for i := end; i <= count; i++ {
fmt.Println(i)
}
}
}
Improvement : Make number of "prefix" and "suffix" pages variable ;-)
See: https://play.golang.org/p/wOOO9GmpNV
Added a shortened version :
package main
import (
"fmt"
)
func main() {
pages(10, 3, 20)
}
func pages(n int, around int, count int) {
var i int
for i = 1; i <= 2 && i<=count; i++ {
fmt.Println(i)
}
if i < n-around {
fmt.Println("...")
i = n - around
}
for ; i <= n+around && i<=count; i++ {
fmt.Println(i)
}
if i < count-1 {
fmt.Println("...")
i = count - 1
}
for ; i <= count; i++ {
fmt.Println(i)
}
}
We can easily wrap things around by providing a callback. Note that channels are very slow.
package main
import (
"fmt"
)
func main() {
pages(10, 3, 20, func(i int) {
if i < 0 {
fmt.Println("...")
return
}
fmt.Println(i)
})
}
func pages(n int, around int, count int, render func(int)) {
var i int
for i = 1; i <= 2 && i <= count; i++ {
render(i)
}
if i < n-around {
render(-1)
i = n - around
}
for ; i <= n+around && i <= count; i++ {
render(i)
}
if i < count-1 {
render(-1)
i = count - 1
}
for ; i <= count; i++ {
render(i)
}
}
Last version (unless bugs) which includes everything :
package main
import (
"fmt"
)
func main() {
pages(10, 3, 21,4,4, func(i int) {
if i < 0 {
fmt.Println("...")
return
}
fmt.Println(i)
})
}
func pages(n int, around int, count int,start int, end int, render func(int)) {
var i int
for i = 1; i <= start && i <= count; i++ {
render(i)
}
if i < n-around {
render(-1)
i = n - around
}
for ; i <= n+around && i <= count; i++ {
render(i)
}
if i < count-end+1 {
render(-1)
i = count - end+1
}
for ; i <= count; i++ {
render(i)
}
}
See: https://play.golang.org/p/KfTORNuHY_
How good is this ?
Note, if an array is needed, one can do it through a closure. Same with channels.
Upvotes: 2