paginate item list

This commit is contained in:
Nazar Kanaev 2020-07-09 14:50:23 +01:00
parent bc627f5d2c
commit a203792b1d
4 changed files with 89 additions and 9 deletions

View File

@ -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{}

View File

@ -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

View File

@ -77,9 +77,9 @@
<img src="./static/images/check.svg" alt="" style="width: 20px; height: 20px;">
</button>
</div>
<div class="p-2 overflow-auto">
<div class="p-2 overflow-auto" v-scroll="loadMoreItems">
<label v-for="item in items" :key="item.id"
class="nav-select mb-1"
class="nav-select"
:class="{'text-muted' : filterSelected=='all' && item.status=='read',
'text-primary': filterSelected=='all' && item.status=='starred'}">
<input type="radio" name="item" :value="item.id" v-model="itemSelected">
@ -93,6 +93,7 @@
</div>
</div>
</label>
<button class="btn btn-link btn-block loading my-3" v-if="itemsPage.cur < itemsPage.num"></button>
</div>
</div>
<div class="vh-100 d-flex flex-column w-100">

View File

@ -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()