Vue 3 Sorting Tables by Column with TypeScript and Composition API
composition apijavascripttypescriptvueYou can view the source code here:
https://github.com/kungfuphil/vue-sort-example
See it in action here:
First things first, this example uses Material Icons, so you’ll want to include this in your HTML:
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
For this example, I wanted to use a simple data set.
Here's what I came up with:
let data = [
{"id": 1, "name": "Phil"},
{"id": 2, "name": "Bobby"},
{"id": 3, "name": "Susie"},
];
Because this is TypeScript and we’re going to need it later, we also add an interface for this array.
interface dataInfo {
id: number,
name: string
}
Then we update the declaration of the data variable.
let data: Array<dataInfo> = [
{"id": 1, "name": "Phil"},
{"id": 2, "name": "Bobby"},
{"id": 3, "name": "Susie"},
];
Then we add a few more lines to allow us to access the data in the template.
const tableData = ref(data);
return {tableData};
The most simple table before we start adding the sorting logic looks like this.
<table>
<thead>
<tr>
<th>ID</th>
<th>Name</th>
</tr>
</thead>
<tbody>
<tr v-for="row in tableData" :key="row.id">
<td></td>
<td></td>
</tr>
</tbody>
</table>
Adding the sort icons is very simple. You can search and look through the Material icons here:
https://fonts.google.com/icons
<th>ID <span class="material-icons">sort</span></th>
In this example, I wanted to show one icon to show that sorting is possible for that column and another icon to show if we are sorting that column and in what direction. To do that, we need to include a few more variables.
const sortColumn = ref("id");
const sortDirection = ref(1);
By default, we will display the data sorted by ID in ascending order. You can change these values to suit your data’s needs. To make it a bit easier on ourselves, we will also include a variable to hold the name of the icon that shows the sort direction.
const arrowIconName = ref("arrow_drop_up");
Now to sort the data in different ways, we create this function to sort the data depending on what column we select.
const sortByColumn = (columnName: keyof dataInfo) => {
sortColumn.value = columnName;
sortDirection.value = -1 * sortDirection.value;
if (sortDirection.value == 1) {
arrowIconName.value = "arrow_drop_up";
tableData.value.sort((a, b) => (a[columnName] > b[columnName] ? 1 : -1));
} else {
arrowIconName.value = "arrow_drop_down";
tableData.value.sort((a, b) => (a[columnName] < b[columnName] ? 1 : -1));
}
}
Finally, we give the column headers a click event and tie that to the function above to do the sorting. We also need to update the icons to show whether each column is being sorted or not as well as show the direction of the sort.
<th @click="sortByColumn('id')">ID
<span v-if="sortColumn == 'id'" class="material-icons"></span>
<span v-else class="material-icons">sort</span>
</th>
<th @click="sortByColumn('name')">Name
<span v-if="sortColumn == 'name'" class="material-icons"></span>
<span v-else class="material-icons">sort</span>
</th>
Finally here’s what the entire component looks like:
<template>
<table>
<thead>
<tr>
<th @click="sortByColumn('id')">ID
<span v-if="sortColumn == 'id'" class="material-icons"></span>
<span v-else class="material-icons">sort</span>
</th>
<th @click="sortByColumn('name')">Name
<span v-if="sortColumn == 'name'" class="material-icons"></span>
<span v-else class="material-icons">sort</span>
</th>
</tr>
</thead>
<tbody>
<tr v-for="row in tableData" :key="row.id">
<td></td>
<td></td>
</tr>
</tbody>
</table>
</template>
<script lang="ts">
import { defineComponent, ref } from 'vue'
export default defineComponent({
setup() {
interface dataInfo {
id: number,
name: string
}
let data: Array<dataInfo> = [
{"id": 1, "name": "Phil"},
{"id": 2, "name": "Bobby"},
{"id": 3, "name": "Susie"},
];
const tableData = ref(data);
const sortColumn = ref("id");
const sortDirection = ref(1);
const arrowIconName = ref("arrow_drop_up");
const sortByColumn = (columnName: keyof dataInfo) => {
sortColumn.value = columnName;
sortDirection.value = -1 * sortDirection.value;
if (sortDirection.value == 1) {
arrowIconName.value = "arrow_drop_up";
tableData.value.sort((a, b) => (a[columnName] > b[columnName] ? 1 : -1));
} else {
arrowIconName.value = "arrow_drop_down";
tableData.value.sort((a, b) => (a[columnName] < b[columnName] ? 1 : -1));
}
}
return {tableData, sortColumn, sortDirection, arrowIconName, sortByColumn};
},
})
</script>
<style scoped>
table {
border-collapse: collapse;
margin: auto;
box-shadow: 0 0 20px rgba(0, 0, 0, 0.15);
}
thead tr {
background-color: gray;
color: white;
}
tbody tr {
border-bottom: 1px solid black;
}
th, td {
padding: 12px 15px;
}
.material-icons {
vertical-align: -6px;
}
th {
cursor: pointer;
}
</style>