I propose today a simple way to create a filterable table and all this dynamically, using VueJS. Know that to use VueJS, you don’t need to have a complete environment, or even an active server. We will simply use the available CDN, which will allow us to import the Vue framework, just like any JavaScript library.
First, let’s create our blank instance:
<!DOCTYPE html>
<html lang="en">
<head>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css"
type="text/css">
<title>Vue search sample</title>
</head>
<body>
<div id="app">
{{ test }}
</div>
<script>
var app = new Vue({
el: '#app',
data: {
test: 'Hello',
},
})
</script>
</body>
</html>
Here, we first import the Vue CDN, as well as that of Bootstrap, just to put a little design in our result. We simply create a div that we associate in our script part with a View element. And then, we are able within this context to run our application. You can therefore open this HTML file in your browser, and you will see the following result appear:
Now that Vue is functional, let’s move on to our structure and data. We will take for this example the list of the countries of the world. A free API allows us to retrieve all this information, at the following URL: https://countriesnow.space/api/v0.1/countries/flag/images
We will retrieve them using the fetch method:
fetch('https://countriesnow.space/api/v0.1/countries/flag/images')
.then(response => response.json())
.then(countries => {
this.elements = countries.data
this.sortElements()
})
This table of elements filled, we need a shot of HTML to display all that. Let’s also add our search inputs, pagination, and the ability to control how many items are displayed per page:
<div id="app">
<div>
<form>
Query: <input type="search" placeholder="Search..." v-model="searchQuery" @keyup="sortElements">
Elements per page: <input type="number" min="1" placeholder="Number of elements per page..."
v-model="elementsPerPage" @keyup="sortElements">
Ignore case: <input type="checkbox" v-model="ignoreCase" @change="sortElements">
<input type="reset">
</form>
<hr>
<table class="table table-striped w-100" v-if="elements.length">
<tr>
<th>Country</th>
</tr>
<tr v-for="element in currentElements">
<td>
<img :src=element.flag height="25px">
<span>{{ element.name }}</span>
</td>
</tr>
</table>
<div v-else>List is empty.</div>
<br>
<button :disabled="this.currentPage === 0" @click="changePage(false)">←</button>
{{ currentPage + 1 }} / {{ numberOfPages }}
<button :disabled="this.currentPage +1 >= numberOfPages" @click="changePage">→</button>
</div>
</div>
We finally end up with our table of well-filled countries, with our pagination and our filter possibility:
Once this visual has been created, we need the Javascript methods that will allow us to perform our actions. Among them :
- A method to filter our elements according to the text that we entered in our input query
- A method for changing pages, which will be a classic slice of an array, taking an offset and a limit
- The current items to display in our array, which is equivalent to using our filter method
- Associate all the elements used in our front, with variables defined in the data part, and thus associate them with v-models to ensure the dynamism of the page
We will therefore have the following script part to ensure everything:
<script>
var app = new Vue({
el: '#app',
data: {
elements: [],
elementsSorted: [],
searchQuery: "",
ignoreCase: true,
currentPage: 0,
elementsPerPage: 10,
},
mounted() {
fetch('https://countriesnow.space/api/v0.1/countries/flag/images')
.then(response => response.json())
.then(countries => {
this.elements = countries.data
this.sortElements()
})
},
computed: {
numberOfPages() {
return Math.ceil(this.elementsSorted.length / this.elementsPerPage)
},
currentElements() {
return this.elementsSorted.slice(
this.currentPage * this.elementsPerPage,
this.currentPage * this.elementsPerPage + this.elementsPerPage
)
}
},
methods: {
sortElements() {
this.elementsSorted = this.elements.filter((element) => {
let elementTransformed = this.ignoreCase ? element.name.toUpperCase() : element.name
let searchQueryTransformed = this.ignoreCase ? this.searchQuery.toUpperCase() : this.searchQuery
return elementTransformed.includes(searchQueryTransformed)
})
},
changePage(next = true) {
this.currentPage += next ? 1 : -1
},
}
})
</script>
And here is the live result:
If we aggregate the parts of codes explained above, we can therefore produce the following complete file:
<!DOCTYPE html>
<html lang="en">
<head>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css"
type="text/css">
<title>Vue search sample</title>
</head>
<body>
<div id="app">
<div>
<form>
Query: <input type="search" placeholder="Search..." v-model="searchQuery" @keyup="sortElements">
Elements per page: <input type="number" min="1" placeholder="Number of elements per page..."
v-model="elementsPerPage" @keyup="sortElements">
Ignore case: <input type="checkbox" v-model="ignoreCase" @change="sortElements">
<input type="reset">
</form>
<hr>
<table class="table table-striped w-100" v-if="elements.length">
<tr>
<th>Country</th>
</tr>
<tr v-for="element in currentElements">
<td>
<img :src=element.flag height="25px">
<span>{{ element.name }}</span>
</td>
</tr>
</table>
<div v-else>List is empty.</div>
<br>
<button :disabled="this.currentPage === 0" @click="changePage(false)">←</button>
{{ currentPage + 1 }} / {{ numberOfPages }}
<button :disabled="this.currentPage +1 >= numberOfPages" @click="changePage">→</button>
</div>
</div>
<script>
var app = new Vue({
el: '#app',
data: {
elements: [],
elementsSorted: [],
searchQuery: "",
ignoreCase: true,
currentPage: 0,
elementsPerPage: 10,
},
mounted() {
fetch('https://countriesnow.space/api/v0.1/countries/flag/images')
.then(response => response.json())
.then(countries => {
this.elements = countries.data
this.sortElements()
})
},
computed: {
numberOfPages() {
return Math.ceil(this.elementsSorted.length / this.elementsPerPage)
},
currentElements() {
return this.elementsSorted.slice(
this.currentPage * this.elementsPerPage,
this.currentPage * this.elementsPerPage + this.elementsPerPage
)
}
},
methods: {
sortElements() {
this.elementsSorted = this.elements.filter((element) => {
let elementTransformed = this.ignoreCase ? element.name.toUpperCase() : element.name
let searchQueryTransformed = this.ignoreCase ? this.searchQuery.toUpperCase() : this.searchQuery
return elementTransformed.includes(searchQueryTransformed)
})
},
changePage(next = true) {
this.currentPage += next ? 1 : -1
},
}
})
</script>
</body>
</html>
There you go, not so complex, right?
Feel free to use this datatable at your convenience, and pimp it to add even more functionality. We could think for example of direct deletion in the table, adding mass actions… In short, the only limit is your imagination!