Building Table Sorting and Pagination in Vue.js

Building Table Sorting and Pagination in Vue.js

Earlier this week I was talking to a good friend of mine (who is also a recent convert to the School of Vue) and he was talking about the troubles he went through in adding table sorting and pagination to a table. He was making use of a particular Vue component that was - to be nice - "undocumented". While I was reasonable certain that other solutions existed, I thought it would be fun to take a stab at writing my own support for table sorting and pagination. Not a generic solution, but just a solution for a particular set of data.

I began with a Vue app that loaded in data via an Ajax call and rendered a table. This initial version has no sorting or pagination, it just loads data and dumps it in the view. Here's the layout:

<div id="app">
  
  <table>
    <thead>
      <tr>
        <th>Name</th>
        <th>Age</th>
        <th>Breed</th>
        <th>Gender</th>
      </tr>
    </thead>
    <tbody>
      <tr v-for="cat in cats">
        <td>{{cat.name}}</td>
        <td>{{cat.age}}</td>
        <td>{{cat.breed}}</td>
        <td>{{cat.gender}}</td>
      </tr>
    </tbody>
  </table>
  
</div>

I've got a table with four columns: Name, Age, Breed, and Gender. And then I simply loop over my cats to render each row. The JavaScript is simple:

const app = new Vue({
  el:'#app',
  data:{
    cats:[]
  },
  created:function() {
    fetch('https://www.raymondcamden.com/.netlify/functions/get-cats')
    .then(res => res.json())
    .then(res => {
      this.cats = res;
    })
  }
})

(As an aside, I say it's simple, but as always, if anything here doesn't make sense, just ask me in the comments below!) While it isn't too exciting, you can see this running below.

See the Pen Vue - Sortable Table by Raymond Camden (@cfjedimaster) on CodePen.

Alright, so for the first update, I decided to add sorting. I made two changes to the view. First, I added click handlers to my headers so I could do sorting. Secondly, I switched my loop to use sortedCats, which I'm going to set up as a Vue computed property. Here's the new HTML:

<div id="app">
  
  <table>
    <thead>
      <tr>
        <th @click="sort('name')">Name</th>
        <th @click="sort('age')">Age</th>
        <th @click="sort('breed')">Breed</th>
        <th @click="sort('gender')">Gender</th>
      </tr>
    </thead>
    <tbody>
      <tr v-for="cat in sortedCats">
        <td>{{cat.name}}</td>
        <td>{{cat.age}}</td>
        <td>{{cat.breed}}</td>
        <td>{{cat.gender}}</td>
      </tr>
    </tbody>
  </table>
  
  debug: sort={{currentSort}}, dir={{currentSortDir}}
</div>

On the JavaScript side, I had to do a few things. First, I added properties to keep track of what I was sorting by and in what direction:

data:{
  cats:[],
  currentSort:'name',
  currentSortDir:'asc'
}

Next, I added the sort method. It has to recognize when we are sorting by the same column and flip the direction:

methods:{
  sort:function(s) {
    //if s == current sort, reverse
    if(s === this.currentSort) {
      this.currentSortDir = this.currentSortDir==='asc'?'desc':'asc';
    }
    this.currentSort = s;
  }
}

Finally, I added my computed property, sortedCats:

computed:{
  sortedCats:function() {
    return this.cats.sort((a,b) => {
      let modifier = 1;
      if(this.currentSortDir === 'desc') modifier = -1;
      if(a[this.currentSort] < b[this.currentSort]) return -1 * modifier;
      if(a[this.currentSort] > b[this.currentSort]) return 1 * modifier;
      return 0;
    });
  }
}

I'm just using the sort method of my array with the property being dynamic. The modifier bit just handles reversing the numbers based on the direction of the sort. You can test this version below:

See the Pen Vue - Sortable Table (2) by Raymond Camden (@cfjedimaster) on CodePen.

By the way, you'll notice some debug data at the bottom of the view. In a real application I'd remove that, but I used that as a handy way to track values while I was clicking. I could have used Vue DevTools for that, although I'm not certain how well they work with CodePens.

Woot! Almost there! For the final version I added pagination. I didn't want to add more cats to my JSON data set, so I used a relatively small "page size" of 3. I began by adding buttons to the front end for pagination:

<p>
<button @click="prevPage">Previous</button> 
<button @click="nextPage">Next</button>
</p>

In the JavaScript I made the following changes. First, I added values to track the page size and current page:

data:{
  cats:[],
  currentSort:'name',
  currentSortDir:'asc',
  pageSize:3,
  currentPage:1
},

Next, I added the prevPage and nextPage methods, which were pretty simple:

nextPage:function() {
  if((this.currentPage*this.pageSize) < this.cats.length) this.currentPage++;
},
prevPage:function() {
  if(this.currentPage > 1) this.currentPage--;
}

Finally, I modified my computed property to check the page size and current page values when returning data. I did this via a filter call:

sortedCats:function() {
	return this.cats.sort((a,b) => {
		let modifier = 1;
		if(this.currentSortDir === 'desc') modifier = -1;
		if(a[this.currentSort] < b[this.currentSort]) return -1 * modifier;
		if(a[this.currentSort] > b[this.currentSort]) return 1 * modifier;
		return 0;
	}).filter((row, index) => {
		let start = (this.currentPage-1)*this.pageSize;
		let end = this.currentPage*this.pageSize;
		if(index >= start && index < end) return true;
	});
}

Note the creation of a start and end value. I almost always screw this up so I created variables instead of a super complex if statement. While my code seems to work I'm still not 100% sure that math is right. And here is that final version:

See the Pen Vue - Sortable Table (3) by Raymond Camden (@cfjedimaster) on CodePen.

So that's it. There is definitely room for improvement. I'd like to add disabled to the buttons when they are at the 'edge' and I'd like to highlight, somehow, the table headers when sorting, but I'll leave that as an exercise to my readers. ;) (Don't forget you can fork my CodePens and add your own edits!)

Header Photo by Hannes Egler on Unsplash

Archived Comments

Comment 1 by Che Vilnonis posted on 2/9/2018 at 4:29 PM

Thanks for this post Ray. How would your code change if this code appeared on a child component of App.vue? In using your example (Vue - Sortable Table 2), I'm getting the error "TypeError: this.cats is undefined" w/in the 'export default {)' section of the child component. I can't determine of this is a Vue lifecycle or prop/slot issue between App.vue and the child component or something else?

Comment 2 (In reply to #1) by Raymond Camden posted on 2/9/2018 at 4:40 PM

Do you have a CodePen for this?

Comment 3 (In reply to #2) by Che Vilnonis posted on 2/9/2018 at 4:54 PM

I'm using Vuetify/Vue to display a bunch of pages that are simple 'vue snippets' of code as a reference/learning exercise. I wanted
to add a new snippet, based on this blog post. I've included my App.vue,
main.js and TableSort.vue in one gist.
https://gist.github.com/ano...

Comment 4 (In reply to #3) by Raymond Camden posted on 2/9/2018 at 6:19 PM

I'm not quite sure what is wrong. I haven't done much with "multi component" Vue apps yet so I'm still a bit unsure in that area. If you have a complete project I could clone and run I could take a stab at it.

Comment 5 (In reply to #4) by Che Vilnonis posted on 2/9/2018 at 6:33 PM

See if this works. Will need to install dependencies. FTR, your 'Form Stepper' example is there as well. :) https://filetea.me/n3wSdH2p...

Comment 6 (In reply to #4) by Che Vilnonis posted on 2/9/2018 at 6:34 PM

And I had no issues getting your code to work as a single standalone page.

Comment 7 (In reply to #5) by Raymond Camden posted on 2/9/2018 at 7:26 PM

Link doesn't seem to work.

Comment 8 (In reply to #7) by Che Vilnonis posted on 2/9/2018 at 7:28 PM

Sorry. Timed out I guess. No worries.

Comment 9 by Geo posted on 2/13/2018 at 10:28 PM

Very informative

Are there any repositories on github to download the example?

Comment 10 (In reply to #9) by Raymond Camden posted on 2/13/2018 at 11:26 PM

No but you can download from the CodePen embeds.

Comment 11 (In reply to #9) by Geo posted on 2/14/2018 at 7:42 AM

Thank you Raymond for the quick response

One more question, can you change the source, instead the json to scrape the data from a remote table/page using xpath or something like this. I have seen recently you have done an scraping example in Firefox.

Comment 12 (In reply to #11) by Raymond Camden posted on 2/14/2018 at 1:56 PM

Sure you can change the source, but be aware that you would need to change the code too obviously. So my table, for example, is hard coded for the data.

Comment 13 (In reply to #9) by Juan Antonio Navarro Jimenez posted on 2/19/2018 at 9:13 PM

You can download from codepen with export button.

Comment 14 by Julious Lara posted on 4/30/2018 at 1:30 AM

hi raymond, thank you so much for this tutorial. I am new in vue js and I wondered how to put a search input on the top of the table.

Comment 15 (In reply to #14) by Julious Lara posted on 4/30/2018 at 1:59 AM

by adding this line of codes solved my problem. thanks again raymond.

var self=this;
return this.categories.filter(function(ctg){return ctg.title.toLowerCase().indexOf(self.search.toLowerCase())>=0;});

Comment 16 (In reply to #15) by Raymond Camden posted on 4/30/2018 at 12:22 PM

Glad you got it!

Comment 17 by bubs posted on 5/1/2018 at 1:56 AM

excellent tutorial for anyone learning the basics. next would be adding a check box or button column!!

Comment 18 (In reply to #17) by Raymond Camden posted on 5/1/2018 at 1:27 PM

Can you clarify what you mean?

Comment 19 (In reply to #18) by bubs posted on 5/1/2018 at 3:02 PM

adding check box columns or button columns so that actions can be taken on a row. but all in all a very good tutorial.

there is one issue that i fixed this computed and that is that you have to make a copy of the array with .slice() function to make sure you're not modifying the original (sideeffect).

Comment 20 (In reply to #19) by Raymond Camden posted on 5/1/2018 at 3:32 PM

Ah in theory then thats just a table cell with @click="doSomething(cat)"

Comment 21 by Sam Singh posted on 5/17/2018 at 7:13 PM

Excellent tutorial. I would like to show the prev and next buttons if and only if there are going to be prev and next elements. I tried the following code to hide the prev button on the first page but didn't quite work:

if(this.currentPage < 1) {this.showprev = 0} else {this.showprev = 1} and then in html v-if="showprev > 0"

I would like to do something similar to show the next page too

Comment 22 (In reply to #21) by Raymond Camden posted on 5/17/2018 at 8:09 PM

Here is the first version - but it has too much logic in the view. Going to fix that:

https://codepen.io/cfjedima...

Comment 23 (In reply to #22) by Raymond Camden posted on 5/17/2018 at 8:11 PM

And this version is nicer imo: https://codepen.io/cfjedima...

Comment 24 (In reply to #23) by Sam Singh posted on 5/17/2018 at 9:35 PM

It worked. One more minor issue that I have found is that when I do numerical sorting, it doesn't sort by the amount. Instead it looks at the first number and sorts accordingly. For example, 2000 will appear after 200 even though 300 and 500 are on the list.

Comment 25 (In reply to #24) by Raymond Camden posted on 5/17/2018 at 9:40 PM

Good find. Give me 10.

Comment 26 (In reply to #25) by Raymond Camden posted on 5/17/2018 at 9:41 PM

So wait - in my demo, I can sort by age just fine. Do you see that?

Comment 27 (In reply to #26) by Sam Singh posted on 5/17/2018 at 11:02 PM

Strange - May be because I I have decimals in my numbers. This is the logic I have for the sorted products.

sortedProds: function() {
return this.filteredProds.sort((a,b) => {
let modifier = 1;
if(this.currentSortDir === 'desc') modifier = -1;
if(a[this.currentSort] < b[this.currentSort]) return -1 * modifier;
if(a[this.currentSort] > b[this.currentSort]) return 1 * modifier;
return 0;
})

Comment 28 (In reply to #27) by Raymond Camden posted on 5/17/2018 at 11:10 PM

In theory it shouldn't matter - do you have your JSON data available online?

Comment 29 (In reply to #28) by Sam Singh posted on 5/17/2018 at 11:14 PM

[
{
"id": 124,
"name": "iPhone X 256GB Silver",
"image": "",
"purchasedate": "2018-05-14T07:00:00.000Z",
"price": "124.00"
},
{
"id": 125,
"name": "Memorex - PurePlay Portable Speaker",
"image": "",
"purchasedate": "2018-05-14T07:00:00.000Z",
"price": "345.00"
}

Comment 30 (In reply to #29) by Raymond Camden posted on 5/17/2018 at 11:18 PM

Your prices aren't numbers - they are strings. Remove the quotes around them.

"price":99.99

Comment 31 (In reply to #30) by Sam Singh posted on 5/17/2018 at 11:22 PM

Ok I will try that. I will probably have to do that on the client side somehow because they are coming from an api.

Comment 32 (In reply to #31) by Raymond Camden posted on 5/17/2018 at 11:23 PM

Ah. If so - iterate over the array and use parseFloat - https://developer.mozilla.o....

Comment 33 by Marc Bosse posted on 5/29/2018 at 5:29 PM

Great tutorial, was able to tie this logic into an existing Vue.js + Laravel app. Nice and elegant, thank you very much!

Comment 34 by Joe LoMoglio posted on 6/26/2018 at 5:30 AM

You should be aware that you can not have the sortedCats as a computed property if you plan to use this as a basis for a re-usable component, it will cause an infinite render loop, because the v-for loop will constantly update, because applying the sort causes an update then the update causes an update and so on and so on. You have to have the sortedCats computed property be a function in your methods and then add watch for the currentSort and currentSortDir that calls the sortedCats method.

watch: {
'currentSort': 'sortedCats',
'currentSortDir': 'sortedCats'
}

Comment 35 (In reply to #34) by Raymond Camden posted on 6/26/2018 at 6:17 PM

Interesting - can you explain why it works ok as a bare Vue app but not as a component?

Comment 36 (In reply to #35) by Joe LoMoglio posted on 6/26/2018 at 6:33 PM

I am by no means an expert, but I discovered when you have more than one instance of it as a reusable component it causes this side effect.

I loved your tutorial as it gave me the perfect starting point for my data table, I have added complete pagination, hide/show columns and filtering, then went to use it in my project and got the error.

It took me about 5 hours of searching on Google before I found the explanation i posted. It boils down to that when you click on the header to sort the column - that click event causes the component to re-render and then the computed property detects the change and also causes a re-render, and that re-render causes the computed property to re-render again and therefore the infinite loop.

By using a watcher to call the sort, it prevents the sort functionality from triggering a re-render.

I just wanted to save anyone who uses your tutorial the headache I ran into if they wanted to have multiple tables in a single vue instance.

Comment 37 (In reply to #36) by Raymond Camden posted on 6/26/2018 at 7:10 PM

Fascinating. Thank you for sharing this. I'm going to build a demo that replicates the issue and then show the fix. I'll do this in a blog post and then delete these comments so I can take credit and - oh wait - I'm typing this - what I mean is - I'll post a follow up and credit you! :)

Comment 38 (In reply to #36) by Raymond Camden posted on 6/26/2018 at 8:54 PM

Query - can you share your code? Im curious about how this bug is occuring. It sounds like the array is being passed by ref instead of by copy.

Comment 39 (In reply to #37) by Joe LoMoglio posted on 6/26/2018 at 8:59 PM

LOL!!! I plan to give you credit in my code, for your code that I used. I am developing a new Vue component library called Vui and the beta release will be launched soon, hope to have the official launch end of July. I'll let you know.

Comment 40 (In reply to #39) by Raymond Camden posted on 6/26/2018 at 9:03 PM

Feel free to drop me an email (if you don't mind) when you go live - I'd like to find time to review it on the blog.

Comment 41 (In reply to #40) by Joe LoMoglio posted on 6/26/2018 at 9:04 PM

Excellent will do!!!

Comment 42 by b1012 posted on 6/30/2018 at 3:17 AM

How would this example work with null values intermixed in the dataset, if we want the null values to come at the end?

Comment 43 (In reply to #42) by Raymond Camden posted on 6/30/2018 at 9:33 PM

Fork the CodePen and try - then let us know. :)

Comment 44 (In reply to #43) by b1012 posted on 6/30/2018 at 10:04 PM

Sorry should have been more specific haha - I have tried this and it messes up the sorting, not sure exactly why. It goes through a specific pattern (like 5 different type of sorts), maybe dependent on the # of null values? Not sure, was wondering if anyone else ran into this bug and whether they found a fix.

Comment 45 (In reply to #44) by b1012 posted on 7/1/2018 at 5:20 PM

Actually just solved this. Just make sure to also do a check for typeof a[this.currentSort] == 'undefined') and b[this.currentSort] == 'undefined') during the sortedRows() function. Sort accordingly (put null values at either the end or beginning).

Comment 46 (In reply to #45) by Raymond Camden posted on 7/2/2018 at 12:37 PM

Sweet - thank you for sharing what worked!

Comment 47 by Aditya Chauhan posted on 7/15/2018 at 3:40 PM

how to add search on this table..?

Comment 48 (In reply to #47) by Raymond Camden posted on 7/16/2018 at 10:45 AM

It would be a few steps. First, add some UI to let you type, an input field. Then instead of using a data field (cats), you would use a computed property where the array is filtered by the search term.

Comment 49 (In reply to #48) by Raymond Camden posted on 7/16/2018 at 10:46 AM

Oops, I forgot I was already using a computed property - sortedCats. You would make *that* logic also do filtering. That may impact paging though.

Comment 50 (In reply to #49) by Raymond Camden posted on 7/16/2018 at 11:05 AM

So I worked up this - https://codepen.io/cfjedima.... But I feel like when you change the filter it should reset to page 1.

Comment 51 (In reply to #50) by Aditya Chauhan posted on 7/16/2018 at 4:41 PM

hey thanks man for yours help..i was asking because you already using a computed property .

Comment 52 (In reply to #50) by br00klyn posted on 7/19/2018 at 1:44 AM

You can add a watcher for that:

watch: {
search: function() {
this.currentPage = 1
}
},

Comment 53 (In reply to #52) by Raymond Camden posted on 7/19/2018 at 9:52 PM

Try forking my codepen with that. :)

Comment 54 (In reply to #0) by Raymond Camden posted on 7/31/2018 at 6:25 PM

Got a Code-Pen I can fork?

Comment 55 (In reply to #0) by Raymond Camden posted on 7/31/2018 at 7:23 PM

Err wait... this isn't my code at all. You are using a Vue plugin?

Comment 56 by Nebojsa posted on 8/21/2018 at 1:37 PM

I have some problems with sorting...

This is sort by Price https://drive.google.com/op...
This is sort by ID https://drive.google.com/op...
This is sort by Name https://drive.google.com/op...

It is sorting numbers by first number 1, 10, 100, 2, 20, 200, 3... And letters by random as I can see.

Can You help me somehow? What can make this kind of problem?

Comment 57 (In reply to #56) by Raymond Camden posted on 8/21/2018 at 2:16 PM

Instead of sharing pics, please share a Code-Pen I can run and test.

Comment 58 (In reply to #57) by Nebojsa posted on 8/22/2018 at 6:19 AM

https://codepen.io/bonitasr...
I had no Code-Pen so I needed to create it, hope it is ok because I use Code-Pen for the first time... I'm sorry I late.

Comment 59 (In reply to #57) by Nebojsa posted on 8/23/2018 at 11:04 AM

I figured out what is problem... This plugin sort per ASCII kod and 4 > 1 and doesn't metter if one number is 4 and other is 19999, it compares only first character.

Can you help me with something like IF statement here:
computed:{
sortedProducts:function() {
return this.products.sort((a,b) => {
let modifier = 1;
if(this.currentSortDir === 'desc') modifier = -1;
if(a[this.currentSort] < b[this.currentSort]) return -1 * modifier;
if(a[this.currentSort] > b[this.currentSort]) return 1 * modifier;
return 0;});
}}

and combined with this:
function compareNumbers(a, b) {
return a - b;
}

Comment 60 (In reply to #59) by Raymond Camden posted on 8/23/2018 at 2:54 PM

Right, this is expected as the sort is going to assume you are sorting strings, not numbers. Is your price data properly set as number values and not strings? An easy way to do that would be when you load your data, loop over it and do something like: x = parseInt(x, 10);.

Comment 61 (In reply to #60) by Nebojsa posted on 8/27/2018 at 1:44 PM

I had some other stuffs to do so I didn't work on this.

How can I check which <th> is clicked and if it's "name" then to use Your code and if it is number (id, price or pzn) to use parseFloat()...

I have problem with checking which fields is clicked. I'm pretty new with Vue.

Comment 62 by Inginiero posted on 8/29/2018 at 9:28 AM

Amazing how simple and clean this solution is! What about adding some css icons on the <th> to show the sorting direction?

Comment 63 (In reply to #61) by Raymond Camden posted on 8/29/2018 at 2:00 PM

Is your TH dynamic?

Comment 64 (In reply to #63) by Nebojsa posted on 9/4/2018 at 6:18 AM

No, it is static and it won't be changed I think ever.

Comment 65 (In reply to #64) by Raymond Camden posted on 9/4/2018 at 1:22 PM

Modify the click event to pass in the name.

Comment 66 by Jared Beiswenger posted on 9/14/2018 at 1:21 PM

Wonderful tutorial. Shouldn't changing a[this.currentSort] to a[this.currentSort].toUpperCase() give me case insensitive sorting? I'm getting a Cannot read property 'toUpperCase' of undefined error...

Comment 67 (In reply to #66) by Raymond Camden posted on 9/14/2018 at 2:48 PM

It should work - can you setup a CodePen with this and share the URL?

Comment 68 by slidenerd posted on 9/17/2018 at 9:32 AM

Just wanted to say you are so totally badass man! I swear I was thinking about which component to go for but you simplified it like it was nothing! If it isnt too much to ask, could you kindly add a follow up post to this that also covers filtering, would be super awesome if you could do that

Comment 69 (In reply to #67) by Jared Beiswenger posted on 9/18/2018 at 8:09 PM

I just verified that my code is actually identical to yours except I changed "cats" to "games". But it currently sorts numbers, and I'm guessing toUpperCase will cause an issue if you pass it a number. Anyways, I'll throw up a CodePen if I can't figure it out. Thanks!

Comment 70 (In reply to #68) by Raymond Camden posted on 9/19/2018 at 2:18 PM

Thank you for the kind words (and if this helps, visit the Amazon Wishlist linked to above ;) - try this - I wrote it pretty quickly though:

https://codepen.io/cfjedima...

Comment 71 by Aigars posted on 9/25/2018 at 12:59 PM

Hello, can u help me to make pagination like in this screenshot? I want to see all pages and click on page what I want. Now its only works to previous and next page
https://uploads.disquscdn.c...

Comment 72 (In reply to #71) by Raymond Camden posted on 9/29/2018 at 1:44 PM

It's possible - you would need to determine how many total pages you have first. I can help and I think it would be a good followup, but I may not get to it for a while - have a busy couple of weeks coming up.

Comment 73 (In reply to #72) by Raymond Camden posted on 10/2/2018 at 1:46 PM

Thoughts on this version? https://codepen.io/cfjedima...

Comment 74 by MilMike posted on 10/10/2018 at 9:01 AM

Thank you for the amazing code! simple and working. I ran also in another problem: how can I sort by 2 columns? eg. Sort by country asc/desc and name asc? I extended your code and just appended 2 columns in the comparision. It works but when I sort country by desc, also names are sorted by desc. Any thoughts?

 
sortedContacts: function(){
var modifier = 1;
var th = this;
return this.contacts.sort(function(a,b){
if(th.currentSortDir === 'desc') {modifier = -1;}

//Values for comparisions will be put in these vars:
var aString = ''; var bString = '';

if(typeof a[th.currentSort] === 'undefined'){
aString = '';
}else{
if(a[th.currentSort] === null){
aString = '';
}else{
aString = a[th.currentSort].toLowerCase();
}
}

if(typeof b[th.currentSort] === 'undefined'){
bString = '';
}else{
if(b[th.currentSort] === null){
bString = '';
}else{
bString = b[th.currentSort].toLowerCase();
}
}

if(th.currentSort2.length > 0){
//Should we sort also by a 2nd key? append the value of the 2nd screen.
aString += a[th.currentSort2].toLowerCase();
bString += b[th.currentSort2].toLowerCase();
}

if(aString < bString){return -1 * modifier;}
if(aString > bString){return 1 * modifier;}

return 0;
});
}

Comment 75 (In reply to #74) by Raymond Camden posted on 10/23/2018 at 1:28 PM

Honestly all I can suggest is what I'd do - Google for sorting JavaScript arrays by two criteria.

In theory you would do something like - if a == b (ie the first sort criteria matches) then you compare c and d- the second criteria.

Comment 76 by john andrew posted on 10/28/2018 at 2:11 AM

i need this. thanks for the wonderfull article

Comment 77 by Viresh Hemraj posted on 2/15/2019 at 1:40 PM

Thanks this worked for me....

Comment 78 by JoeyMaker posted on 4/14/2019 at 11:47 AM

Thanks bro

Comment 79 (In reply to #37) by Jared Pellegrini posted on 4/24/2019 at 5:32 PM

Hi! This tutorial was easy to follow and greatly helped me learn some tricks for sorting a table in Vue. But I'm having the same problem with the infinite loop on a reusable component. Wondering if you ever got a demo working to replicate the issue. Thanks!

Comment 80 (In reply to #79) by Jared Pellegrini posted on 4/25/2019 at 5:38 PM

I don't know if this solution will work for everyone, but here's how I fixed the infinite loop problem in my setup:

I changed sortedCats from a computed property to a method, and I renamed it getSortedCats. I created a new variable in data called sortedCats, and set its default value = cats. Then instead of having my v-for loop over the computed property, I have it looping over the sortedCats variable. The last line of my sort() function now looks like this:

this.sortedCats = this.getSortedCats();

So sortedCats only changes when the sort() function is invoked. For me, this wound up satisfying my use case. It works faster than my previous method and didn't require writing a custom watcher. Again, this may not be a solution for everyone, but I hope it helps.

Comment 81 (In reply to #80) by Raymond Camden posted on 4/26/2019 at 2:34 PM

Thanks for sharing this, Jared!

Comment 82 by Phil D'Agostino posted on 8/16/2019 at 4:32 PM

Raymond,

This is great code for sorting a vue table. I wanted to add filtering per column. I already have the filter code working in my code, but could not figure out how to add sorting to it! I'm kind of a beginner at Vue ( though a few years in Javascript ). Any direction you could give me? Thanks, man.

Comment 83 (In reply to #82) by Raymond Camden posted on 8/16/2019 at 7:25 PM

You want filtering *per* column? Like you imagine one input field above each column to allow for filtering?

Comment 84 (In reply to #83) by Phil D'Agostino posted on 8/16/2019 at 7:41 PM

Raymond,

Thank you much for the
quick response!

I've already got Vue code to filter on more than one column.what I couldn't figure out was how then to sort the whole table by a column. Using Vue I can either filter or sort, but not both..

E.g. show me every record where building = 24, then sort the whole table by room

Comment 85 (In reply to #84) by Raymond Camden posted on 8/16/2019 at 7:45 PM

Ok, so have you tried adding in this code? If so, how did it fail?

Comment 86 (In reply to #85) by Phil D'Agostino posted on 8/16/2019 at 9:37 PM

Let me post the code I have...no time to do it this moment..

Comment 87 (In reply to #86) by Phil D'Agostino posted on 8/17/2019 at 2:06 AM

Raymond, here is my code so far for filtering per column. Some of your sorting code is there, but as I say I could not figure out how to add it to the same table.

My table uses filteredBlogs as the source array ( computed property). How can I

use sortedBlogs in addition?

Thanks for any insights...As I say I am near a beginner at Vue

[code - note this code is run in the Webpack local dev server..]
// filename = showBlogs.vue
//

<template>

<div id="show-blogs" v-on:keyup.enter="go()">
<h1>All Blogs</h1>
<input type="text" id="tsearch1" placeholder="lens"/>
<input type="text" id="tsearch2" placeholder="type"/>
<input type="text" id="tsearch3" placeholder="stn"/>
<input type="text" id="tsearch4" placeholder="c_start"/>
<input type="text" id="tsearch5" placeholder="destination"/>
<input type="text" id="tsearch6" placeholder="room"/>
<input type="text" id="tsearch7" placeholder="user"/>
<button v-on:click="go()">GO</button>
<div>

<table id="tdisplay">
<thead>
<tr>
<th @click="sort('lens')">LENS</th>
<th @click="sort('typ')">TYPE</th>
<th @click="sort('stn')">STN</th>
<th @click="sort('c_start')">C_START</th>
<th @click="sort('destination')">DESTINATION</th>
<th @click="sort('room')">ROOM</th>
<th @click="sort('user')">USER</th>
</tr>
</thead>

<tbody>
<tr v-for="blog in filteredBlogs">

<td>{{ blog.lens}}</td>
<td>{{ blog.typ }}</td>
<td>{{ blog.stn }}</td>
<td>{{ blog.c_start }}</td>
<td>{{ blog.destination }}</td>
<td>{{ blog.room }}</td>
<td>{{ blog.user }}</td>
</tr>

</tbody>
</table>

</div>

</div>
</template>

<script>

export default {

data () {
return {
blogs:[],
currentSort:'lens',
currentSortDir:'asc',
search1: '',
search2: '',
search3: '',
search4: '',
search5: '',
search6: '',
search7: ''
} // return
}, //data
methods:{
go: function(){
// alert("Go button was clicked");
this.search1 = tsearch1.value;
this.search2 = tsearch2.value;
this.search3 = tsearch3.value;
this.search4 = tsearch4.value;
this.search5 = tsearch5.value;
this.search6 = tsearch6.value;
this.search7 = tsearch7.value;

},
sort : function(s){

//if s == current sort, reverse
if(s === this.currentSort) {
this.currentSortDir = this.currentSortDir==='asc'?'desc':'asc';
} // end if
this.currentSort = s;
console.log(this.currentSort);
} // end sort

}, // end methods..
created(){
this.blogs = [
{
'lens': '000001',
'typ': 'S',
'stn': '1234',
'c_start': 'K2-123',
'destination': 'Verrill',
'room': '88',
'user' : 'phild'

},
{
'lens': '003004',
'typ': 'S',
'stn': '5678',
'c_start': 'K2-456',
'destination': '118',
'room': '127',
'user' : 'Ho, Janet'

},
{
'lens': '000034',
'typ': 'S',
'stn': '3434',
'c_start': 'SC-057',
'destination': 'Argiro',
'room': '88',
'user' : 'phild'

},
{
'lens': '003007',
'typ': 'S',
'stn': '209-1111',
'c_start': 'LB-066',
'destination': 'LBSW',
'room': '200',
'user' : 'Circulation Desk'

},
{
'lens': '002145',
'typ': 'S',
'stn': '469-6666',
'c_start': 'LB-123',
'destination': "Men's Peace Palace",
'room': '23',
'user' : 'MVVT Line'

},
{
'lens': '001145',
'typ': 'v',
'stn': '2346',
'c_start': '',
'destination': 'Verrill',
'room': '25',
'user' : 'Vacant Port'

}

]
},
computed: {
filteredBlogs: function(){
return this.blogs.filter( (blog) => {

return ( blog.lens.toLowerCase().match(this.search1.toLowerCase())
&& blog.typ.toLowerCase().match(this.search2.toLowerCase())
&& blog.stn.toLowerCase().match(this.search3.toLowerCase())
&& blog.c_start.toLowerCase().match(this.search4.toLowerCase())
&& blog.destination.toLowerCase().match(this.search5.toLowerCase())
&& blog.room.toLowerCase().match(this.search6.toLowerCase())
&& blog.user.toLowerCase().match(this.search7.toLowerCase()) );

}) // end filter()

},
sortedBlogs:function() {

return this.blogs.sort((a,b) => {
let modifier = 1;
if(this.currentSortDir === 'desc') modifier = -1;
if(a[this.currentSort] < b[this.currentSort]) return -1 * modifier;
if(a[this.currentSort] > b[this.currentSort]) return 1 * modifier;
return 0;
}); // end blogs.sort
} // end sorted blogs

}, // end computed
// declare locally - applies only to this component
filters:{
'touppercase': function(value){
return value.toUpperCase();
}

}

}// export
</script>

<style>

#show-blogs{

max-width: 800px;
margin: 0 auto;
}

#tdisplay{
margin: 20px auto;
border: 1px solid black;
border-collapse:collapse;

}

table tbody tr td{
border:1px solid black;
padding:5px;

}

table tr th{
border:1px solid black;
padding:5px;

}
a {
color: blue;

}

</style>

Comment 88 by Vibhu Sharma posted on 10/11/2019 at 3:50 PM

Its a great tutorial! Thanks for sharing it. I have successfully implemented this. Now say I have another column (an action column) which has delete button. My problem is when I sort the other columns, the button column does not get sorted at all and stays the same. How can I make sure that button column also changes when I sort any other column?

Comment 89 (In reply to #88) by Vibhu Sharma posted on 10/11/2019 at 3:52 PM

My full question is here: https://stackoverflow.com/q...

can you please answer it?

Comment 90 (In reply to #88) by Raymond Camden posted on 10/11/2019 at 4:24 PM

Do you have an online demo? Your SO question is about *another* code, not my demo. If you used my demo and can share an example via CodePen, I'd be happy to try.

Comment 91 (In reply to #90) by Vibhu Sharma posted on 10/11/2019 at 4:51 PM

https://codepen.io/Vibhu92/...

Here is your example which I have edited. Can you do the following on this codepen:

1. Make delete button show only for rows having Age =2
2. Make the button column stick only to the rows having Age = 2 when you sort the other columns
3. When clicked on the "delete" button, display an html alert box

Comment 92 (In reply to #91) by Raymond Camden posted on 10/11/2019 at 5:10 PM

1. That can be done with v-if: <button v-if="cat.age = 2">Delete</button>
2. When I tried this, it worked for me as I expected it would.
3. For that you would use Vue's event handlers, so: @click="alert('something')"

Comment 93 (In reply to #92) by Vibhu Sharma posted on 10/11/2019 at 5:16 PM

Thanks but when I use v-if="cat.age = 2" , the table only shows the rows having Age=2. Other rows are disappeared from the table view. Did you get the same ?

here is the updated codepen: https://codepen.io/Vibhu92/...

Comment 94 (In reply to #93) by Raymond Camden posted on 10/11/2019 at 5:30 PM

Sorry, should be ==, not =.

Comment 95 (In reply to #94) by Vibhu Sharma posted on 10/11/2019 at 7:23 PM

Cool, thanks a lot!

Comment 96 by Kunwoo Kim posted on 1/13/2020 at 1:55 AM

Hello Raymond! Thank you for great tutorial!, but its not sorted well with null value. :(
https://codepen.io/cfjedima...

Comment 97 (In reply to #96) by Kunwoo Kim posted on 1/13/2020 at 2:15 AM

so I edited code little bit to it works!

let modifier = 1;
var va = (a[this.currentSort] === null) ? "" : a,
vb = (b[this.currentSort] === null) ? "" : b;
if(this.currentSortDir === 'desc') modifier = -1;
if(va < vb) return -1 * modifier;
if(va > vb) return 1 * modifier;
return 0;

Comment 98 by Ruslan Nasonov posted on 1/31/2020 at 8:27 AM

How to change `sortedCats` if sorting going on server? Is the Vue support async computed?

Comment 99 (In reply to #98) by Raymond Camden posted on 1/31/2020 at 3:29 PM

I'm not quite sure what your second sentence is asking. You can definitely use async/await with Vue. In my demo, all of the data is available client-side. It would absolutely be possible to build a table sorting/pagination app where the data is fetched via Ajax. I think this would be a good demo and I'll add it to my queue of stuff to write, but it IS doable in Vue.

Comment 100 (In reply to #99) by Raymond Camden posted on 1/31/2020 at 8:15 PM

Ok, I built this which demonstrates paging an sorting via remote APIs: https://codepen.io/cfjedima.... I'll do a blog post later on it.

Comment 101 (In reply to #100) by Raymond Camden posted on 2/1/2020 at 5:04 PM

Check out this update - https://www.raymondcamden.c....

Comment 102 (In reply to #101) by Ruslan Nasonov posted on 2/11/2020 at 7:51 AM

Cool demo, thanks!

Comment 103 (In reply to #96) by Emil Atanasov posted on 2/16/2020 at 9:19 AM

I believe that the sorting doesn't work fine with string values, that have empty space. I'm using the following sort function:




sort((a, b) => {


let field = this.currentSort


let modifier = 1;


if (this.currentSortDir === "desc") modifier = -1;




if (typeof a[field] === 'string') {


return modifier * ('' + a[field]).localeCompare(b[field]);


}


// console.log("Barbara Noz < Stanley Hummer ", "Barbara Noz" < "Stanley Hummer")


// console.log("Cmp: ", a[this.currentSort], " < ", b[this.currentSort], " = ", a[this.currentSort] < b[this.currentSort])





if (a[field] < b[field]) return -1 * modifier;


if (a[field] > b[field]) return 1 * modifier;


return 0;


});


Comment 104 (In reply to #103) by Raymond Camden posted on 2/17/2020 at 3:04 PM

Can you share what you are seeing? Ie what is incorrect. If your expectation is that it would do last name first, then I agree, it would not sort. You can try sorting by splitting on the first space, but that would break if someone did Raymond Kenneth Camden. You could sort by assuming the last space is before the last name, but that would break too if the last name had a space, like "Van Haussen".

Comment 105 by mrnobody posted on 7/24/2020 at 11:32 AM

Thanks a lot! Excellent article.

Comment 106 by josetomastocino posted on 7/28/2020 at 8:18 AM

I do not usually comment on blog posts, but this tutorial is one of the easiest and most to-the-point articles I've ever seen. Thanks a lot.

Comment 107 (In reply to #106) by Raymond Camden posted on 7/28/2020 at 12:11 PM

You are most welcome.

Comment 108 by El Purz posted on 9/23/2020 at 11:03 AM

Thanks a lot! You absolutely saved me because we forgot a whole page for a project and I did last minute changes with your tutorial :-) I went through a couple of tutorials/stackoverflow-answers and yours was the most understandable.

Comment 109 (In reply to #108) by Raymond Camden posted on 9/23/2020 at 2:49 PM

You are most welcome.

Comment 110 by Valentino Leonardo Stillhardt posted on 10/5/2020 at 6:18 PM

Great tutorial!

However, your api is dead :c

Comment 111 (In reply to #110) by Raymond Camden posted on 10/5/2020 at 6:19 PM

Thanks - will fix asap.

Comment 112 (In reply to #110) by Raymond Camden posted on 10/5/2020 at 7:41 PM

All fixed - thank you.

Comment 113 by mohan kumar posted on 2/4/2021 at 5:34 PM

Thanks it is a great tutorial! Thanks for sharing it.I need to implement the sorting pagination for two tables in a same component.How to apply it can u help me

Comment 114 by mara posted on 2/5/2021 at 10:37 PM

please mr, how can I add filter to this table, like i want to filter by name