From 6fca4a82186c3633f1cb5bdf6a0eb79f8c0529e9 Mon Sep 17 00:00:00 2001 From: Jens Bissinger Date: Thu, 2 Oct 2014 11:40:46 +0200 Subject: [PATCH 1/5] Insert pagination utilities from beego/wetalk. Refs #835. --- controller.go | 8 ++ pagination/controller.go | 32 ++++++++ pagination/paginator.go | 164 +++++++++++++++++++++++++++++++++++++++ pagination/utils.go | 20 +++++ 4 files changed, 224 insertions(+) create mode 100644 pagination/controller.go create mode 100644 pagination/paginator.go create mode 100644 pagination/utils.go diff --git a/controller.go b/controller.go index 72ba323b..e7eaa468 100644 --- a/controller.go +++ b/controller.go @@ -93,6 +93,14 @@ type ControllerInterface interface { URLMapping() } +func (c *Controller) GetCtx() *context.Context { + return c.Ctx +} + +func (c *Controller) GetData() map[interface{}]interface{} { + return c.Data +} + // Init generates default values of controller operations. func (c *Controller) Init(ctx *context.Context, controllerName, actionName string, app interface{}) { c.Layout = "" diff --git a/pagination/controller.go b/pagination/controller.go new file mode 100644 index 00000000..794d779d --- /dev/null +++ b/pagination/controller.go @@ -0,0 +1,32 @@ +// Copyright 2014 beego Author. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package pagination + +import ( + "github.com/astaxie/beego/context" +) + +type PaginationController interface { + GetCtx() *context.Context + GetData() map[interface{}]interface{} +} + +func SetPaginator(controller PaginationController, per int, nums int64) (paginator *Paginator) { + request := controller.GetCtx().Request + paginator = NewPaginator(request, per, nums) + data := controller.GetData() + data["paginator"] = paginator + return +} diff --git a/pagination/paginator.go b/pagination/paginator.go new file mode 100644 index 00000000..593f587d --- /dev/null +++ b/pagination/paginator.go @@ -0,0 +1,164 @@ +// Copyright 2014 beego Author. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package pagination + +import ( + "math" + "net/http" + "net/url" + "strconv" +) + +type Paginator struct { + Request *http.Request + PerPageNums int + MaxPages int + + nums int64 + pageRange []int + pageNums int + page int +} + +func (p *Paginator) PageNums() int { + if p.pageNums != 0 { + return p.pageNums + } + pageNums := math.Ceil(float64(p.nums) / float64(p.PerPageNums)) + if p.MaxPages > 0 { + pageNums = math.Min(pageNums, float64(p.MaxPages)) + } + p.pageNums = int(pageNums) + return p.pageNums +} + +func (p *Paginator) Nums() int64 { + return p.nums +} + +func (p *Paginator) SetNums(nums interface{}) { + p.nums, _ = ToInt64(nums) +} + +func (p *Paginator) Page() int { + if p.page != 0 { + return p.page + } + if p.Request.Form == nil { + p.Request.ParseForm() + } + p.page, _ = strconv.Atoi(p.Request.Form.Get("p")) + if p.page > p.PageNums() { + p.page = p.PageNums() + } + if p.page <= 0 { + p.page = 1 + } + return p.page +} + +func (p *Paginator) Pages() []int { + if p.pageRange == nil && p.nums > 0 { + var pages []int + pageNums := p.PageNums() + page := p.Page() + switch { + case page >= pageNums-4 && pageNums > 9: + start := pageNums - 9 + 1 + pages = make([]int, 9) + for i, _ := range pages { + pages[i] = start + i + } + case page >= 5 && pageNums > 9: + start := page - 5 + 1 + pages = make([]int, int(math.Min(9, float64(page+4+1)))) + for i, _ := range pages { + pages[i] = start + i + } + default: + pages = make([]int, int(math.Min(9, float64(pageNums)))) + for i, _ := range pages { + pages[i] = i + 1 + } + } + p.pageRange = pages + } + return p.pageRange +} + +func (p *Paginator) PageLink(page int) string { + link, _ := url.ParseRequestURI(p.Request.RequestURI) + values := link.Query() + if page == 1 { + values.Del("p") + } else { + values.Set("p", strconv.Itoa(page)) + } + link.RawQuery = values.Encode() + return link.String() +} + +func (p *Paginator) PageLinkPrev() (link string) { + if p.HasPrev() { + link = p.PageLink(p.Page() - 1) + } + return +} + +func (p *Paginator) PageLinkNext() (link string) { + if p.HasNext() { + link = p.PageLink(p.Page() + 1) + } + return +} + +func (p *Paginator) PageLinkFirst() (link string) { + return p.PageLink(1) +} + +func (p *Paginator) PageLinkLast() (link string) { + return p.PageLink(p.PageNums()) +} + +func (p *Paginator) HasPrev() bool { + return p.Page() > 1 +} + +func (p *Paginator) HasNext() bool { + return p.Page() < p.PageNums() +} + +func (p *Paginator) IsActive(page int) bool { + return p.Page() == page +} + +func (p *Paginator) Offset() int { + return (p.Page() - 1) * p.PerPageNums +} + +func (p *Paginator) HasPages() bool { + return p.PageNums() > 1 +} + +func NewPaginator(req *http.Request, per int, nums interface{}) *Paginator { + p := Paginator{} + p.Request = req + if per <= 0 { + per = 10 + } + p.PerPageNums = per + p.SetNums(nums) + return &p +} diff --git a/pagination/utils.go b/pagination/utils.go new file mode 100644 index 00000000..d1199f2d --- /dev/null +++ b/pagination/utils.go @@ -0,0 +1,20 @@ +package pagination + +import ( + "fmt" + "reflect" +) + +// convert any numeric value to int64 +func ToInt64(value interface{}) (d int64, err error) { + val := reflect.ValueOf(value) + switch value.(type) { + case int, int8, int16, int32, int64: + d = val.Int() + case uint, uint8, uint16, uint32, uint64: + d = int64(val.Uint()) + default: + err = fmt.Errorf("ToInt64 need numeric not `%T`", value) + } + return +} From c4f8f45da4ade512dd6fa0a2f943472fc92e2e6c Mon Sep 17 00:00:00 2001 From: Jens Bissinger Date: Mon, 6 Oct 2014 11:36:57 +0200 Subject: [PATCH 2/5] Move pagination to utils/pagination. Refs #837, #835. --- pagination/utils.go | 20 ----------- .../pagination}/controller.go | 0 {pagination => utils/pagination}/paginator.go | 0 utils/pagination/utils.go | 34 +++++++++++++++++++ 4 files changed, 34 insertions(+), 20 deletions(-) delete mode 100644 pagination/utils.go rename {pagination => utils/pagination}/controller.go (100%) rename {pagination => utils/pagination}/paginator.go (100%) create mode 100644 utils/pagination/utils.go diff --git a/pagination/utils.go b/pagination/utils.go deleted file mode 100644 index d1199f2d..00000000 --- a/pagination/utils.go +++ /dev/null @@ -1,20 +0,0 @@ -package pagination - -import ( - "fmt" - "reflect" -) - -// convert any numeric value to int64 -func ToInt64(value interface{}) (d int64, err error) { - val := reflect.ValueOf(value) - switch value.(type) { - case int, int8, int16, int32, int64: - d = val.Int() - case uint, uint8, uint16, uint32, uint64: - d = int64(val.Uint()) - default: - err = fmt.Errorf("ToInt64 need numeric not `%T`", value) - } - return -} diff --git a/pagination/controller.go b/utils/pagination/controller.go similarity index 100% rename from pagination/controller.go rename to utils/pagination/controller.go diff --git a/pagination/paginator.go b/utils/pagination/paginator.go similarity index 100% rename from pagination/paginator.go rename to utils/pagination/paginator.go diff --git a/utils/pagination/utils.go b/utils/pagination/utils.go new file mode 100644 index 00000000..5932647d --- /dev/null +++ b/utils/pagination/utils.go @@ -0,0 +1,34 @@ +// Copyright 2014 beego Author. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package pagination + +import ( + "fmt" + "reflect" +) + +// convert any numeric value to int64 +func ToInt64(value interface{}) (d int64, err error) { + val := reflect.ValueOf(value) + switch value.(type) { + case int, int8, int16, int32, int64: + d = val.Int() + case uint, uint8, uint16, uint32, uint64: + d = int64(val.Uint()) + default: + err = fmt.Errorf("ToInt64 need numeric not `%T`", value) + } + return +} From fa6cbc08d99afe9b975e0823fe0fb3b0acaa3f4b Mon Sep 17 00:00:00 2001 From: Jens Bissinger Date: Tue, 7 Oct 2014 11:02:07 +0200 Subject: [PATCH 3/5] Document usage of utils/pagination. Refs #835. --- utils/pagination/controller.go | 50 ++++++++++++++++++++++++++++++++++ utils/pagination/paginator.go | 25 +++++++++++++++++ 2 files changed, 75 insertions(+) diff --git a/utils/pagination/controller.go b/utils/pagination/controller.go index 794d779d..5a0d4e99 100644 --- a/utils/pagination/controller.go +++ b/utils/pagination/controller.go @@ -12,6 +12,55 @@ // See the License for the specific language governing permissions and // limitations under the License. +// Usage +// +// In your beego.Controller: +// +// package controllers +// +// import "github.com/astaxie/beego/utils/pagination" +// +// type PostsController struct { +// beego.Controller +// } +// +// func (this *PostsController) ListAllPosts() { +// // sets this.Data["paginator"] with the current offset (from the url query param) +// postsPerPage := 20 +// paginator := pagination.SetPaginator(this, postsPerPage, CountPosts()) +// +// // fetch the next 20 posts +// this.Data["posts"] = ListPostsByOffsetAndLimit(paginator.Offset(), postsPerPage) +// } +// +// +// In your view templates: +// +// {{if .paginator.HasPages}} +// +// {{end}} +// +// See also http://beego.me/docs/mvc/view/page.md package pagination import ( @@ -23,6 +72,7 @@ type PaginationController interface { GetData() map[interface{}]interface{} } +// Instantiates a Paginator and assigns it to controller.Data["paginator"]. func SetPaginator(controller PaginationController, per int, nums int64) (paginator *Paginator) { request := controller.GetCtx().Request paginator = NewPaginator(request, per, nums) diff --git a/utils/pagination/paginator.go b/utils/pagination/paginator.go index 593f587d..f89e878e 100644 --- a/utils/pagination/paginator.go +++ b/utils/pagination/paginator.go @@ -21,6 +21,7 @@ import ( "strconv" ) +// Paginator within the state of a http request. type Paginator struct { Request *http.Request PerPageNums int @@ -32,6 +33,7 @@ type Paginator struct { page int } +// Returns the total number of pages. func (p *Paginator) PageNums() int { if p.pageNums != 0 { return p.pageNums @@ -44,14 +46,17 @@ func (p *Paginator) PageNums() int { return p.pageNums } +// Returns the total number of items (e.g. from doing SQL count). func (p *Paginator) Nums() int64 { return p.nums } +// Sets the total number of items. func (p *Paginator) SetNums(nums interface{}) { p.nums, _ = ToInt64(nums) } +// Returns the current page. func (p *Paginator) Page() int { if p.page != 0 { return p.page @@ -69,6 +74,15 @@ func (p *Paginator) Page() int { return p.page } +// Returns a list of all pages. +// +// Usage (in a view template): +// +// {{range $index, $page := .paginator.Pages}} +// +// {{$page}} +// +// {{end}} func (p *Paginator) Pages() []int { if p.pageRange == nil && p.nums > 0 { var pages []int @@ -98,6 +112,7 @@ func (p *Paginator) Pages() []int { return p.pageRange } +// Returns URL for a given page index. func (p *Paginator) PageLink(page int) string { link, _ := url.ParseRequestURI(p.Request.RequestURI) values := link.Query() @@ -110,6 +125,7 @@ func (p *Paginator) PageLink(page int) string { return link.String() } +// Returns URL to the previous page. func (p *Paginator) PageLinkPrev() (link string) { if p.HasPrev() { link = p.PageLink(p.Page() - 1) @@ -117,6 +133,7 @@ func (p *Paginator) PageLinkPrev() (link string) { return } +// Returns URL to the next page. func (p *Paginator) PageLinkNext() (link string) { if p.HasNext() { link = p.PageLink(p.Page() + 1) @@ -124,34 +141,42 @@ func (p *Paginator) PageLinkNext() (link string) { return } +// Returns URL to the first page. func (p *Paginator) PageLinkFirst() (link string) { return p.PageLink(1) } +// Returns URL to the last page. func (p *Paginator) PageLinkLast() (link string) { return p.PageLink(p.PageNums()) } +// Returns true if the current page has a predecessor. func (p *Paginator) HasPrev() bool { return p.Page() > 1 } +// Returns true if the current page has a successor. func (p *Paginator) HasNext() bool { return p.Page() < p.PageNums() } +// Returns true if the given page index points to the current page. func (p *Paginator) IsActive(page int) bool { return p.Page() == page } +// Returns the current offset. func (p *Paginator) Offset() int { return (p.Page() - 1) * p.PerPageNums } +// Returns true if there is more than one page. func (p *Paginator) HasPages() bool { return p.PageNums() > 1 } +// Instantiates a paginator struct for the current http request. func NewPaginator(req *http.Request, per int, nums interface{}) *Paginator { p := Paginator{} p.Request = req From 0b3763cc6775d2b423002a0405d15d1a2e38ac1d Mon Sep 17 00:00:00 2001 From: Jens Bissinger Date: Tue, 7 Oct 2014 11:39:27 +0200 Subject: [PATCH 4/5] Cleanup pagination documentation. Refs #835. --- utils/pagination/controller.go | 49 ---------------------------- utils/pagination/doc.go | 59 ++++++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+), 49 deletions(-) create mode 100644 utils/pagination/doc.go diff --git a/utils/pagination/controller.go b/utils/pagination/controller.go index 5a0d4e99..da92bd69 100644 --- a/utils/pagination/controller.go +++ b/utils/pagination/controller.go @@ -12,55 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -// Usage -// -// In your beego.Controller: -// -// package controllers -// -// import "github.com/astaxie/beego/utils/pagination" -// -// type PostsController struct { -// beego.Controller -// } -// -// func (this *PostsController) ListAllPosts() { -// // sets this.Data["paginator"] with the current offset (from the url query param) -// postsPerPage := 20 -// paginator := pagination.SetPaginator(this, postsPerPage, CountPosts()) -// -// // fetch the next 20 posts -// this.Data["posts"] = ListPostsByOffsetAndLimit(paginator.Offset(), postsPerPage) -// } -// -// -// In your view templates: -// -// {{if .paginator.HasPages}} -// -// {{end}} -// -// See also http://beego.me/docs/mvc/view/page.md package pagination import ( diff --git a/utils/pagination/doc.go b/utils/pagination/doc.go new file mode 100644 index 00000000..db8cd57e --- /dev/null +++ b/utils/pagination/doc.go @@ -0,0 +1,59 @@ +/* + +The pagination package provides utilities to setup a paginator within the +context of a http request. + +Usage + +In your beego.Controller: + + package controllers + + import "github.com/astaxie/beego/utils/pagination" + + type PostsController struct { + beego.Controller + } + + func (this *PostsController) ListAllPosts() { + // sets this.Data["paginator"] with the current offset (from the url query param) + postsPerPage := 20 + paginator := pagination.SetPaginator(this, postsPerPage, CountPosts()) + + // fetch the next 20 posts + this.Data["posts"] = ListPostsByOffsetAndLimit(paginator.Offset(), postsPerPage) + } + + +In your view templates: + + {{if .paginator.HasPages}} + + {{end}} + +See also + +http://beego.me/docs/mvc/view/page.md + +*/ +package pagination From 262665f4e59c8eb504c53b012af3c57066f7d9df Mon Sep 17 00:00:00 2001 From: Jens Bissinger Date: Wed, 8 Oct 2014 15:59:26 +0200 Subject: [PATCH 5/5] Remove PaginationController interface and pass context instead. Refs #835. --- controller.go | 8 -------- utils/pagination/controller.go | 15 ++++----------- utils/pagination/doc.go | 2 +- 3 files changed, 5 insertions(+), 20 deletions(-) diff --git a/controller.go b/controller.go index e7eaa468..72ba323b 100644 --- a/controller.go +++ b/controller.go @@ -93,14 +93,6 @@ type ControllerInterface interface { URLMapping() } -func (c *Controller) GetCtx() *context.Context { - return c.Ctx -} - -func (c *Controller) GetData() map[interface{}]interface{} { - return c.Data -} - // Init generates default values of controller operations. func (c *Controller) Init(ctx *context.Context, controllerName, actionName string, app interface{}) { c.Layout = "" diff --git a/utils/pagination/controller.go b/utils/pagination/controller.go index da92bd69..28473f8a 100644 --- a/utils/pagination/controller.go +++ b/utils/pagination/controller.go @@ -18,16 +18,9 @@ import ( "github.com/astaxie/beego/context" ) -type PaginationController interface { - GetCtx() *context.Context - GetData() map[interface{}]interface{} -} - -// Instantiates a Paginator and assigns it to controller.Data["paginator"]. -func SetPaginator(controller PaginationController, per int, nums int64) (paginator *Paginator) { - request := controller.GetCtx().Request - paginator = NewPaginator(request, per, nums) - data := controller.GetData() - data["paginator"] = paginator +// Instantiates a Paginator and assigns it to context.Input.Data["paginator"]. +func SetPaginator(context *context.Context, per int, nums int64) (paginator *Paginator) { + paginator = NewPaginator(context.Request, per, nums) + context.Input.Data["paginator"] = paginator return } diff --git a/utils/pagination/doc.go b/utils/pagination/doc.go index db8cd57e..df0fa3b7 100644 --- a/utils/pagination/doc.go +++ b/utils/pagination/doc.go @@ -18,7 +18,7 @@ In your beego.Controller: func (this *PostsController) ListAllPosts() { // sets this.Data["paginator"] with the current offset (from the url query param) postsPerPage := 20 - paginator := pagination.SetPaginator(this, postsPerPage, CountPosts()) + paginator := pagination.SetPaginator(this.Ctx, postsPerPage, CountPosts()) // fetch the next 20 posts this.Data["posts"] = ListPostsByOffsetAndLimit(paginator.Offset(), postsPerPage)