I'm attempting to implement pagination in this table I've built which contains data for Magic: The Gathering cards.
The default state is to only show 5 items per page, with a page selector dropdown and a results-per-page dropdown located at the bottom of the screen (along with "Next Page" and "Previous Page" buttons for navigation).
const app = new Vue({
el: "#app",
data: {
apiUrl: "https://api.magicthegathering.io/v1/cards",
cards: [],
pageNumber: 1,
resultsPerPage: 5,
dropdownResultsPerPage: 5,
increments: [5, 10, 15, 20, 25]
},
created: function() {
var vue = this;
axios
.get(this.apiUrl)
.then(function(data) {
vue.cards = data.data.cards;
})
.catch(function(error) {
console.log(error);
});
},
computed: {
startIndex: function() {
return (this.pageNumber - 1) * this.resultsPerPage;
},
endIndex: function() {
return this.startIndex + this.dropdownResultsPerPage;
},
numberOfPages: function() {
return Math.ceil(this.cards.length / this.dropdownResultsPerPage);
},
paginatedData: function() {
return this.cards.slice(this.startIndex, this.endIndex);
}
},
methods: {
nextPage: function() {
this.pageNumber++;
},
previousPage: function() {
this.pageNumber--;
}
}
});
body {
overflow: hidden;
}
#header {
display: flex;
position: sticky;
border-bottom: 1px solid black;
}
#app .content {
overflow: auto;
height: 300px;
position: relative;
}
#pagination: {
position: absolute;
bottom: 0;
left: 0;
width: 100%;
box-shadow: 0px 0px 6px 2px #fafafa;
}
[v-cloak] {
display: none;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/1.6.0/axios.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.7.8/vue.min.js"></script>
<div id="header">
<h1>MTG Cards</h1>
</div>
<div id="app" v-cloak>
<div class="content">
<table>
<thead>
<th>Id</th>
<th>Name</th>
<th>Mana Cost</th>
</thead>
<tbody>
<tr v-for="(card, index) in paginatedData" :key="index">
<td>{{ card.id }}</td>
<td>{{ card.name }}</td>
<td>{{ card.manaCost }}</td>
</tr>
</tbody>
</table>
</div>
<div id="pagination">
<p>
Page:
<select v-model="pageNumber">
<option v-for="(page, index) in numberOfPages" :key="index" :value="page">
{{ page }}
</option>
</select>
of {{ numberOfPages }}
<button @click="previousPage" :disabled="pageNumber == 1">
Previous
</button>
<button @click="nextPage" :disabled="pageNumber >= numberOfPages">
Next
</button> |
<select v-model="dropdownResultsPerPage">
<option v-for="(increment, index) in increments" :key="index" :value="increment">
{{ increment }}
</option>
</select>
cards per page
</p>
</div>
</div>
Let's say you have resultsPerPage
set to 5
, which would give you a total number of pages of 100
.
Now let's say you navigate to page 5 of 20 and then change the resultsPerPage
value to 25
. This will change the total number of pages from 20 to 4, but since you are on page 5 you won't be able to see that reflected in the Page Number dropdown, and the page navigation then gets a little confusing.
Is there a way to maintain the visual page number while also keeping the same results in the table on screen?
The issue you're facing is that when the number of results per page changes so the current page number may exceed the new total number of pages.
To fix this, you need to adjust the current page number when the resultsPerPage changes to ensure it doesn't exceed the new total number of pages.
To achieve this, you can watch the resultsPerPage and adjust the pageNumber accordingly. Here's how you can implement this logic:
const app = new Vue({
el: "#app",
data: {
apiUrl: "https://api.magicthegathering.io/v1/cards",
cards: [],
pageNumber: 1,
resultsPerPage: 5,
dropdownResultsPerPage: 5,
increments: [5, 10, 15, 20, 25]
},
created: function() {
this.fetchCards();
},
watch: {
dropdownResultsPerPage(newVal, oldVal) {
const oldStartIndex = (this.pageNumber - 1) * oldVal;
this.pageNumber = Math.floor(oldStartIndex / newVal) + 1;
this.resultsPerPage = newVal; // Ensure resultsPerPage is updated
}
},
computed: {
startIndex: function() {
return (this.pageNumber - 1) * this.resultsPerPage;
},
endIndex: function() {
return this.startIndex + this.resultsPerPage;
},
numberOfPages: function() {
return Math.ceil(this.cards.length / this.resultsPerPage);
},
paginatedData: function() {
return this.cards.slice(this.startIndex, this.endIndex);
}
},
methods: {
nextPage: function() {
if (this.pageNumber < this.numberOfPages) {
this.pageNumber++;
}
},
previousPage: function() {
if (this.pageNumber > 1) {
this.pageNumber--;
}
},
fetchCards: function() {
var vue = this;
axios
.get(this.apiUrl)
.then(function(response) {
vue.cards = response.data.cards;
})
.catch(function(error) {
console.log(error);
});
}
}
});
I added a watcher for dropdownResultsPerPage. When it changes, we calculate the new pageNumber based on the old start index to keep the user approximately on the same range of data. The resultsPerPage is updated to reflect the change as well.