Vue updating all components in v-for - javascript

In my component the v-for displays all items in the allItems array brought from firestore.
Add the end of there as an Add button so that the user can add an item to his basket for that I have an array set up in store set up in which the items get added to.
I have a v-if on that button show the message Item Added when clicked for that particular item in which itemAdded becomes true. So that the Item Added message can be shown and the button dissapears.
But when I click the button all the items show the message Item Added.
I want only the particular item to show the message. Can anyone suggest a solution for this.
The Component
<v-col cols="12" sm="6" md="3" v-for="(item) in allItems" :key="item.ITEM_NAME">
<v-card
class="d-flex flex-row disableScroll"
flat
tile
style="margin-top: 16px; padding: 2px; overflow-x: auto;">
<v-card max-width="250">
<v-img
class="white--text align-end"
height="200px"
src="../../assets/img/category_blank.jpg"
>
<v-card-title>{{item.ITEM_NAME}}</v-card-title>
</v-img>
<v-card-subtitle class="pb-0">{{item.ITEM_DESC}}</v-card-subtitle>
<v-card-actions>
<v-card-text>Rs {{item.ITEM_PRICE}}</v-card-text>
<v-btn v-if="item.ITEM_STOCK && !itemAdded" style="margin-left: auto" color="orange" text #click="addItem(item)">
Add
</v-btn>
<p v-else-if="itemAdded">Item Added</p>
<p v-else> Out of Stock</p>
</v-card-actions>
</v-card>
</v-card>
</v-col>
The Script
<script>
import {db, getStoreID} from "#/main";
export default {
data() {
return {
allItems: [],
orderItems: {},
itemAdded: false,
}
},
name: "HomeCategories",
props: ["category"],
created() {
let docID = getStoreID();
db.collection("STORES")
.doc(docID)
.collection("ITEMS")
.doc("DATA")
.collection(this.$props.category).get().then((querySnapshot) => {
querySnapshot.forEach((doc) => {
this.allItems.push(doc.data())
})
})
},
methods: {
addItem(item){
this.orderItems.name = item.ITEM_NAME,
this.orderItems.price = item.ITEM_PRICE,
this.orderItems.isAvail = item.ITEM_AVAILABLE,
this.orderItems.imgUrl = item.ITEM_IMG_URL,
this.orderItems.quantity = 1,
this.itemAdded = true
this.$store.commit("ADD_ITEM_TO_BASKET", this.orderItems);
}
}
};
</script>

When you are checking condition instead of itemAdded as boolean, make it as an Array
create a new function in methods like this
isItemAdded(value) {
if (this.itemAdded.includes(value)) {
return true;
}
return false;
}
now, in the addItem function add the value to array this.itemAdded like this
this.itemAdded.push(item);
Changes in the HTML
<v-btn v-if="item.ITEM_STOCK && !isItemAdded(item)" style="margin-left: auto"
color="orange" text #click="addItem(item)">
Add
</v-btn>
<p v-else-if="isItemAdded(item)">Item Added</p>
And you are all set. Cheers

Your itemAdded value is boolean, so it has only 2 values: true and false, but you use it for every item, so if you change it then you change it for every item.
This option for only 1 item can be added.
You should be more precise. For example, you can keep item name in itemAdded, so you will always know what item was added. In that case only one item can have this flag.
This option if every item can be added once.
Another option is to treat itemAdded as array, so every item can be added (once).
First, change component data to have itemsAdded to be array:
...
data() {
return {
allItems: [],
orderItems: {},
itemAdded: [], // <--- here we change itemAdded to be array
}
},
...
Now we need to check itemAdded not as boolean, but as array of boolean:
...
<v-btn v-if="item.ITEM_STOCK && !itemAdded[item.ITEM_NAME]"
...
<p v-else-if="itemAdded[item.ITEM_NAME]">Item Added</p>
...
And finally we change the flag for item on button click:
...
methods: {
addItem(item){
...
this.itemAdded[item.ITEM_NAME] = true; // <--- see array here?
...
If you need, I can add some code examples.

Related

BootstrapVue b-table not storing selected rows when changing pages using b-pagination

I have the following b-table and b-pagination in my template:
<b-table
bordered
hover
responsive
:items="items"
:fields="fields"
:sort-by.sync="sortBy"
:sort-desc.sync="sortDesc"
sort-icon-left
:filter="searchFilter"
#filtered="onFiltered"
:filter-included-fields="filterOn"
:per-page="perPage"
:current-page="currentPage"
class="table1"
ref="table1"
selectable
#row-selected="selectedRow"
:tbody-tr-class="styleRow"
>
<template #cell(selected)="{ rowSelected }">
<template v-if="rowSelected">
<span class="sr-only">Selected</span>
</template>
<template v-else>
<span class="sr-only">Not selected</span>
</template>
</template>
</b-table>
<b-pagination
v-model="currentPage"
:total-rows="totalRows"
:per-page="perPage"
align="center"
limit="15"
></b-pagination>
In my Vue app, I have the following functions for when the user selects a row:
export default {
data() {
return {
items: [],
sortBy: 'build',
sortDesc: true,
searchFilter: '',
perPage: 11,
currentPage: 10,
totalRows: 1,
fields: [],
defaultFields: [],
fieldNames: [],
filterOn: [],
selectedInfo: { item: {}, value: {}, field: {} },
selected: [],
};
},
methods: {
styleRow(item) {
if (item.selected) {
return ['b-table-row-selected', 'table-secondary'];
}
return [];
},
selectedRow(items) {
this.selected = items;
}
},
};
Here's the problem: let's say the user selects a few rows on page 1 of the table, then goes to page 2, and then goes back to page 1. The previously clicked rows are no longer selected. How do I fix this? How do I prevent pagination from resetting the selected rows?
Based on BootstrapVue documentation, Sorting/filtering/paginating the table will clear the active selection. The row-selected event will be emitted with an empty array ([]) if needed.
My suggestion is to store the selected rows to another variable (eg: selectedList: []) on row-selected, so that it will not emitted with an empty array when pagination trigerred. Then, append the selectedList on refreshed Event, so that when you go to next page or ack to previous page, selectedList variable will hold all rows you've selected.

How to put selected list values inside array in vue

I have a vue application where I have to select two elements from an list component and then put them inside an array.Right now I have my list and I can select them thanks to vuetify I binded it to an array with v-model I can console log it inside of an array but what I want to achieve is something like this:
{
"meetingName":"",
"meetingUrl":"",
"participants":{
participant1: "Hasan",
participant2: "Turan"
}
}
instead I am getting right now this:
{
"meetingName":"",
"meetingUrl":"",
"participants":[
"Hasan",
"Turan"
]
}
Could someone look at my code and tell me what is wrong with it?
html:
<template>
<v-container>
<v-row>
<v-col cols="4">
<v-card>
<v-list>
<v-list-item-group
v-model="model"
multiple
color="indigo"
v-model="model.participants"
>
<v-list-item
v-for="(item, i) in voterArrayFilteredByTime"
:key="item.voterUniqueName"
:value="item.voterUniqueName"
v-on:click= "generateGroup"
>
<v-list-item-content>
<v-list-item-title v-text="item.voterUniqueName"></v-list-item-title>
</v-list-item-content>
</v-list-item>
</v-list-item-group>
</v-list>
</v-card>
</v-col>
</v-container>
</template>
an here is the method with which I want to console log it but it does not work like I said I am just getting numbers.
<script>
import axios from "axios";
export default {
name: "AddGroupsModal",
data : ()=>({
singleSelect: false,
selection: "",
model:{ meetingName: "", meetingUrl: "", participants: [] },
methods: {
generateGroup(){
console.log(this.model)
}
}
</script>
One of the problems in your markup is it has two bindings to v-model when there should be just one (the last one in this case):
<v-list-item-group
v-model="model"❌
multiple
color="indigo"
v-model="model.participants"❌
>
The v-list components can't create the expected value format, but you can use a computed prop along with Array.prototype.reduce to create an object from the array entries:
export default {
computed: {
computedParticipants() {
return this.model.participants.reduce((obj, name, i) => {
obj[`participant${i + 1}`] = name
return obj
}, {})
// OR:
const obj = {}
this.model.participants.forEach((name, i) => {
obj[`participant${i + 1}`] = name
})
return obj
},
},
}
demo

Vuejs: How to make this if else as Boolean?

I have a component with buttons that show some elements (v-autocomplete) when we click on them, but I should make it with a Boolean to simplify the code, how can I do that?
Because actually, it adds the index of the item in one array in showCompetence state, but I just would like a Boolean on each index Basically at the "openCompetence" function in "Methods".
import { mapGetters, mapActions } from "vuex";
export default {
name: "SkillCvCard",
data() {
return {
selectedCompetence: []
}
},
updated() {
this.addSelectSkill(this.userCompetences.competences.list);
console.log(this.selectedSkills)
},
props: {
userCompetences: {
type: Array
},
showCompetence: {
type: Array
}
},
computed: {
...mapGetters(["selectedSkills"]),
console: () => console,
},
methods: {
...mapActions(['addSelectSkill']),
openCompetence(index) {
if (this.showCompetence.includes(index)) {
console.log("close")
this.showCompetence.splice(this.showCompetence.indexOf(index), 1)
} else {
this.showCompetence.push(index)
console.log("open")
}
console.log(this.showCompetence)
}
}
about the template I will just add the minimum I hope it will be ok:
The first is where we click, to launch the "openCompetence" function.
<div v-for="(competences, index) of userCompetences.competences" :key="index">
{{ competences.category }} <v-btn #click="openCompetence(index)"> Add </v-btn>
</div>
the rest is a v-container with a v-for including multiple v-autocomplete, but the most important inside is the v-if:
<div class="skill-field" v-for="(skill, index) of userCompetences.competences" :key="index">
<template>
<v-flex md12 sm12 xs12 v-if="skill.list.length>0">
<v-autocomplete
v-if="showCompetence.includes(index)"
v-model="userCompetences.competences.list"
:items="skill.list"
chips
hide-selected
:label="skill.category"
item-text="name"
item-value="name"
multiple
>
</v-autocomplete>
</v-flex>
</template>
</div>
Normally, you would use a computed property to solve a problem like this. However, because you are inside of a v-for, a computed property is a little more difficult since you can't evaluate it in the context of each value of the v-for's index.
There are two common options:
Do exactly what you are doing. It's actually no less efficient since it will not re-evaluate unless data it depends on changes.
Add a computed called something like competencesDisplayState that returns an array of booleans that matches the ordering of the userCompetences.competences array in the v-for. Then your v-if can become something like:
v-if="competencesDisplayState[index]"
I normally just opt for the first approach as it is simpler and is easier to read and maintain.
If you want to go route 2, here's some code for the competencesDisplayState computed:
competencesDisplayState: () => {
var result = [];
for (var index=0;index < this.userCompetences.competences.length;++index) {
result(this.showCompetence.includes(index));
}
return result;
}

How to filter data using boolean(True or False) in VueJS?

I'm creating a button to filter those data(games) that a user bought or have though in my code it only filters data with no boolean attribute set upon them.
Edited: added some more details on the code with firebase data to ref
<!-- This is the button I'm having trouble with -->
<v-tooltip right>
<v-btn small flat color="grey" #click="toggleHave(true)" slot="activator">
<v-icon left small>title</v-icon>
<span class="caption text-lowercase">by All titles bought</span>
</v-btn>
<span>Sort by Game's Title that I have</span>
</v-tooltip>
</v-flex>
<!-- filterGames is for the search method -->
<v-layout row wrap>
<v-card v-for="game in filterGames" :key="game.id" class="ma-2" width="240px">
<router-link :to="{ name: 'view-game', params: { game_id: game.game_id }}">
<v-img :src="game.cover" :alt="game.name" />
</router-link>
<v-card-title>
<div>
<span class="subheading">{{ game.title }}</span><br>
<span class="caption grey--text">{{ game.platform }}</span><br>
</div>
</v-card-title>
</v-card>
</v-layout>
export default {
data() {
return {
games: [], //this is connected to firebase
search: '',
}
},
toggleHave(bought) {
console.log(bought)
this.games = this.games.filter(game => {
return game.have === bought.have
})
},
computed: {
filterGames() {
return this.games.filter((game) => {
return game.title.toLowerCase().match(this.search)
})
}
}
}
// with true
game_id: "006"
title: "Far Cry 4"
have: true // this is the boolean
// with false
game_id: "051"
title: "Assassin's Creed Unity"
have: false // this is the boolean
You're calling toggleHave(true), but in your toggleHave function you use argument as it was an object:
game.have === bought.have
That means you compare game.have with undefined, which explains current behaviour.
Replace bought.have with bought and it should work.
Your question is more of a JavaScript question then a vue question. The filter function returns an array of the elements that meet your condition see here
If you would like to receive Boolean on filtering try using the includes method like here
see this Filter list with Vue.js
just change
return this.games.filter(game => {
return game.have == bought
})

Why does V-select value changes on second click instead of first?

I have a V-select like below, and when I load the page it gets filled with data from my Vuex-store. I then have a computed property to get the currently selected company. My problem is that the value from the currently selected company only updates after I click on it Twice. Have I done something wrong in the implementation?
So when a user changes value in the V-select I want to update the list of users being shown, but this only works if the user clicks twice on the v-select selection.
<template>
<v-container fluid fill-height>
<v-layout child-flex>
<v-card>
<v-card-title>
<h3>Användare</h3>
<v-spacer></v-spacer>
<v-select
v-bind:items="listOfCompanys"
v-model="selectedCompany"
item-value="customerid"
item-text="name"
single-line
bottom
v-on:change="onChangeCompany"
autocomplete></v-select>
</v-card-title>
<UserTable></UserTable>
</v-card>
</v-layout>
</v-container>
</template>
<script>
import { FETCH_COMPANY_LIST, FETCH_USER_LIST } from '../store/actions.type'
import UserTable from './UserTable.vue'
export default {
name: 'Users',
data: () => ({
selectedCompany: 0
}),
components: {
UserTable
},
methods: {
onChangeCompany () {
this.$store.dispatch(FETCH_USER_LIST, this.currentCompany)
}
},
mounted: function () {
this.$store.dispatch(FETCH_COMPANY_LIST)
},
computed: {
listOfCompanys () {
return this.$store.state.users.companyList
},
currentCompany () {
return this.selectedCompany
}
}
}
</script>
Don't do both v-model and v-on:change. You're sending this.currentCompany, which I think is supposed to be selectedCompany. If the idea is to send the value when it changes, put a watch on the value, not on the widget. The widget feeds the value, the value feeds the store.

Categories

Resources