Button pagination with dots - vue.js

I am practicing vue and I am trying to build a pagination with Rick Morty Api https://rickandmortyapi.com/documentation/
Currently looks like:
I would like to display these buttons in this form 1 2 3 4 5 ... 20 if I click on 20, then it would look like 1 ... 15 16 17 18 19 20. How can I achieve this? Do I need to use css for this, or pure js and use computed property?
<div class="button_container">
<button #click="pageChange(i + 1)" v-for="(item, i) in pages" :key="i">
{{ i + 1 }}
</button>
</div>

it's not that straight forward to do it in a one-liner, the approach I would recommend is to use a computed
you could use the function from https://stackoverflow.com/a/67658442/197546
// define method
function paginate(current_page, last_page, onSides = 3) {
// pages
let pages = [];
// Loop through
for (let i = 1; i <= last_page; i++) {
// Define offset
let offset = (i == 1 || last_page) ? onSides + 1 : onSides;
// If added
if (i == 1 || (current_page - offset <= i && current_page + offset >= i) ||
i == current_page || i == last_page) {
pages.push(i);
} else if (i == current_page - (offset + 1) || i == current_page + (offset + 1)) {
pages.push('...');
}
}
return pages;
}
// then in component...
data:{
return{
pages:[...],
currentPage: 0,
}
},
//...
computed: {
pageNums = () {
return paginate(this.currentPage, this.pages.length, 4)
}
}
then, because the ... should not have an event listener, you can use <template> and v-if to use different element
<div class="button_container">
<template v-for="(pageNum, i) in pages" :key="i">
<button v-if="Number.isInteger(pageNum)" #click="currentPage = i">
{{ pageNum }}
</button>
<span v-else>
{{ pageNum }}
</span>
</template >
</div>

Related

VueJS how to make a button disabled, under different conditions?

I have a button on my website that gives bonuses to the user. I need to make several conditions in 1 button.
If heal_used = 1 or diff < 1, the button must be disabled. I tried to do it like this:
<button v-if="heal_used 1" :disabled="diff < 1" v-else class="btn btn--small btn--purple" #click="takeBonus">Take</button>
But nothing worked. Also, if the button is active and the user can get a bonus, after the bonus you need to make the button inactive. I did it like this:
if (data.type === 'success') {
this.bonus_num = data.bonus_num;
this.heal_used = data.heal_used;
this.$forceUpdate();
}
Is it true? Can you help me please, to make 2 condifitions?
UPDATE
I change code to:
<button class="btn btn--small btn--purple" :disabled="isDisabled" #click="takeBonus">Take</button>
And add:
computed: {
isDisabled() {
return this.heal_used = 1 || this.diff < 10;
},
},
Console.log say me:
console.log(data.heal_used);
console.log(data.diff);
0
17
But button is stil; disabled, what's wrong?
UPDATE takeBonus:
takeBonus() {
this.$root.axios.post('/user/takeBonus', {
value: this.user.cashback
})
.then(res => {
const data = res.data;
if (data.type === 'success') {
this.bonus_num = data.bonus_num;
this.$root.user.balance = data.newBalance;
this.heal_used = data.heal_used;
this.$forceUpdate();
}
this.$root.showNotify(data.type, this.$t(`index.${data.message}`));
})
},
new Vue({
el: '#example',
data: {
heal_used : 4,
diff: 3
},
methods: {
takeBonus1: function () {
this.heal_used=1;
this.diff=0;
},
takeBonus2: function () {
this.heal_used=1;
this.diff=4;
},
takeBonus3: function () {
this.heal_used=2;
this.diff=.1;
},
reset: function () {
this.heal_used=4;
this.diff=3;
}
}
})
<head>
<title>My first Vue app</title>
<script src="https://cdn.jsdelivr.net/npm/vue#2.6.12"></script>
</head>
<body>
<div id="example">
<p>
<span>heal_used: {{ heal_used }}</span>
<span>diff: {{ diff }}</span>
</p>
<button
#click="takeBonus1()"
:disabled="heal_used===1 || diff < 1" >
Take bonus (both)
</button>
<br>
<button
#click="takeBonus2()"
:disabled="heal_used===1 || diff < 1" >
Take bonus (heal_used===1)
</button>
<br/>
<button
#click="takeBonus3()"
:disabled="heal_used===1 || diff < 1" >
Take bonus (diff < 1)
</button>
<br>
<button
#click="reset()">
Reset
</button>
</div>
</body>

How do I add min and max value to my Vue count value

The user can click on a + and - button to increment and decrement the value. How do I add a min and max value e.g. min = 1 and max = 10 for the <span>[[ count ]]</span>?
My Vue.js app:
<div id="app">
<a class="btn" v-on:click="increment">Add 1</a>
<a class="btn" v-on:click="decrement">Remove 1</a>
<span>[[ count ]]</span>
</div>
<script>
const App = new Vue({
el: '#app',
delimiters: ['[[',']]'],
data() {
return {
min: 1,
max: 10,
count: 1
}
},
methods: {
increment() {
this.count = this.count === this.max ? this.max : this.count + 1;
},
decrement() {
this.count = this.count === this.min ? this.min : this.count + 1;
}
}
})
</script>
Update:
Above code is working now.
1) How do I change my <span>[[ count ]]</span> into an <input type="number" min="0" max="10" />, controlled by this buttons?
2) How do I add a class e.g disabled when [[ count ]] === 1?
Update 2:
I changed it to an input field:
<input type="number" name="lineItems[{{ product.id }}][quantity]" value="{{ quantity }}" v-model.number="quantity" min="{{ product.minPurchase }}" max="{{ product.calculatedMaxPurchase }}" class="custom-qty__qty-field">
And make the input value adjustable by the min and plus buttons:
<script>
const app = new Vue({
el: '#app',
delimiters: ['[[',']]'],
data() {
return {
quantity: 1,
max: {{ product.calculatedMaxPurchase }},
min: {{ product.minPurchase }}
}
},
methods: {
increment() {
//this.count++
this.quantity = this.quantity === this.max ? this.max : this.quantity + 1;
},
decrement() {
//this.count--
this.quantity = this.quantity === this.min ? this.min : this.quantity - 1;
}
}
})
</script>
E.g {{ product.minPurchase }} are twig variables which contains settings from the ShopWare backend.
Is this a clean way? And how do I add a CSS class when the count reaches 1, so I can disable the button?
Check if the count is already at the limits during increment and decrement and act accordingly.
increment() {
this.count = this.count === 10 ? 10 : this.count + 1;
}
decrement() {
this.count = this.count === 1 ? 1 : this.count - 1;
}
You could also make min and max data properties instead of hardcoding 1 and 10 if you wanted.
After your Edit:
If you use a number input instead, you could solve this without methods. All that's needed is to bind your data to the input like this:
<input type="number" v-model="count" :min="min" :max="max" />
See the demo below:
new Vue({
el: "#app",
data() {
return {
min: 1,
max: 10,
count: 1
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<input type="number" v-model="count" :min="min" :max="max" />
<span class="instructions"><< Mouseover the input to change</span>
<div class="count">Count: {{ count }}</div>
</div>

Dynamic v-model problem on nuxt application

I am trying to create a dynamic v-model input. All seems well except for the following.
The on click event that triggers the checkAnswers method only works if you click out of the input then click back into the input and then press the button again. It should trigger when the button is pressed the first time.
Does anyone have any ideas? Thanks in advance.
<template>
<div class="addition container">
<article class="tile is-child box">
<div class="questions">
<ul v-for="n in 5">
<li>
<p>{{ randomNumberA[n] }} + {{ randomNumberB[n] }} = </p>
<input class="input" type="text" maxlength="8" v-model.number="userAnswer[n]">
<p>{{ outcome[n] }}</p>
</li>
</ul>
</div>
<div class="button-container">
<button #click="checkAnswers" class="button">Submit Answer</button>
</div>
</article>
</div>
</template>
<script>
export default {
data() {
return {
randomNumberA: [] = Array.from({length: 40}, () => Math.floor(Math.random() * 10)),
randomNumberB: [] = Array.from({length: 40}, () => Math.floor(Math.random() * 10)),
userAnswer: [],
outcome: [],
}
},
methods: {
checkAnswers() {
for (var i = 0; i < 6; i++) {
if (this.userAnswer[i] === (this.randomNumberA[i] + this.randomNumberB[i])) {
this.outcome[i] = 'Correct';
} else {
this.outcome[i] = 'Incorrect';
}
}
}
}
}
</script>
You have some basic issues with your use of the template syntax here. According to the vue docs:
One restriction is that each binding can only contain one single
expression, so the following will NOT work: {{ var a = 1 }}
If you want to populate your arrays with random numbers you would be better calling a function on page mount. Something like this:
mounted() {
this.fillArrays()
},
methods: {
fillArrays() {
for (let i = 0; i < 5; i++) {
this.randomNumberA.push(Math.floor(Math.random() * 10))
this.randomNumberB.push(Math.floor(Math.random() * 10))
this.answer.push(this.randomNumberA[i] + this.randomNumberB[i])
}
}
}
Then you can use template syntax to display your arrays.
It looks like you are setting up a challenge for the user to compare answers so I think you'd be better to have a function called on input: Something like:
<input type="whatever" v-model="givenAnswer[n-1]"> <button #click="processAnswer(givenAnswer[n-1])>Submit</button>
Then have a function to compare answers.
Edit
I have basically rewritten your whole page. Basically you should be using array.push() to insert elements into an array. If you look at this you'll see the randomNumber and answer arrays are populated on page mount, the userAnswer array as it is entered and then the outcome on button click.
<template>
<div>
<div >
<ul v-for="n in 5">
<li>
<p>{{ randomNumberA[n-1] }} + {{ randomNumberB[n-1] }} = </p>
<input class="input" type="text" maxlength="8" v-model.number="userAnswer[n-1]">
<p>{{ outcome[n-1] }}</p>
</li>
</ul>
</div>
<div class="button-container">
<button #click="checkAnswers" class="button">Submit Answers</button>
</div>
</div>
</template>
<script>
export default {
data() {
return {
randomNumberA: [],
randomNumberB: [],
answer: [],
userAnswer: [],
outcome: [],
}
},
mounted() {
this.fillArrays()
},
methods: {
checkAnswers() {
this.outcome.length = 0
for (var i = 0; i < 6; i++) {
if (this.userAnswer[i] === this.answer[i]) {
this.outcome.push('Correct');
} else {
this.outcome.push('Incorrect');
}
}
},
fillArrays() {
for (let i = 0; i < 5; i++) {
this.randomNumberA.push(Math.floor(Math.random() * 10))
this.randomNumberB.push(Math.floor(Math.random() * 10))
this.answer.push(this.randomNumberA[i] + this.randomNumberB[i])
}
}
}
}
</script>

Calculating multiple totals from a v-for loop

I have a unique situation where I have a v-for loop of users (Staff) and inside that I have another v-for loop checking the leave a user has accumulated.
so to put it simply
v-for get user
//Print users name
v-for get any leave associated with this user
//Print that days Annual Leave
//Print that days Sick Leave
v-end
//Print total Annual Leave
//Print total Sick Leave
v-end
The leave database content has these values
Type: (Sick Leave, Annual Leave, Bereavement, etc)
Hours: integer
So essentially it will say
Thomas Annual Sick
------------------------------------------
Annual Leave 2 hours 0 Hours
Sick Leave 0 Hours 3 Hours
Annual Leave 4 Hours 0 Hours
-------------------------------------------
Total 6 Hours 3 Hours
John Annual Sick
------------------------------------------
Annual Leave 2 hours 0 Hours
Annual Leave 2 Hours 0 Hours
-------------------------------------------
Total 4 Hours 0 Hours
Now for the code and what I have so far:
<div v-for="user_list in user_list_filtered()">
<div class="user_heading"><h2>{{ user_list.first_name }}</h2></div>
<div class="report_content" v-for="userleave in user_leave_filtered(user_list['.key'])">
<div class="leave_type_content">
{{ userleave.type }}
</div>
<div class="days_taken_content">
//Get Leave
</div>
<div class="lsl_content">
//Get Sick Leave
</div>
</div>
<div class="leave_content">
<div class="total_leave_title">
Total Leave Taken
</div>
<div class="total_hours">
// Print Total Leave
</div>
<div class="total_hours">
//Print Total Sick Leave
</div>
</div>
</div>
So if it is of Type Sick Leave add it to the second column and set the first column to 0 or if !== Sick Leave set first column to value and second column to 0. Then add each side up and print below.
I have tried some things as functions but I get infinite loops so I am kinda stuck as most posts are not as complicated as what I am trying to achieve.
Thanks for the help
Edit:
Additional functions
user_leave_filtered(userPassed) {
var self = this
return this.userLeave.filter(function (i) {
if (i.users_id === userPassed &&
((i.start_time >= self.getUnix(self.firstDate) && i.start_time <= self.getUnix(self.lastDate)) ||
(self.firstDate === null || self.firstDate === '' || self.lastDate === null || self.lastDate === ''))) {
return true
} else {
return false
}
})
},
user_list_filtered() {
var self = this
return this.userList.filter(function (i) {
var passed = false
if (self.userToShow === i['.key'] || self.userToShow === 'All') {
// Track whether to filter out this leave or not
self.userLeave.forEach(function (element) {
if (element.users_id === i['.key']) {
passed = true
} else {}
})
}
return passed
})
},
First rule of thumb, don't call functions in your HTML. Use computed properties instead.
You can get a filtered user list and map it to present the data you need per user.
Anyway, I recommend you to handle the mapping of "user leaves" in the backend, and bring the data as close as you'll use it in the client.
This is an example of how I'd address your case (notice I don't use the same object structure you are probably using, since you didn't provide the full code in your question)
new Vue({
el: "#app",
data: {
userList: [
{ id: 1, firstName: "Jon Doe" },
{ id: 2, firstName: "Jane Doe" }
],
userLeave: [
{ userId: 1, type: "anual", hours: 2 },
{ userId: 1, type: "sick", hours: 3 },
{ userId: 1, type: "anual", hours: 4 },
{ userId: 2, type: "anual", hours: 2 },
{ userId: 2, type: "sick", hours: 3 },
{ userId: 2, type: "anual", hours: 4 }
]
},
computed: {
usersById () {
if (!this.userList.length) return null;
// create a list of users by id and save a few iterations
return this.userList.reduce((list, user) => {
list[user.id] = user;
return list
}, {})
},
filteredUsers () {
if (!this.userLeave.length) return [];
const users = {}
this.userLeave.forEach(leave => {
const user = this.usersById[leave.userId]
if (user) {
if (leave.type === "sick") {
user.totalSick = typeof user.totalSick === "number"
? leave.hours + user.totalSick
: leave.hours;
} else {
user.totalAnual = typeof user.totalAnual === "number"
? leave.hours + user.totalAnual
: leave.hours;
}
if (user.leaves === undefined) user.leaves = []
user.leaves.push(leave)
users[user.id] = user
}
})
return users
}
}
})
.leave_type_content,
.days_taken_content,
.lsl_content,
.total_leave_title,
.total_hours,
.total_hours {
display: inline-block
}
<script src="https://unpkg.com/vue#2.5.17/dist/vue.min.js"></script>
<div id="app">
<div v-for="user in filteredUsers"> <!-- NOTICE THE COMPUTED PROPERTY -->
<div class="user_heading"><h2>{{ user.firstName }}</h2></div>
<div class="report_content" v-for="userleave in user.leaves">
<div class="leave_type_content">
{{ userleave.type }}
</div>
<div class="days_taken_content">
{{ userleave.type === "anual" && userleave.hours || 0 }} hours
</div>
<div class="lsl_content">
{{ userleave.type === "sick" && userleave.hours || 0 }} hours
</div>
</div>
<div class="leave_content">
<div class="total_leave_title">
Total Leave Taken
</div>
<div class="total_hours">
{{ user.totalAnual }}
</div>
<div class="total_hours">
{{ user.totalSick }}
</div>
</div>
</div>
</div>
I would create a computed property holding the users you want to display along with their mapped leave and totals. For example
computed: {
usersWithLeave () {
const unixFirstDate = this.firstDate && this.getUnix(this.firstDate)
const unixLastDate = this.lastDate && this.getUnix(this.lastDate)
// first map the leave entries by user for quick access
const leaveByUser = this.userLeave.reduce((map, element) => {
// Filter out by dates
if (
(!unixFirstDate || element.start_time >= unixFirstDate) &&
(!unixLastDate || element.start_time <= unixLastDate)
) {
const elements = map.get(element.users_id) || []
elements.push(element)
map.set(element.users_id, elements)
}
return map
}, new Map())
// now construct a filtered array of users then map to one with leave attached
return this.userList
.filter(({'.key': id}) => [id, 'All'].includes(this.userToShow) && leaveByUser.has(id))
.map(({'.key': id, first_name}) => {
const leave = leaveByUser.get(id)
return {
first_name,
leave, // an array of all leave elements
totals: leave.reduce((totals, element) => {
totals[element.type === 'Sick Leave' ? 'sick' : 'annual'] += element.Hours
return totals
}, { sick: 0, annual: 0 })
}
})
}
}
Whoo, that was more work than expected. This will produce an array of objects that look something like
{
first_name: 'Thomas',
leave: [
{ type: 'Annual Leave', Hours: 2 },
{ type: 'Sick Leave', Hours: 3 },
{ type: 'Annual Leave', Hours: 4 }
],
totals: {
sick: 3,
annual: 6
}
}
Now you can easily use this in your template
<div v-for="user in usersWithLeave">
<div class="user_heading"><h2>{{ user.first_name }}</h2></div>
<div class="report_content" v-for="userleave in user.leave">
<div class="leave_type_content">
{{ userleave.type }}
</div>
<div class="days_taken_content">
{{ userleave.type !== 'Sick Leave' && userleave.Hours || 0 }}
</div>
<div class="lsl_content">
{{ userleave.type === 'Sick Leave' && userleave.Hours || 0 }}
</div>
</div>
<div class="leave_content">
<div class="total_leave_title">
Total Leave Taken
</div>
<div class="total_hours">
{{ userleave.totals.annual }}
</div>
<div class="total_hours">
{{ userleave.totals.sick }}
</div>
</div>
</div>

Vue.js, transition and slots to work together

I've been trying to develop a carousel with Vue.js but I'm stuck with the transition effects. Following are my codes,
// Main component
<slider>
<slide v-for="(slide, index) in compOpts.blockImg" :key="index">
<img :src="slide" :alt="features ${index}`">
</slide>
</slider>
// Slider component
<transition-group class="slider__container" name="slideTr" tag="div" appear mode="out-in">
<slot></slot>
</transition-group>
import slide from './contentSliderSlide';
export default {
name: 'slider',
data: () => ({
slideCount: 0,
activeSlide: 0,
currentSlide: null,
lastSlide: null,
slideInterval: null
}),
mounted() {
this.init();
},
methods: {
getSlideCount() {
this.slideCount = (
this.$slots
&& this.$slots.default
&& this.$slots.default.filter(
slot => slot.tag
&& slot.tag.indexOf('slide') > -1
).length
) || 0;
},
init() {
this.getSlideCount();
this.$slots.default[this.activeSlide].elm.classList.add('visible');
this.playSlide();
},
gotoSlide(n, p) {
this.currentSlide = (n + this.slideCount) % this.slideCount;
if (p) {
this.lastSlide = ((n - 1) + this.slideCount) % this.slideCount;
this.$slots.default[this.lastSlide].elm.classList.remove('visible');
this.activeSlide += 1;
} else {
this.lastSlide = ((n + 1) + this.slideCount) % this.slideCount;
this.$slots.default[this.lastSlide].elm.classList.remove('visible');
this.activeSlide -= 1;
}
this.$slots.default[this.currentSlide].elm.classList.add('visible');
},
playSlide() {
this.slideInterval = setInterval(this.nextSlide, 2000);
},
nextSlide() {
this.gotoSlide(this.activeSlide + 1, true);
}
},
components: {
slide
}
};
// Slide component
<div class="slider__slide">
<slot></slot>
</div>
If there isn't any solution then at least please advise me how to implement css transition effect with before and after hook (using pure css method)