Now that we have searching and have rewired our project, it will be relatively straightforward to add sorting.
The first thing we will do is add 2 variables to our project. One to keep track of the column we are sorting on and another to keep track of if we are ascending or descending our sort.
createApp({
searchString: "",
search,
filteredWorkers: [],
sortColumn: "",
order: "ASC",
setSortColumn,
getWorkers,
workers: [],
loaded: false,
mounted,
}).mount("#app");
We are going to set the order to ascending by default.
Now we can update the html, we will add some click events to the header.
<thead>
<th @click="setSortColumn('name')">Name</th>
<th @click="setSortColumn('position')">Position</th>
<th @click="setSortColumn('office')">Office</th>
<th @click="setSortColumn('age')">Age</th>
</thead>
We are going to call our setSortColumn function and this will set what column we will sort on and the direction. One thing to keep in mind is that we are specifying the key we are sorting on.
Now let's look at what our setSortColumn function will look like.
function setSortColumn(key) {
if (this.sortColumn === key) {
if (this.order === 'ASC') {
this.order = 'DESC';
} else {
this.order = 'ASC';
}
} else {
this.order = 'ASC';
}
this.sortColumn = key;
this.search();
}
We first check if our column that we clicked is one that already have active. If this is the case, then we will need to flip the order based on what the current order is. If however we are clicking a new column to sort on, then we set the order to ASC by default.
We then set the sortColumn and called our search function. We will be updating our search function to sort the filteredWorkers list as that makes the most sense.
Our updated search function:
function search() {
this.filteredWorkers = this.searchString === ""
? this.workers
: this.workers.filter(wo => Object.values(wo).join("").indexOf(this.searchString) !== -1);
const column = this.sortColumn
const order = this.order;
this.filteredWorkers.sort(function(a, b) {
var nameA = a[column]+"".toUpperCase();
var nameB = b[column]+"".toUpperCase();
if (order === "DESC" && nameA > nameB) {
return -1;
}
if (order === "DESC" && nameA < nameB) {
return 1;
}
if (nameA < nameB) {
return -1;
}
if (nameA > nameB) {
return 1;
}
return 0;
});
}
Once we search for our string, we can then sort our filteredWorkers by the column we have clicked on. We also need to sort by ascending or descending.
We should now be able to refresh our web page and be able to click on a heading to sort the table. We can also search for a string and then sort as well.
With that we are done! We have now used PetiteVue to do everything to set up a table that is searchable and sortable. Through that process we have hit the major elements of PetiteVue and we can now add interactivity and templating easily.
I really like petitevue for how simple it has kept everything and the best part is that most of this logic will work as is in Vue2 and Vue3. There are some slight changes that need to be made but for the most part the logic is all the same.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>PetiteVue Tutorial</title>
<link rel="icon" href="data:;base64,=">
<style>
</style>
</head>
<body>
<div id="app" @vue:mounted="mounted">
<div v-if="!loaded">Waiting for data...</div>
<div v-else>
<input type="text" v-model="searchString" v-on:keyup.enter="search">
<button @click="search">Search</button>
<table>
<thead>
<th @click="setSortColumn('name')">Name</th>
<th @click="setSortColumn('position')">Position</th>
<th @click="setSortColumn('office')">Office</th>
<th @click="setSortColumn('age')">Age</th>
</thead>
<tbody>
<tbody>
<tr v-for="worker in filteredWorkers">
<td>{{worker.name}}</td>
<td>{{worker.position}}</td>
<td>{{worker.office}}</td>
<td>{{worker.age}}</td>
</tr>
</tbody>
</tbody>
</table>
</div>
</div>
<script type="module">
import { createApp } from '/js/petite-vue.es.js'
async function getWorkers() {
const workers = [
{ name: "Airi Satou", position: "Accountant", office: "Tokyo", age: 33},
{ name: "Angelica Ramos", position: "Chief Executive Officer (CEO)", office: "London", age: 47 },
{ name: "Cedric Kelly", position: "Senior Javascript Developer", office: "Edinburgh", age: 22 },
{ name: "Jennifer Chang", position: "Regional Director", office: "Singapore", age: 28 },
];
this.loaded = true;
return workers;
}
function search() {
this.filteredWorkers = this.searchString === ""
? this.workers
: this.workers.filter(wo => Object.values(wo).join("").indexOf(this.searchString) !== -1);
const column = this.sortColumn
const order = this.order;
this.filteredWorkers.sort(function(a, b) {
var nameA = a[column]+"".toUpperCase();
var nameB = b[column]+"".toUpperCase();
if (order === "DESC" && nameA > nameB) {
return -1;
}
if (order === "DESC" && nameA < nameB) {
return 1;
}
if (nameA < nameB) {
return -1;
}
if (nameA > nameB) {
return 1;
}
return 0;
});
}
function setSortColumn(key) {
if (this.sortColumn === key) {
if (this.order === 'ASC') {
this.order = 'DESC';
} else {
this.order = 'ASC';
}
} else {
this.order = 'ASC';
}
this.sortColumn = key;
this.search();
}
async function mounted() {
this.workers = await this.getWorkers();
this.filteredWorkers = this.workers;
}
createApp({
searchString: "",
search,
filteredWorkers: [],
sortColumn: "",
order: "ASC",
setSortColumn,
getWorkers,
workers: [],
loaded: false,
mounted,
}).mount("#app");
</script>
</body>
</html>