Feathers-vuex: Example for Server Side Pagination

Created on 10 Mar 2018  路  5Comments  路  Source: feathersjs-ecosystem/feathers-vuex

I'm struggling to implement server side pagination (standard paginated data table) using feathers-vuex. I currently bind the items of the data table to a computed property like this:

computed: {
  items () {
    // Return the items from the store
    return this.findGetterForItems()
  }
}

Next, I watch for changes in a pagination object (limit, skip, items per page). For each change, I clear the store by commiting clearAll and load the new data into the store using the find (from server) method.

Am I doing this correctly or is there a more "vuexish" way to do it? For questions like this, it would be nice to have a more "complete" example than the chat one...

Fixed in next release documentation

Most helpful comment

I am interested in this one too. What is the recommended way yo query a paginated service

All 5 comments

If clearAll first, that will be a short time empty table in UI
And how do you deal with the total return from server for pagination? Currently map state it from pagination.default.total

I am interested in this one too. What is the recommended way yo query a paginated service

I am also interested. Rigth now I'm using a custom computed maping the ids from the pagination object. Something like that (Assuming you have a "products" service):

import { get } from "lodash";
// ...
computed: {
    collection() {
      const ids = get(this.$store.state, "products.pagination.default.ids", []);
      return ids.map(id => this.$store.state.products.keyedById[id]);
    }
}
//...

Here is the OFFICIAL article on pagination... it's a bit light,
https://feathers-vuex.feathers-plus.com/service-module.html#querying-with-find-pagination

I'm very happy with the results of my implementation. However I need to throw it into a mixin or something to generalize it. Making a demo out of this would be fun

in my data I have

  data () {
    return {
      search: '',
      loading: false,
      scrapPagination: true
    }
  },

Then I check if a page is left.

    ...mapState('user', {
      paginationFull: `pagination`
    }),
    pagination () {
      return this.scrapPagination || this.paginationFull[NS]
    },
    isAPageLeft () {
      if (!this.paginationFull[NS] || this.scrapPagination) {
        return true
      }
      const loadedCount = this.paginationFull[NS].skip + this.paginationFull[NS].limit
      return loadedCount < this.paginationFull[NS].total
    }

Methods is where it gets nasty. One of the goals is to make the user mostly unaware of any loading occurring, as such I load two pages on every go. I use the 10th from last elements on screen presence as a trigger for loading more users. And I ignore pagination object if switching between regular listing and searching. Searching can be intense, so only do it if requested, as opposed to running the regex engine on everything for no reason.

methods: {
    ...mapActions('user', {
      findUsers: 'find'
    }),
    ...mapMutations('user', {
      clearAll: 'clearAll'
    }),
    onScroll () {
      const tolerance = 10
      if (!this.$refs.userEls || !this.isAPageLeft) {
        return
      }
      const triggerEl = this.$refs.userEls.length - tolerance > 0
        ? this.$refs.userEls[this.$refs.userEls.length - tolerance]
        : this.$refs.moreButton
      const that = this
      requestAnimationFrame(() => {
        if (isElementInViewport(triggerEl)) {
          that.loadMore()
        }
      })
    },
    async loadMore (bypass) {
      winston.debug(`${NS}: Loading More:`, this.pagination)
      if (
        bypass ||
        (this.isAPageLeft && !this.loading)
      ) {
        this.loading = true
        winston.debug(`${NS}: Loading More:`, 'FIRE!')
        const skip = this.scrapPagination ? 0 : this.pagination.skip + this.pagination.limit
        this.scrapPagination = false
        await this.findUsers({
          qid: NS,
          query: {
            $limit: LIMIT,
            $sort: SORT,
            $skip: skip,
            $search: this.search ? this.search : undefined
          }
        })
        // Load two pages to begin with
        if (this.isAPageLeft && !bypass) {
          this.loadMore(true)
        }
      }
      this.loading = false
    },
    reset () {
      this.scrapPagination = true
      this.clearAll()
    },
    init () {
      this.reset()
      this.loadMore()
    }
  },
  watch: {
    search: async function () {
      if (!this.search) {
        this.search = undefined
        this.init()
        return
      }
      this.init()
    }
  },
  created () {
    this.init()
    window.addEventListener('scroll', throttle(this.onScroll, 300))
  },
  destroyed () {
    window.removeEventListener('scroll', this.onScroll)
  }
}

I used faker for the dummy data.

@daenuprobst, support for server-side pagination is now baked into the makeFindMixin by default in the 2.0 prerelease. It keeps track of which records return with each set of $limit and $skip params. This also means that the lists are not reactive anymore, even though the data in the lists continues to be reactive. You can set paginate:false in the params to get a reactive list, again.

Was this page helpful?
0 / 5 - 0 ratings