-
-
Notifications
You must be signed in to change notification settings - Fork 16
Pagination
What's a keyset pagination: http://use-the-index-luke.com/no-offset.
Take a model named Post
as an example. A data type PostPage
will be generated for paginating:
type PostPage struct {
WhereString string
WhereParams []interface{}
Order map[string]string
FirstId int64
LastId int64
PageNum int
PerPage int
TotalPages int
TotalItems int64
}
And meanwhile generated three methods Current
, Next
and Previous
:
// Current get the current page of PostPage object for pagination
func (_p *PostPage) Current() ([]Post, error)
// Current get the next page of PostPage object for pagination
func (_p *PostPage) Next() ([]Post, error)
// Current get the previous page of PostPage object for pagination
func (_p *PostPage) Previous() ([]Post, error)
You can init a PostPage
at first:
pp := &PostPage{Order: map[string]string{"id": "desc"}, PerPage: 5}
Then get the first page:
ps, err := pp.Current()
Then next page:
ps, err = pp.Next()
Or back to previous page:
ps, err = pp.Previous()
Show the current page number:
fmt.Println(pp.PageNum)
Show total pages and total count:
fmt.Println(pp.TotalPages)
fmt.Println(pp.TotalItems)
You can add a where clause with parameters as a filter when init a PostPage
object:
pp := &PostPage{
WhereString: "title LIKE ? AND hits > ?",
WhereParams: []interface{}{"%go-on-rails%", 200},
Order: map[string]string{"id": "desc"},
PerPage: 5}
Here the WhereString
is a ? binding with WhereParams
correspondingly.
All above is making a pagination object in the Model level, now we show how to make it work from the View to the Controller(a handler), and then call the methods of the object.
For example we GET an index page and pass three parameters:
- direction: previous / current / next, required
- first_id: the first ID in the page, optional, it will be 0 if omited
- last_id: the last ID in the page, optional, it will be 0 if omited
In our Controller, we will do some check and then create a pagination object to get a page of records. Here we show how to make it in the Gin framework:
function IndexHandler(c *gin.Context) {
direction := c.Param("direction")
firstId, _ := strconv.ParseInt(c.Param("first_id"), 10, 64)
lastId, _ := strconv.ParseInt(c.Param("last_id"), 10, 64)
pp := &PostPage{
WhereString: "title LIKE ? AND hits > ?",
WhereParams: []interface{}{"%go-on-rails%", 200},
Order: map[string]string{"id": "desc"},
PerPage: 5,
FirstId: firstId,
LastId: lastId}
ps := []Post
switch direction {
case "previous":
ps, _ = pp.Previous()
case "next":
ps, _ = pp.Next()
default:
ps, _ = pp.Current()
}
c.JSON(200, gin.H{
"status": "success"
"data": ps
})
}
maybe you'll think the switch
loop is tedious to write for every pagination controller, so there's a helper function for you: GetPage
. We can rewrite switch section:
function IndexHandler(c *gin.Context) {
direction := c.Param("direction")
firstId, _ := strconv.ParseInt(c.Param("first_id"), 10, 64)
lastId, _ := strconv.ParseInt(c.Param("last_id"), 10, 64)
pp := &PostPage{
WhereString: "title LIKE ? AND hits > ?",
WhereParams: []interface{}{"%go-on-rails%", 200},
Order: map[string]string{"id": "desc"},
PerPage: 5,
FirstId: firstId,
LastId: lastId}
ps, err := pp.GetPage(direction)
c.JSON(200, gin.H{
"status": "success"
"data": ps
})
}
it gets a little bit clearer!
In the View, take the GET action for example, the relative path will be:
/posts?direction=(previous|current|next)&first_id=<PAGE_NUM>&last_id=<PAGE_NUM>
Other actions like POST are similar to pass the three parameters: direction
, first_id
and last_id
.