Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sorting a paginated table #399

Closed
mathieunicolas opened this issue Jul 9, 2023 · 8 comments · Fixed by #803 · May be fixed by #1476
Closed

Sorting a paginated table #399

mathieunicolas opened this issue Jul 9, 2023 · 8 comments · Fixed by #803 · May be fixed by #1476
Labels
bug Something isn't working

Comments

@mathieunicolas
Copy link

The sorting of a paginated table by a column only applies to the currently displayed page. I know it's normal since we pass a composed, sliced value of the table, but it would be nice if there was an option to pass the original dataset to sort, or to easily setup a callback function that would perform the sort on the variable before slice ?

Version

@nuxthq/ui: 2.5.0
nuxt: 3.6.1

Reproduction Link

https://stackblitz.com/edit/nuxtlabs-ui-ncvb57?file=app.vue

What is Expected?

I'd like to sort the whole dataset before slicing for pagination, but the component applies the sort on the sliced dataset.

@mathieunicolas mathieunicolas added the bug Something isn't working label Jul 9, 2023
@mathieunicolas
Copy link
Author

mathieunicolas commented Jul 9, 2023

update1 : I changed the following :

  • added a prop to UTable component, named "sortingRows", typed as a Function
  • from the component that calls UTable, I created sortingRows (where data is the main data, before slicing) :
const sortingRows = (column, direction) => {
  data.value = orderBy(data.value, column, direction)
}
  • in UTable, I updated lines ~155 :
    const rows = computed(() => {
      if (!sort.value?.column) {
        return props.rows
      }

      const { column, direction } = sort.value
      if(!props.sortingRows) {
        return orderBy(props.rows, column, direction)
      } else {
        props.sortingRows(column, direction)
        return props.rows
      }
    })

The result is a huge loading time (~5 sec) between the call for sorting and the table being sorted... for only 44 items in my array.
It looks like the problem is more about how to optimize the processing time.


update 2 : I rewritten the sorting function without the orderBy function :

const sortingRows = (column, direction) => {
  console.log(direction)
  data.value.sort((a,b) => {
    if(direction === 'asc'){
      return (a[column] - b[column])
    } else if(direction === 'desc') {
      return (b[column] - a[column])
    }})
}

The sorting process is now a lightning speed for 44 items. The remaining problem is the function mutating the array, instead of just returning a sorted version of it. The good point for this method is the user can have full control about how the data is sorted.


update 3 : the last update for now - I managed to plug things the right way (for me) :

  • in UTable.vue :
    const rows = computed(() => {
      if (!sort.value?.column) {
        return props.rows
      }

      const { column, direction } = sort.value
      if(!props.sortingRows) {
        return orderBy(props.rows, column, direction)
      } else {
        return props.sortingRows(column, direction)
      }
    })
  • in the component that calls UTable :
const sortingRows = (column, direction) => {
  console.log(direction)
  return data.value.toSorted((a,b) => {
    if(direction === 'asc'){
      return (a[column] - b[column])
    } else if(direction === 'desc') {
      return (b[column] - a[column])
    }
  }).slice((page.value - 1)*pageCount.value, (page.value)*pageCount.value)
}

The original array is never mutated, the sorting operation is fast and I can control how it's done, and when it's neither asc nor desc everything's back to normal.

The last thing I'd like to improve is the x2 slicing process, as below the sortingRows function I have this one :

const pagiData = computed(() => {
  return data.value.slice((page.value - 1)*pageCount.value, (page.value)*pageCount.value)
})

I don't know if there's any side effect I missed so far.

Copy link
Member

I understand the issue here. Would you mind opening a PR with your changes as it's quite hard to try with codeblocks here.

@clopezpro
Copy link
Contributor

@Smart-Ace-Designs
Copy link

Hello. I am still unable to get a paginated table to sort properly, even after this fix. Has anyone confirmed that #803 indeed fixes this specific problem? Here is the sample code I am trying:

<script setup>
const { data: products } = await useFetch("https://fakestoreapi.com/products");
const page = ref(1);
const pageCount = 10;
const columns = [
  {
    key: "title",
    label: "Title",
    sortable: true,
  },
  {
    key: "price",
    label: "Price",
    sortable: true,
  },
];

const sort = ref({
  column: "title",
  direction: "asc",
});

const rows = computed(() => {
  return products.value.slice(
    (page.value - 1) * pageCount,
    page.value * pageCount
  );
});
</script>

<template>
  <UContainer class="mt-6">
    <UTable :rows="rows" :columns="columns" :sort="sort" />
    <UPagination
      v-model="page"
      :page-count="pageCount"
      :total="products.length"
    />
  </UContainer>
</template>

<style scoped></style>

The first item on the first page should be "Acer SB220Q bi 21.5 inches Full HD (1920 x 1080) IPS Ultra-Thin" but instead it is "Fjallraven - Foldsack No. 1 Backpack, Fits 15 Laptops". I am just starting out learning Vue and Nuxt, so it could totally be something I am doing wrong...due to my inexperience.

image

@clopezpro
Copy link
Contributor

clopezpro commented Oct 30, 2023

Hello. I am still unable to get a paginated table to sort properly, even after this fix. Has anyone confirmed that #803 indeed fixes this specific problem? Here is the sample code I am trying:

<script setup>
const { data: products } = await useFetch("https://fakestoreapi.com/products");
const page = ref(1);
const pageCount = 10;
const columns = [
  {
    key: "title",
    label: "Title",
    sortable: true,
  },
  {
    key: "price",
    label: "Price",
    sortable: true,
  },
];

const sort = ref({
  column: "title",
  direction: "asc",
});

const rows = computed(() => {
  return products.value.slice(
    (page.value - 1) * pageCount,
    page.value * pageCount
  );
});
</script>

<template>
  <UContainer class="mt-6">
    <UTable :rows="rows" :columns="columns" :sort="sort" />
    <UPagination
      v-model="page"
      :page-count="pageCount"
      :total="products.length"
    />
  </UContainer>
</template>

<style scoped></style>

The first item on the first page should be "Acer SB220Q bi 21.5 inches Full HD (1920 x 1080) IPS Ultra-Thin" but instead it is "Fjallraven - Foldsack No. 1 Backpack, Fits 15 Laptops". I am just starting out learning Vue and Nuxt, so it could totally be something I am doing wrong...due to my inexperience.

image

Hello. You tried

v-model:sort="sort"

const rows = computed(() => {
  const { column, direction } = sort.value
  return orderBy(products.value, column, direction).slice((page.value - 1) * pageCount, (page.value) * pageCount)
})

apply your own sorting function or use `import { orderBy } from 'lodash-es'

@clopezpro
Copy link
Contributor

Hello. I am still unable to get a paginated table to sort properly, even after this fix. Has anyone confirmed that #803 indeed fixes this specific problem? Here is the sample code I am trying:

<script setup>
const { data: products } = await useFetch("https://fakestoreapi.com/products");
const page = ref(1);
const pageCount = 10;
const columns = [
  {
    key: "title",
    label: "Title",
    sortable: true,
  },
  {
    key: "price",
    label: "Price",
    sortable: true,
  },
];

const sort = ref({
  column: "title",
  direction: "asc",
});

const rows = computed(() => {
  return products.value.slice(
    (page.value - 1) * pageCount,
    page.value * pageCount
  );
});
</script>

<template>
  <UContainer class="mt-6">
    <UTable :rows="rows" :columns="columns" :sort="sort" />
    <UPagination
      v-model="page"
      :page-count="pageCount"
      :total="products.length"
    />
  </UContainer>
</template>

<style scoped></style>

The first item on the first page should be "Acer SB220Q bi 21.5 inches Full HD (1920 x 1080) IPS Ultra-Thin" but instead it is "Fjallraven - Foldsack No. 1 Backpack, Fits 15 Laptops". I am just starting out learning Vue and Nuxt, so it could totally be something I am doing wrong...due to my inexperience.
image

Hello. You tried

v-model:sort="sort"

const rows = computed(() => {
  const { column, direction } = sort.value
  return orderBy(products.value, column, direction).slice((page.value - 1) * pageCount, (page.value) * pageCount)
})

apply your own sorting function or use `import { orderBy } from 'lodash-es'

This feature is in the development version, it is not in version 2.9

you can use
image

@Smart-Ace-Designs
Copy link

Thanks for the suggestion. After thinking about this for a bit and reading some other posts regarding this, I came to the conclusion that sorting a paginated table might not make the most sense. Maybe on the first page, but then trying to sort on subsequent pages would have a "wierd" user experience. What I ended up doing is sorting the initial results from the useFetch so at least the "first view" of the table, on page one, was correctly sorted. Then after then...it only sorts per page which I think is what the end user would expect. This is basically what you describe in your post...I did a slightly different way but same results.

Thanks again.
SAD

@paidge
Copy link

paidge commented Feb 23, 2024

Here is my solution : https://pastebin.com/MShRWrba

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
5 participants