From a203792b1d5d693203eb786fbb4d67a5f2df9e1f Mon Sep 17 00:00:00 2001 From: Nazar Kanaev Date: Thu, 9 Jul 2020 14:50:23 +0100 Subject: [PATCH] paginate item list --- server/handlers.go | 17 ++++++++-- storage/item.go | 25 +++++++++++++-- template/index.html | 5 +-- template/static/javascripts/app.js | 51 ++++++++++++++++++++++++++++-- 4 files changed, 89 insertions(+), 9 deletions(-) diff --git a/server/handlers.go b/server/handlers.go index f228adb..92c151d 100644 --- a/server/handlers.go +++ b/server/handlers.go @@ -12,6 +12,7 @@ import ( "strings" "path/filepath" "strconv" + "math" ) func IndexHandler(rw http.ResponseWriter, req *http.Request) { @@ -287,7 +288,12 @@ func ItemHandler(rw http.ResponseWriter, req *http.Request) { func ItemListHandler(rw http.ResponseWriter, req *http.Request) { if req.Method == "GET" { + perPage := 20 + curPage := 1 query := req.URL.Query() + if page, err := strconv.ParseInt(query.Get("page"), 10, 64); err == nil { + curPage = int(page) + } filter := storage.ItemFilter{} if folderID, err := strconv.ParseInt(query.Get("folder_id"), 10, 64); err == nil { filter.FolderID = &folderID @@ -299,9 +305,16 @@ func ItemListHandler(rw http.ResponseWriter, req *http.Request) { statusValue := storage.StatusValues[status] filter.Status = &statusValue } - items := db(req).ListItems(filter) + items := db(req).ListItems(filter, (curPage-1)*perPage, perPage) + count := db(req).CountItems(filter) rw.WriteHeader(http.StatusOK) - writeJSON(rw, items) + writeJSON(rw, map[string]interface{}{ + "page": map[string]int{ + "cur": curPage, + "num": int(math.Ceil(float64(count) / float64(perPage))), + }, + "list": items, + }) } else if req.Method == "PUT" { query := req.URL.Query() filter := storage.ItemFilter{} diff --git a/storage/item.go b/storage/item.go index 072db42..1b93841 100644 --- a/storage/item.go +++ b/storage/item.go @@ -96,7 +96,7 @@ func (s *Storage) CreateItems(items []Item) bool { return true } -func (s *Storage) ListItems(filter ItemFilter) []Item { +func listQueryPredicate(filter ItemFilter) (string, []interface{}) { cond := make([]string, 0) args := make([]interface{}, 0) if filter.FolderID != nil { @@ -116,7 +116,11 @@ func (s *Storage) ListItems(filter ItemFilter) []Item { if len(cond) > 0 { predicate = strings.Join(cond, " and ") } + return predicate, args +} +func (s *Storage) ListItems(filter ItemFilter, offset, limit int) []Item { + predicate, args := listQueryPredicate(filter) result := make([]Item, 0, 0) query := fmt.Sprintf(` select @@ -126,7 +130,8 @@ func (s *Storage) ListItems(filter ItemFilter) []Item { join feeds f on f.id = i.feed_id where %s order by i.date desc - `, predicate) + limit %d offset %d + `, predicate, limit, offset) rows, err := s.db.Query(query, args...) if err != nil { s.log.Print(err) @@ -157,6 +162,22 @@ func (s *Storage) ListItems(filter ItemFilter) []Item { return result } +func (s *Storage) CountItems(filter ItemFilter) int64 { + predicate, args := listQueryPredicate(filter) + query := fmt.Sprintf(` + select count(i.id) + from items i + join feeds f on f.id = i.feed_id + where %s`, predicate) + row := s.db.QueryRow(query, args...) + if row != nil { + var result int64 + row.Scan(&result) + return result + } + return 0 +} + func (s *Storage) UpdateItemStatus(item_id int64, status ItemStatus) bool { _, err := s.db.Exec(`update items set status = ? where id = ?`, status, item_id) return err == nil diff --git a/template/index.html b/template/index.html index 17fb6ec..db086d5 100644 --- a/template/index.html +++ b/template/index.html @@ -77,9 +77,9 @@ -
+
+
diff --git a/template/static/javascripts/app.js b/template/static/javascripts/app.js index e97e8a8..c50f19f 100644 --- a/template/static/javascripts/app.js +++ b/template/static/javascripts/app.js @@ -1,5 +1,24 @@ 'use strict'; +var debounce = function(callback, wait) { + var timeout + return function() { + var context = this, args = arguments + clearTimeout(timeout) + timeout = setTimeout(function() { + callback.apply(this, args) + }, wait) + } +} + +Vue.directive('scroll', { + inserted: function(el, binding) { + el.addEventListener('scroll', debounce(function(event) { + binding.value(event, el) + }, 200)) + }, +}) + var vm = new Vue({ el: '#app', created: function() { @@ -17,10 +36,17 @@ var vm = new Vue({ 'feeds': [], 'feedSelected': null, 'items': [], + 'itemsPage': { + 'cur': 1, + 'num': 1, + }, 'itemSelected': null, 'itemSelectedDetails': {}, 'settings': 'create', - 'loading': {newfeed: 0}, + 'loading': { + 'newfeed': false, + 'items': false, + }, } }, computed: { @@ -94,10 +120,29 @@ var vm = new Vue({ }, refreshItems: function() { var query = this.getItemsQuery() - api.items.list(query).then(function(items) { - vm.items = items + this.loading.items = true + var vm = this + api.items.list(query).then(function(data) { + vm.items = data.list + vm.itemsPage = data.page + vm.loading.items = false }) }, + loadMoreItems: function(event, el) { + if (this.itemsPage.cur >= this.itemsPage.num) return + if (this.loading.items) return + var closeToBottom = (el.scrollHeight - el.scrollTop - el.offsetHeight) < 50 + if (closeToBottom) { + this.loading.moreitems = true + var query = this.getItemsQuery() + query.page = this.itemsPage.cur + 1 + api.items.list(query).then(function(data) { + vm.items = vm.items.concat(data.list) + vm.itemsPage = data.page + vm.loading.items = false + }) + } + }, markItemsRead: function() { var vm = this var query = this.getItemsQuery()