Use Vuetify v-btn with loader inside a v-for loop - javascript

I am trying to figure out how to have my dynamically generated buttons inside a for loop each have a separate loader. Vuetify has buttons with Loaders.
The problem I am having is, When these buttons are in a for loop and one is clicked they all show the loading indicator. I would like only the one that is clicked to show the loading indicator.
HTML:
<div v-for="(item, i) in items" :key='i'>
<v-btn
dark
color="pink"
:loading="loading"
#click="loader = 'loading'"
>
<v-icon>location_on</v-icon> Lookup
<span slot="loader">locating...</span>
<span slot="loader" class="custom-loader">
<v-icon dark>cached</v-icon>
</span>
</v-btn>
</div>
SCRIPT
data () {
return {
loader: null,
loading: false
}
},
Let's say I have 3 items. The code above will generate three buttons but they all will share the same loading param. How can I have each button use its only loading param? As always any and all help is much appreciated.

You're using the same data property for all buttons, so these buttons share the same loading state which affects the at one time, to make difference try to add a data property called index which represents the current clicked button index :
data () {
return {
index:-1,
loader: null,
loading: false
}
},
and bind loading prop to the condition loading && i==index and update the current index on click event #click="loader = 'loading';index=i" :
<div v-for="(item, i) in items" :key='i'>
<v-btn
dark
color="pink"
:loading="loading && i==index"
#click="loader = 'loading';index=i"
>
<v-icon>location_on</v-icon> Lookup
<span slot="loader">locating...</span>
<span slot="loader" class="custom-loader">
<v-icon dark>cached</v-icon>
</span>
</v-btn>
</div>

Its actually a lot easier than you think:
<div v-for="(item, i) in items" :key='i'>
<v-btn
dark
color="pink"
:loading="loading[index]"
#click="loading[index] = true"
>
<v-icon>location_on</v-icon> Lookup
<span slot="loader">locating...</span>
<span slot="loader" class="custom-loader">
<v-icon dark>cached</v-icon>
</span>
</v-btn>
</div>
data () {
return {
loading: {},
}
},
In the beginning loading[index] will be undefined, so it will be evaluated as false, once you establish its value in the click event it will be evaluated as true, it worked for me, hope it helps.

you can use reactive array to prevent changing the index like this.
<div v-for="(item, i) in items" :key='i'>
<v-btn #click.prevent="testload(i)" :loading="loading[i]"></v-btn>
</div>
data () {
return {
loading: [],
}
},
methods: {
testload: function (index) {
// reactive array
this.$set(this.loading, index, true);
console.log(index)
console.log(this.loading[index])
// stop loading after x miliseconds
setTimeout(() => (this.$set(this.loading, index, false)), 3000)
},
dont forget to input false according the length of items, this is the example:
getDataAll: function () {
var i = 0
for (i = 0; i < items.length; i++) {
this.loading.push(false);
}
}

Related

How to react and deactivate only that button in Vuetify.js when the follow, unfollow button is clicked

I have a series of buttons with follow, unfollow status and when I click on each button, I want its status to change after a little delay and deactivation, and the button to be reactivated , For example, if the button was in the follow mode, when it was clicked, the button would be deactivated and loaded until the backend operation was performed Then change its status to unfollow.
But now with the click of a button, all the buttons change position together.
The result of my work is in the following image:
The code I wrote on the user side is as follows:
<v-btn
#click="join()"
:loading="joinGroup"
:disabled="joinGroup"
dark
v-show="joinBtn"
color="#7256EA"
class="mt-2 rounded-full bg-purple-gradient"
>Join Group
</v-btn>
The code I wrote on the back side is as follows:
<script>
data() {
return {
joinGroup: false,
}
}
methods: {
join: async function() {
this.joinGroup = true;
if (
this.group.privacy == 1 ||
(this.group.privacy == 2 && this.isFollower)
) {
this.addGroupMember(true);
} else if (this.group.privacy == 0) {
await this.addGroupMember(false);
this.sendJoinRequest();
this.snackBarPrivate();
} else if (this.group.privacy == 2 && !this.isFollower) {
this.snackBarFollowers();
}
this.joinGroup = false;
},
}
</script>
I guess your button is in a v-for and each row represents an item of your data. The problem is that each of your buttons bind to your joinGroup for :loading and :disabled, so all of them will show the state when you toggle it true.
When you are within a v-for however, you could identify which button should be disabled/loaded in connection with your item. I have made a codepen for you that shows the concept Codepen.
You could potentially also achieve this using the index in your v-for like in the following snippet.
<div v-for="(item, index) in items">
<v-btn
:disabled="disabled[index]"
:loading="loading[index]"
#click="letItLoad(index)">
Test Button
</v-btn>
</div>

How to properly splice array item by id

I have a simple popup component that get's called on a click event in some places in my app. The popups get created on click and are pushed in an array, that is later forEach-ed and displayed so that when the user clicks on a button multiple times multiple popups are displayed. The success ones disappear and the red ones disappear once someone clicks on the X, I achieve that by splicing them from the array, but if a user generated 5 popups and clicks the X the next popup gets closed, not the one that the user has clicked on, so it seems that I am not targeting the specific popup order in the array but the next, how can I properly target the exact clicked modal so that it gets closed and not the one that is next in line, what am I doing wrong here? How should I splice the array properly so that I target the exact pushed modal and not the rest?
Here is my code:
<template>
<div class="main">
<div
v-for="item in array"
:key="item.id"
:class="['popUp', `popUp--type--${item.popUpTypeType}`]"
>
<div class="popUp-side">
<p class="exclamation-mark">!</p>
</div>
<h5 class="popUp-message">{{item.message}}</h5>
<div class="popUp-side">
<p class="closing-x" #click="closeMessage" v-if="item.popUpTypeType === 'danger'">X</p>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'popUp',
data: () => ({
array:[],
}),
methods: {
removepopUpType() {
var self = this;
var id = Math.round( Math.random()*1e13 );
var index = self.array.map(x => {
return x.id;
}).indexOf(id);
self.array.splice(index, 1);
},
callpopUpType(obj){
var newpopUpType = {
popUpTypeType: obj.type,
message: obj.message,
id: obj.id
};
if(obj.type === 'success'){
setTimeout(this.removepopUpType, 2000)
}
this.array.push(newpopUpType);
},
closeMessage() {
this.removepopUpType()
},
},
created() {
this.$root.$on('call-popUp', this.callpopUpType);
},
destroyed(){
this.$root.$off('call-popUp', this.callpopUpType);
}
}
</script>
There is quite a bit of unusual code in the example, so I may be missing something, but couldn't you just pass the item to the method.
<p
v-if="item.popUpTypeType === 'danger'"
class="closing-x"
#click="closeMessage(item)"
>X</p>
and then
closeMessage(item) {
const index = this.array.indexOf(item);
if (index >= 0) this.array.splice(index, 1)
},

How to inactivate all items in the botnav component of Vuetify?

I have a Vuetify botnav; each item is activated under a certain router path, I want to inactivate the item when it's under another path.
I tried to set active.sync to -1 when I want to inactivate the tab, this will work if I never activate any item in the botnav, but if I activate an item, then set active.sync=-1 again, it will automatically activate the first item:
<v-bottom-nav
:active.sync="bottomNav"
:value="true"
shift
absolute
>
<v-btn
color="teal"
flat
value="recent"
>
<span>Recent</span>
<v-icon>history</v-icon>
</v-btn>
<v-btn
color="teal"
flat
value="favorites"
>
<span>Favorites</span>
<v-icon>favorite</v-icon>
</v-btn>
<v-btn
color="teal"
flat
value="nearby"
>
<span>Nearby</span>
<v-icon>place</v-icon>
</v-btn>
</v-bottom-nav>
In the script:
watch:{
$route:function(to, from){
switch(to.path){
case "/0":
this.bottomNav=0;
break
case "/1":
this.bottomNav=1;
break
case "/2":
this.bottomNav=2;
break
default: this.bottomNav=-1
}
}
}
I did find a solution that works, by setting a dummy hidden item in the botnav and set the v-show of this item to false, when I want to inactivate all items, I activate this unshown one, and it result in what I want:
A dummy item:
<v-bottom-nav
:active.sync="bottomNav"
//more stuff
>
//other items
<v-btn v-show="0" value="inactivate"></v-btn>
</v-bottom-nav>
And do this whenever I want to inactivate all items in the script:
this.bottomNav = "inactivate"
This WORKS, but that's kind of hacky, is there a more formal/elegant way to do this?
The default value for active.sync is undefined, so try using void to reset:
this.bottomNav = void(0)
[ https://jsfiddle.net/e8a67qtp/ ]

Handle click events in a list

Here is a ToDoList from Vue examples.
I want to add some extra features to this small app, e.g. set date for task. Therefore I'd like to show more operations of the task when I click "...".
Below is what I want to avoid, which after clicking another task, the previous click action doesn't be removed:
I try to add a property for each todo, and bind a click function on the "..." (more). Each time click "more", firstly set "isMoreClick" property of all task to false, then toggle the value of "isMoreClick" of current clicked task:
<button class="more"
#click="isMoreClick(todo)"
v-show="!todo.isMoreClick">
</button>
<div class="more-opt" v-show="todo.isMoreClick">
<button class="destroy" #click="removeTodo(todo)"></button>
</div>
...
this.todos.push({
id: todoStorage.uid++,
title: value,
completed: false,
isMoreClick: false // this is what I added
})
...
isMoreClick (todo) {
this.todos.forEach(todo => {
todo.isMoreClick = false
})
todo.isMoreClick = !todo.isMoreClick
}
I think my approach is a little stupid. Is there any better solution? (set a flag symbol?)
You don't say how you're rendering the todo elements. But if you're using a v-for loop, one approach could be
<ul>
<li
v-for="(todo, index) in todos"
:key="index"
>
{{todo.whatever}}
<button
v-if="index !== visibleTodoIndex"
class="more"
#click="visibleTodoIndex = index"
/>
<div
v-else
class="more-opt"
>
<button
class="destroy"
#click="visibleTodoIndex = -1"
/>
</div>
</li>
<ul>
Then just add visibleTodoIndex to the component's data.
It looks to me you need to use a global variable accessible from all todos, not to have a local variable inside each todo and updating it everywhere every time. I recommend using vuex store and updating isMoreClick value via mutations.

Vuejs sort and filter data

i'm trying make sorting and filters for my data. I have some cards from API.
For sorting i have few issues ( sort card by price, sort by time, etc. )
The default sort is sort by price, it's enabled.
Filters are inputs that should show show/hide my cards ( example: range of min-prices and max-prices)
I've already done my filters, but when i'm trying to sort items by time, filter's v-show logic saved from previous sorting ( sort by price )
Cards are coming from mounted() axios responce
Result.vue
Buttons for sort
<div class="sort-btn down" v-bind:class="{ activesort: activesort }"
v-on:click='changeSort'>By Price
</div>
<div class="sort-btn down" v-bind:class="{ activesort: !activesort }"
v-on:click='changeSort'>By Time
</div>
Card component
<card :item="item" v-for="(item, index) in filteredAndSortedData"
:key="index"></card>
JS
computed: {
filteredAndSortedData() {
let result = this.cards;
if(this.orderByTime){
return result.items.sort(function(a, b) {
return a.info.time - b.info.time;
});
} else {
return result.items.sort(function(a, b) {
return a.price - b.price;
});
}
}
}
data() {
return {
activesort: true,
orderByTime: false,
showFilters: false,
}
},
methods: {
changeSort: function () {
this.activesort = !this.activesort;
this.orderByTime = !this.orderByTime;
},
}
Card.vue
<div class="card-wrapper" v-bind:class="{'has-label': hasLabel}" v-show="showCard">
<div>{{item.price}}</div>
<div>{{item.time}}</div>
</div>
data () {
return {
showCard: true,
}
},
methods: {
changeFilter() {
if(some logic when clicking input){
this.showCard = false;
}
}
}
So, the problem is: i have two sorting buttons, few filters buttons.
When i'm trying to change sorting and then use filters button, it works correct.
But when i'm trying to filter first and then change sorting, i get sorted cards, but filters work incorrect
Also i set up a js fiddle to represent what i want.
Why don't you just add:
result = result.filter(todo => todo.price >= this.price);
before sorting? Your watcher and show property in your todos are not necessary too.
https://jsfiddle.net/3wLtk6zn/4/
Edit: if you want to do it your way with a watcher, it's possible too:
https://jsfiddle.net/3wLtk6zn/8/
The reason why your code didn't work is that each time your todos property is recomputed you set your result to array with all items and your watcher only filters your result when you change the price, so changing sorting method "unfilters" the array. This can be fixed by moving result outside your computed property to data.

Categories

Resources