Quasar: [BUG] datatable component with rowsNumber param

Created on 19 Jul 2018  路  6Comments  路  Source: quasarframework/quasar

Software version

Operating System Linux(4.15.0-24-generic) - linux/x64
NodeJs 9.11.1

Global packages
NPM 6.0.0
yarn 0.32
quasar-cli 0.16.4
vue-cli 2.9.3
cordova Not installed

Important local packages
quasar-cli 0.16.4 (Quasar Framework CLI)
quasar-framework 0.16.0 (Build responsive websites, PWAs, hybrid mobile apps and Electron apps, all simultaneously using same codebase)
quasar-extras 2.0.2 (Quasar Framework fonts, icons and i18n.)
vue 2.5.16 (Reactive, component-oriented view layer for modern web interfaces.)
vue-router 3.0.1 (Official router for Vue.js 2)
vuex 3.0.1 (state management for Vue.js)
electron Not installed
electron-packager Not installed
electron-builder Not installed
@babel/core 7.0.0-beta.49 (Babel compiler core.)
webpack 4.16.1 (Packs CommonJs/AMD modules for the browser. Allows to split your codebase into multiple bundles, which can be loaded on demand. Support loaders to preprocess files, i.e. json, jsx, es7, css, less, ... and your custom stuff.)
webpack-dev-server 3.1.4 (Serves a webpack app. Updates the browser on changes.)
workbox-webpack-plugin 3.2.0 (A plugin for your Webpack build process, helping you generate a manifest of local files that workbox-sw should precache.)

Networking
Host webscientist-avell
enp3s0f1 192.168.1.3
wlp4s0 192.168.1.4

JsFiddle (for Quasar v0.15+ only)

https://jsfiddle.net/s4mgwohn/8/

What did you get as the error?

pagination and lines per page do not work

What steps did you take, to get the error?

when I comment on the code "rowsNumber: 20," it works again

Most helpful comment

I think you're missing some key points here when using server pagination. Trying to be as complete as possible:

  1. If you set "rowsNumber" you enable server pagination. Why is it required, along with rowsPerPage etc? So that QTable knows how to display the pagination UI at the bottom of the table. You tell it the current page number, how many rows are total and and how many rows each page has. So then it computes the other things: like "entries x - y of Z", if you are on the first or last page (so it can disable the previous/next navigation buttons).

  2. With server pagination, you're in control of what gets displayed for each page. You give it 5 rows as data, then QTable displays 5 rows. You give it 100 rows as data, it will display 100 rows, regardless of page number, total number of rows etc. Don't expect sub-pagination of 100 rows, because it doesn't makes any sense. It defeats the purpose of server pagination. Don't confuse server pagination with client pagination. Choose one or the either, but you can't have both.

  3. When you use server pagination then the server does the sorting, pagination etc. The pagination prop in the case of server pagination only gives hints to QTable as how to build the UI (explained at point 1) and also allows to trigger the right requests for prev/next buttons, which comes to my next very important point below.

  4. In your example, you're totally dismissing the @request event. This is exactly the main point, where the magic happens. Pagination gives hints so QTable knows what to display on the bottom of the component. User click on the prev/next buttons (or changes the rowsPerPage through the QSelect) then QTable triggers @request with the new pagination. You should take the parameters you receive from @request into account and make another request to the server accordingly, then populate the QTable data. In your example, you:

    • enable server pagination by specifying rowsNumber
    • give it N rows -- then N rows it displays

Misunderstanding in your example:

  1. You tell it there's 3 rows per page but you actually populate the table data with 20 rows (!!)
  2. You're disregarding the @request event completely. So when you hit prev/next buttons in QTable, instead of fetching the necessary new rows for the new page number (or whatever), you don't do anything. Yet you expect pagination to work.

Without server pagination -> You fetch all data at startup
WITH server pagination -> You handle pagination through @request and fetching the right data for the specified page number (and ONLY that page number), sorting, filtering etc.

Without server pagination -> QTable handles pagination and what data to display on each page, QTable computes the total number of rows, QTable filters your data, QTable sorts your data
WITH server pagination -> You handle pagination and what to display for each page, you need to supply the total number of rows, you filter the data, you sort the data (through server requests).

This is a simulation of how server pagination works: https://github.com/quasarframework/quasar-play/blob/master/src/pages/showcase/grouping/table/table-server-pagination.vue . Notice the request method, which is the handler for @request QTable event, and the fact that the first request is triggered after the Vue component is mounted, through the Vue component lifecycle method "mounted".

The pseudo-code of how a @request handler should look like:

request (props) { <<<< notice props here

  1. set loading to true to notify user there is a data fetch in progress;
  2. fetch data according to "props.pagination" props (so for a specific page, with the number of rows equal to your rowsPerPage), with the sorting and filtering as told by props.pagination
  3. when data is ready, set QTable data with your received data, and set loading to false to tell QTable we got the data and we're no longer in "loading" state.
    }

Does this makes sense to you?

All 6 comments

When you load data from a server, you're in charge of the pagination. It's the sole purpose of using the server pagination feature, to not have to load all data at page startup (which may be millions of rows). The request method includes what page you should request from server. Take a look again at the examples in doc.

By using server pagination you are assuming that data received from server gets paginated by QTable, which is wrong. You either use server pagination, or the default client pagination.

If you always have a small number of rows, then it makes sense to not use the server pagination. Make an Ajax request, display a loader then show the data table.

This was just an example, but I think it was not so clear, the actual execution triggers a call to a vuex action, and the pagination is a state within that vuex, with two-way bind computed property

In the serverData variable you can see that there is only the result of page 1 of 9, and it has a total of 167 records, which is what I assign to the pagination object in the rowsNumber parameter.

The problem is that if I assign the rowsNumber parameter on the paging object within the vuex the component stops working.

I can not find a way to demonstrate this by jsfiddle.

component.vue

methods: {
    searchList () {
      this.$store.dispatch('patients/searchList')
    }
  },
  computed: {
    serverData () {
      return this.$store.state.patients.list
    },
    loading () {
      return this.$store.state.patients.loading
    },
    filter: {
      get () {
        return this.$store.state.patients.filter
      },
      set (value) {
        this.$store.commit('patients/setFilter', value)
      }
    },
    pagination: {
      get () {
        return this.$store.state.patients.pagination
      },
      set (value) {
        this.$store.dispatch('patients/setPaginationValue', value)
      }
    }

store/patients/action.js

searchList: (context) => {
    context.commit('setResponseStatus', {index: 'list', value: 1})
    let pagination = context.getters.pagination
    const filter = context.getters.filter
    const where = urlEncodeSearch(context.getters.where, 'where')
    const whereHas = urlEncodeSearch(context.getters.whereHas, 'whereHas')
    const module = context.getters.module
    const orderDirection = (pagination.descending) ? 'DESC' : 'ASC'
    const url = `${process.env.HOS_DATA_URL}api/${module.url}?page=${pagination.page}&limit=${pagination.rowsPerPage}&order=${pagination.sortBy},${orderDirection}&filter=${filter}${where}${whereHas}`
    axios
      .get(url)
      .then(({data}) => {
        pagination.rowsNumber = data.total
        context.commit('setPagination', pagination)
        context.commit('setList', data.data)
        context.commit('setLoading', false)
        context.commit('setResponseStatus', {index: 'list', value: 2})
      })
      .catch(error => {
        Notify.create({
          message: 'Erro ao buscar a lista de ' + module.plural + '! ' + error.response.data.message,
          color: 'negative',
          icon: 'fa fa-check-circle'
        })
        context.commit('setLoading', false)
        context.commit('setList', [])
        context.commit('setResponseStatus', {index: 'list', value: 3})
      })
  },
  setPaginationValue: (context, value) => {
    context.commit('setPagination', value)
  }

/store/patients/mutation.js

export const setPagination = (state, value) => {
  state.pagination = value
}

I think you're missing some key points here when using server pagination. Trying to be as complete as possible:

  1. If you set "rowsNumber" you enable server pagination. Why is it required, along with rowsPerPage etc? So that QTable knows how to display the pagination UI at the bottom of the table. You tell it the current page number, how many rows are total and and how many rows each page has. So then it computes the other things: like "entries x - y of Z", if you are on the first or last page (so it can disable the previous/next navigation buttons).

  2. With server pagination, you're in control of what gets displayed for each page. You give it 5 rows as data, then QTable displays 5 rows. You give it 100 rows as data, it will display 100 rows, regardless of page number, total number of rows etc. Don't expect sub-pagination of 100 rows, because it doesn't makes any sense. It defeats the purpose of server pagination. Don't confuse server pagination with client pagination. Choose one or the either, but you can't have both.

  3. When you use server pagination then the server does the sorting, pagination etc. The pagination prop in the case of server pagination only gives hints to QTable as how to build the UI (explained at point 1) and also allows to trigger the right requests for prev/next buttons, which comes to my next very important point below.

  4. In your example, you're totally dismissing the @request event. This is exactly the main point, where the magic happens. Pagination gives hints so QTable knows what to display on the bottom of the component. User click on the prev/next buttons (or changes the rowsPerPage through the QSelect) then QTable triggers @request with the new pagination. You should take the parameters you receive from @request into account and make another request to the server accordingly, then populate the QTable data. In your example, you:

    • enable server pagination by specifying rowsNumber
    • give it N rows -- then N rows it displays

Misunderstanding in your example:

  1. You tell it there's 3 rows per page but you actually populate the table data with 20 rows (!!)
  2. You're disregarding the @request event completely. So when you hit prev/next buttons in QTable, instead of fetching the necessary new rows for the new page number (or whatever), you don't do anything. Yet you expect pagination to work.

Without server pagination -> You fetch all data at startup
WITH server pagination -> You handle pagination through @request and fetching the right data for the specified page number (and ONLY that page number), sorting, filtering etc.

Without server pagination -> QTable handles pagination and what data to display on each page, QTable computes the total number of rows, QTable filters your data, QTable sorts your data
WITH server pagination -> You handle pagination and what to display for each page, you need to supply the total number of rows, you filter the data, you sort the data (through server requests).

This is a simulation of how server pagination works: https://github.com/quasarframework/quasar-play/blob/master/src/pages/showcase/grouping/table/table-server-pagination.vue . Notice the request method, which is the handler for @request QTable event, and the fact that the first request is triggered after the Vue component is mounted, through the Vue component lifecycle method "mounted".

The pseudo-code of how a @request handler should look like:

request (props) { <<<< notice props here

  1. set loading to true to notify user there is a data fetch in progress;
  2. fetch data according to "props.pagination" props (so for a specific page, with the number of rows equal to your rowsPerPage), with the sorting and filtering as told by props.pagination
  3. when data is ready, set QTable data with your received data, and set loading to false to tell QTable we got the data and we're no longer in "loading" state.
    }

Does this makes sense to you?

Makes perfect sense and thank you for the detailed explanation, I was really giving a stupid programmer.

I was able to resolve it before reading your answer here and the problem was all connected to how I wrote @request, passing the pagination and filter parameters by it started to work as expected (it was only having seen the correct signature of the method).

I'll leave the refactored code here:

component.vuejs

methods: {
    searchList (payload) {
      this.$store.dispatch('patients/searchList', payload)
    }
  },
  computed: {
    list () {
      return this.$store.state.patients.list
    },
    loading () {
      return this.$store.state.patients.loading
    },
    filter: {
      get () {
        return this.$store.state.patients.filter
      },
      set (value) {
        this.$store.commit('patients/setFilter', value)
      }
    },
    pagination: {
      get () {
        return this.$store.state.patients.pagination
      },
      set (value) {
        this.$store.dispatch('patients/setPaginationValue', value)
      }
    }
  },
  mounted () {
    this.searchList(
      {
        pagination: this.pagination,
        filter: this.filter
      }
    )
  }

store / patients / action.js

searchList: (context, payload) => {
    context.commit('setResponseStatus', {index: 'list', value: 1})
    let pagination = payload.pagination
    const filter = payload.filter
    const where = urlEncodeSearch(context.getters.where, 'where')
    const whereHas = urlEncodeSearch(context.getters.whereHas, 'whereHas')
    const module = context.getters.module
    const orderDirection = (pagination.descending) ? 'DESC' : 'ASC'
    const url = `${process.env.HOS_DATA_URL}api/${module.url}?page=${pagination.page}&limit=${pagination.rowsPerPage}&order=${pagination.sortBy},${orderDirection}&filter=${filter}${where}${whereHas}`
    axios
      .get(url)
      .then(({data}) => {
        pagination.rowsNumber = data.total
        context.commit('setPagination', pagination)
        context.commit('setList', data.data)
        context.commit('setLoading', false)
        context.commit('setResponseStatus', {index: 'list', value: 2})
      })
      .catch(error => {
        Notify.create({
          message: 'Erro ao buscar a lista de ' + module.plural + '! ' + error.response.data.message,
          color: 'negative',
          icon: 'fa fa-check-circle'
        })
        context.commit('setLoading', false)
        context.commit('setList', [])
        context.commit('setResponseStatus', {index: 'list', value: 3})
      })
  },

/store/patients/mutation.js

export const setPagination = (state, value) => {
  state.pagination = value
}

I would also like to thank you for the excellent tool you are developing, I am sure to become a patron as soon as possible.

Glad you had it figured out. And thanks for thinking of becoming a Patreon. Every cent really helps out in making Quasar even better than what it already is!

Was this page helpful?
0 / 5 - 0 ratings