How to get rid of cloning / duplication of elements in vue? - javascript

I'm a beginner programmer and I create a spa like trello. Creates boards. In the board creates lists, they are displayed different with different id, but the list items are displayed with the same id and they are duplicated in each list. Sorry for my english:) Help me, please and tell in detail what the problem is.. Thank you very much
vuex file router.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
boards: JSON.parse(localStorage.getItem('boards') || '[]'),
lists: [],
items: []
// items: JSON.parse(localStorage.getItem('items') || '[]')
// lists: JSON.parse(localStorage.getItem('lists') || '[]')
},
mutations: {
addBoard(state, board) {
state.boards.push(board)
localStorage.setItem('boards', JSON.stringify(state.boards))
},
addList(state, list) {
state.lists.push(list)
// localStorage.setItem('lists', JSON.stringify(state.lists))
},
createItemListt(state, item) {
state.items.push(item)
// localStorage.setItem('items', JSON.stringify(state.items))
}
},
actions: {
addBoard({commit}, board) {
commit('addBoard', board)
},
addList({commit}, list) {
commit('addList', list)
},
createItemListt({commit}, item) {
commit('createItemListt', item)
}
},
getters: {
boards: s => s.boards,
taskById: s => id => s.boards.find(t => t.id === id),
lists: d => d.lists,
items: a => a.items
},
modules: {
}
})
the page on which the lists are created MyBoard.vue
<template>
<div>
<div class="wrapper">
<div class="row">
<h1>{{board.title}}</h1>
<div class="list " v-for="list in lists" :key="list.idList">
<div class="list__title">
<h3>{{list.titleList}}</h3>
</div>
<div class="list__card" v-for="item in items" :key="item.idItemList">
<span class="list__item">{{item.itemList}}</span>
<a class="btn-floating btn-tiny btn-check" tag="button">
<i class="material-icons">check</i>
</a>
</div>
<createItemList />
</div>
<createList />
</div>
</div>
</div>
</template>
<script>
export default {
computed: {
board() {
return this.$store.getters.taskById(+this.$route.params.id);
},
lists() {
return this.$store.getters.lists;
},
items() {
return this.$store.getters.items;
}
},
components: {
createList: () => import("../components/createList"),
createItemList: () => import("../components/createItemList")
}
};
</script>
CreateList.Vue
<template>
<div>
<div class="row">
<div class="new-list" v-show="isCreating">
<div class="list__title input-field">
<input
type="text"
required
id="list-title"
class="none validate"
tag="button"
autofocus
v-model="titleList"
v-on:keyup.enter="createList"
/>
<label for="list-title">Enter Title List</label>
</div>
<a class="btn-floating transparent btn-close" tag="button" #click="closeList">
<i class="material-icons">close</i>
</a>
</div>
<div class="create-list z-depth-2" v-show="!isCreating">
<p>Create list</p>
<a
class="btn-floating btn-large waves-effect waves-light deep-purple lighten-2 pulse"
tag="button"
#click="addList"
v-on:keyup.enter="addList"
>
<i class="material-icons">add</i>
</a>
</div>
</div>
</div>
</template>
<script>
export default {
data: () => ({
isCreating: false,
titleList: "",
idList: ""
}),
methods: {
addList() {
this.isCreating = true;
},
closeList() {
this.isCreating = false;
},
createList() {
if (this.titleList == "") {
return false;
}
const list = {
idList: Date.now(),
titleList: this.titleList
};
this.$store.dispatch("addList", list);
this.titleList = "";
this.isCreating = false;
console.log(list.titleList);
}
}
};
</script>
CreateItemList.vue
<template>
<div>
<div class="add-item">
<div class="textarea-item input-field" v-show="isAdding">
<input
type="text"
class="validate"
id="list-item"
v-model="itemList"
v-on:keyup.enter="createItemList"
autofocus
/>
<label for="list-item">Enter Item List</label>
</div>
<a class="waves-effect waves-light btn" v-show="!isAdding" #click="addCard">
<i class="material-icons right">add</i>Add Card
</a>
</div>
</div>
</template>
<script>
export default {
data: () => ({
isAdding: false,
itemList: "",
}),
methods: {
addCard() {
this.isAdding = true;
},
createItemList() {
if (this.itemList == "") {
return false;
}
const item = {
idItemList: Date.now(),
itemList: this.itemList
};
this.$store.dispatch("createItemListt", item);
this.itemList = "";
this.isAdding = false;
}
}
};
</script>
attach photo

Tried to go with the basic idea of the structure you laid out. I added:
id to all items, so they can be identifed
children to appropriate items, so you can keep track of what's in them
const store = new Vuex.Store({
state: {
tables: [
{ id: 1, children: ['1.1', '1.2'] },
{ id: 2, children: ['2.1'] }
],
lists: [
{ id: '1.1', children: ['1.1.1'] },
{ id: '1.2', children: ['1.2.1'] },
{ id: '2.1', children: ['2.1.1', '2.1.2'] },
],
cards: [
{ id: '1.1.1' },
{ id: '1.2.1' },
{ id: '2.1.1' },
{ id: '2.1.2' },
]
},
mutations: {
ADD_CARD(state, listId) {
const list = state.lists.find(e => e.id === listId)
const cards = state.cards
const card = { id: Date.now() }
cards.push( card )
list.children.push( card.id )
},
ADD_LIST(state, tableId) {
const table = state.tables.find(e => e.id === tableId)
const lists = state.lists
const list = { id: Date.now(), children: [] }
lists.push( list )
table.children.push( list.id )
},
ADD_TABLE(state) {
const tables = state.tables
const table = { id: Date.now(), children: [] }
tables.push( table )
},
TRY_MOVING_LIST(state) {
const table1 = state.tables.find(e => e.id === 1)
const table2 = state.tables.find(e => e.id === 2)
const item = table1.children.pop() // remove the last item
table2.children.push(item)
}
},
actions: {
addCard({ commit }, listId) {
commit('ADD_CARD', listId)
},
addList({ commit }, tableId) {
commit('ADD_LIST', tableId)
},
addTable({ commit }) {
commit('ADD_TABLE')
},
tryMovingList({ commit }) {
commit('TRY_MOVING_LIST')
}
},
getters: {
getTables: s => s.tables,
getListById: s => id => s.lists.find(e => e.id === id),
getCardById: s => id => s.cards.find(e => e.id === id),
}
})
Vue.component('CustomCard', {
props: ['card'],
template: `<div>
card ID: {{ card.id }}<br />
</div>`
})
Vue.component('CustomList', {
props: ['list'],
template: `<div>
list ID: {{ list.id }}<br />
<custom-card
v-for="item in list.children"
:key="item"
:card="getCard(item)"
/>
<button #click="addCard">ADD CARD +</button>
<hr />
</div>`,
methods: {
getCard(id) {
return this.$store.getters.getCardById(id)
},
addCard() {
this.$store.dispatch('addCard', this.list.id)
}
}
})
Vue.component('CustomTable', {
props: ['cTable'],
template: `<div>
table ID: {{ cTable.id }}<br />
<custom-list
v-for="item in cTable.children"
:key="item"
:list="getList(item)"
/>
<button #click="addList">ADD LIST +</button>
<hr />
</div>`,
methods: {
getList(id) {
return this.$store.getters.getListById(id)
},
addList(id) {
this.$store.dispatch('addList', this.cTable.id)
}
}
})
new Vue({
el: "#app",
store,
computed: {
tables() {
return this.$store.state.tables
}
},
methods: {
addTable() {
this.$store.dispatch('addTable')
},
tryMovingList() {
// this function will move the last list in table ID 1
// to the end of table ID 2's lists
// NOT FOOLPROOF - you should add error handling logic!
this.$store.dispatch('tryMovingList')
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<script src="https://unpkg.com/vuex"></script>
<div id="app">
<button #click="tryMovingList()">MOVE LIST</button><br />
<button #click="addTable()">ADD TABLE +</button>
<hr />
<custom-table v-for="item in tables" :key="'table-' + item.id" :c-table="item" />
</div>
With this setup you can change the hierarchy quite easily: just delete an ID from one Array of children and add it to another (e.g. remove '1.1' from table ID 1's children array and add it to table ID 2's children array - everything moved to table ID 2. tryMovingList() does exactly this - this method/action is not foolproof, just for you to try out moving a whole list)
There could be other patterns to solve this problem (like a real linked list data structure or the mediator pattern), but for smaller apps this is OK, I think (I would use it... :) ).
ONE PIECE OF ADVICE
If you want to store state in localStorage on mutations, don't do it by yourself - use Vuex's integrated subscribe mechanism: https://vuex.vuejs.org/api/#subscribe

Related

How do I watch the output of a function?

I have 2 buttons. One adds a movie to local storage, the other removes it from there. I made a function that basically switches the button. If the movie is added it shows "remove", if the movie's not been added it shows the button "add".
The function works but it doesn't know when the boolean changes so the button doesn't change. Someone explained that i should use watch property, but how am I supposed to watch an output of a function?
here is the code
<template>
<div>
<div class="card" v-for="movie in movies"
:key="movie.id">
{{movie.title}}
{{movie.release_date}}
<button v-show="!showButton(movie.id)" type="submit" #click="storeMovie(movie.id)" >
Aggiungi
</button>
<button v-show="showButton(movie.id)" type="submit" #click="removeMovie(movie.id)">
Rimuovi
</button>
</div>
<div class="card" v-for="favourite in watchlist"
:key="favourite.id">
{{favourite.title}}
</div>
</div>
</template>
<script>
import axios from 'axios'
export default {
name: 'HomeComp',
data () {
return {
movies: [],
watchlist: [],
movie: null,
}
},
mounted () {
axios
.get('https://api.themoviedb.org/3/movie/popular?api_key=###&language=it-IT&page=1&include_adult=false&region=IT')
.then(response => {
this.movies = response.data.results
// console.log(response.data.results)
})
.catch(error => {
console.log(error)
this.errored = true
})
.finally(() => this.loading = false)
},
watch: {
switchButton(oldValue, newValue) {
if (oldValue != newValue) {
this.showButton(id) = true;
} //made an attempt here
}
},
methods: {
storeMovie(id) {
const favouriteMovie = this.movies.find(movie => movie.id === id )
this.watchlist.push(favouriteMovie);
localStorage.setItem("watchlist", JSON.stringify(this.watchlist));
},
removeMovie(id) {
const removedMovie = this.watchlist.find(movie => movie.id === id )
const indexMovie = this.watchlist.indexOf(removedMovie);
if (indexMovie > -1) {
this.watchlist.splice(indexMovie, 1);
}
localStorage.setItem("watchlist", JSON.stringify(this.watchlist));
},
showButton(id) {
const favouriteMovie = this.watchlist.find(movie => movie.id === id )
if (favouriteMovie && favouriteMovie.length > 0) {
return true
} else{
return false
}
}
},
}
</script>
<style scoped lang="scss">
</style>
A better approach would be to store the state of a movie being stored or not in the watchlist directly on the movie object.
Then use a computed to get the watchlist from the movie list instead of using two different arrays.
<template>
<div>
<div class="card" v-for="movie in movies" :key="movie.id">
{{movie.title}}
{{movie.release_date}}
<button v-show="!movie.toWatch" type="submit" #click="storeMovie(movie.id)">
{{ movie.toWatch ? 'Rimuovi' : 'Aggiungi' }}
</button>
</div>
<div class="card" v-for="favourite in watchList" :key="favourite.id">
{{favourite.title}}
</div>
</div>
</template>
<script>
export default {
name: 'HomeComp',
data() {
return {
movies: [],
}
},
computed: {
// Get the watchList from the movies list
watchList() {
return this.movies.filter(movie => movie.toWatch)
}
},
watch: {
watchList(newWatchList) {
// Update the localStorage whenever the list changes
localStorage.setItem("watchlist", JSON.stringify(newWatchList));
}
},
mounted() {
// your axios call
},
methods: {
storeMovie(id) {
const favouriteMovie = this.movies.find(movie => movie.id === id)
if (favouriteMovie) {
// just reverse the boolean
favouriteMovie.toWatch = !favouriteMovie.toWatch
}
},
},
}
</script>

Toggle class(or change style) of element when element in clicked, Vuejs

The way i am getting data is little bit complicated. I have "tweets" array where data is stored and each tweet is a card where i successfully change style when card is clicked(markTweet function), but there are also replies for each tweet which are shown same as tweets(each reply has its own card). The way i am getting data from server:
let replies = []
for(const tweet of tweets) {
let reply = await SQL('SELECT * FROM tweet_replies WHERE tweet_replies.conversation_id = ?', tweet.tweet_id)
replies.push(reply)
}
const data = {
tweets: tweets,
page: parseInt(currentPage),
numberOfPages: arr,
replies
}
Then i have component in vue. You can see replies are stored in tweets array in each tweet as tweetReplies.
In markReply func, am succesfully adding id to array.
<template>
<div class="container-full">
<div class="tweets-container">
<div
v-for="(tweet, i) in tweets"
:key="tweet.id"
>
<div
class="tweet-card"
:class="{ selected: tweet.isSelected }"
#click="markTweet(tweet.tweet_id, i)"
>
<div class="text">
<p
v-html="tweet.tweet_text"
>
{{ tweet.tweet_text }}
</p>
</div>
</div>
<div class="replies">
<div
v-for="(reply, index) in tweet.tweetReplies"
:key="reply.tweet_id"
#click="markReply(reply.tweet_id, index)"
>
<div class="tweet-card tweet-reply">
<div class="text">
<p>
{{ reply.tweet_text }}
</p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import axios from 'axios'
import { getUserToken } from '#/auth/auth'
import moment from 'moment'
import { BFormTextarea, BButton, BFormSelect } from 'bootstrap-vue'
export default {
components: { BFormTextarea, BButton, BFormSelect },
data() {
return {
tweets: [],
tweetActionIds: [],
categories: [],
}
},
beforeMount() {
this.getTweets()
},
methods: {
getTweets() {
this.tweets = []
const API_URL = `${this.$server}/api/twitter/tweets`
const params = {
token: getUserToken(),
page: this.$route.query.page,
newCurrentPage: newCurrent,
}
axios.post(API_URL, null, { params }).then(res => {
this.currentPage = res.data.page
this.numberOfPages = res.data.numberOfPages
if (res.data) {
res.data.tweets.forEach(tweet => {
const tweetData = {
id: tweet.id,
tweet_id: tweet.tweet_id,
tweet_text: htmlText,
tweet_text_en: htmlTextEn,
twitter_name: tweet.twitter_name,
twitter_username: tweet.twitter_username,
added_at: moment(String(tweet.added_at)).format(
'MM/DD/YYYY hh:mm',
),
URL: tweet.URL,
isSelected: false,
tweetReplies: [],
}
this.tweets.push(tweetData)
})
res.data.replies.forEach(reply => {
reply.forEach(r => {
this.tweets.forEach(tweet => {
if (tweet.tweet_id === r.conversation_id) {
tweet.tweetReplies.push(r)
}
})
})
})
}
})
},
markTweet(tweetId, i) {
const idIndex = this.tweetActionIds.indexOf(tweetId)
this.tweets[i].isSelected = !this.tweets[i].isSelected
if (this.tweetActionIds.includes(tweetId)) {
this.tweetActionIds.splice(idIndex, 1)
} else {
this.tweetActionIds.push(tweetId)
}
},
markReply(replyId) {
const idIndex = this.tweetActionIds.indexOf(replyId)
if (this.tweetActionIds.includes(replyId)) {
this.tweetActionIds.splice(idIndex, 1)
} else {
this.tweetActionIds.push(replyId)
}
},
},
}
</script>
I have tried to add replySelected in data and then when click is triggered in markReply i changed replySelected to true, but every reply of a tweet was then selected, which is not what i want.
If I understood you correctly try like following snippet:
const app = Vue.createApp({
data() {
return {
tweets: [{id: 1, tweet_id: 1, isSelected: true, tweet_text: 'aaa', tweetReplies: [{tweet_id: 11, tweet_text: 'bbb'}, {tweet_id: 12, tweet_text: 'ccc'}]}, {id: 2, tweet_id: 2, isSelected: false, tweet_text: 'ddd', tweetReplies: [{tweet_id: 21, tweet_text: 'eee'}, {tweet_id: 22, tweet_text: 'fff'}]}],
tweetActionIds: [],
}
},
methods: {
markTweet(tweetId, i) {
const idIndex = this.tweetActionIds.indexOf(tweetId)
this.tweets[i].isSelected = !this.tweets[i].isSelected
if (this.tweetActionIds.includes(tweetId)) {
this.tweetActionIds.splice(idIndex, 1)
} else {
this.tweetActionIds.push(tweetId)
}
},
markReply(replyId) {
const idIndex = this.tweetActionIds.indexOf(replyId)
if (this.tweetActionIds.includes(replyId)) {
this.tweetActionIds.splice(idIndex, 1)
} else {
this.tweetActionIds.push(replyId)
}
},
checkReply(r) {
return this.tweetActionIds.includes(r) ? true : false
}
},
})
app.mount('#demo')
.selected {color: red;}
<script src="https://cdn.jsdelivr.net/npm/vue#3/dist/vue.global.prod.js"></script>
<div id="demo">
<div class="container-full">
<div class="tweets-container">
<div v-for="(tweet, i) in tweets" :key="tweet.id">
<div
class="tweet-card"
:class="{ selected: tweet.isSelected }"
#click="markTweet(tweet.tweet_id, i)"
>
<div class="text">
<p v-html="tweet.tweet_text">
{{ tweet.tweet_text }}
</p>
</div>
</div>
<div class="replies">
<div
v-for="(reply, index) in tweet.tweetReplies"
:key="reply.tweet_id"
#click="markReply(reply.tweet_id, index)"
>
<div class="tweet-card tweet-reply">
<div class="text" :class="{selected: checkReply(reply.tweet_id)}">
<p>{{ reply.tweet_text }}</p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{{tweetActionIds}}
</div>
You can build on Nikola's answer by bypassing the extra step of adding isSelected to each individual Tweet by just checking if it's in the tweetActionIds array, and then do the same with the replies to keep it clean
<div id="demo">
<div class="container-full">
<div class="tweets-container">
<div
v-for="(tweet, i) in tweets"
:key="tweet.id"
>
<div
class="tweet-card"
:class="{ selected: isActive(tweet) }"
#click="markTweet(tweet.tweet_id, i)"
>
<div class="text">
<p v-html="tweet.tweet_text">
{{ tweet.tweet_text }}
</p>
</div>
</div>
<div class="replies">
<div
v-for="(reply, index) in tweet.tweetReplies"
:key="reply.tweet_id"
#click="markReply(reply.tweet_id, index)"
>
<div
:class="{ selected: isActive(reply) }"
class="tweet-card tweet-reply"
>
<div class="text">
<p>{{ reply.tweet_text }}</p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{{tweetActionIds}}
</div>
const app = Vue.createApp({
data() {
return {
tweets: []
tweetActionIds: [],
categories: [],
}
},
methods: {
markTweet(tweetId, i) {
const idIndex = this.tweetActionIds.indexOf(tweetId)
if (this.tweetActionIds.includes(tweetId)) {
this.tweetActionIds.splice(idIndex, 1)
} else {
this.tweetActionIds.push(tweetId)
}
},
markReply(replyId) {
const idIndex = this.tweetActionIds.indexOf(replyId)
if (this.tweetActionIds.includes(replyId)) {
this.tweetActionIds.splice(idIndex, 1)
} else {
this.tweetActionIds.push(replyId)
}
},
isSelected(tweet) {
return this.tweetActionIds.includes(tweet.tweet_id);
}
},
})

Nuxtjs: Axios Request does not work when switching to another route

i try to build a little clothing web shop with nuxtjs. You can choose the color on the details page. The details page represents a pice of clothing. The ColorMenu is a component. If you choose something a color, it will emit it back to the details page and will send a new details request to my backend.
However, changing the color only works if you don't choose another piece of clothing. If you choose another piece of clothing (so the route parameters will change) and choose another color in the menu, there is a always an error that it cannot load anything. it seems that it sends repeated requests until the request is blocked.
The details routes are built according to this scheme: localhost/details/{sellableId}/{ideaId}/{appearanceId}
Details Page:
<template>
<section class="section">
<div v-if="details">
<div class="columns">
<div class="column">
<ImageCaroussel :images="details.images"></ImageCaroussel>
</div>
<div class="column">
<h3>Farben</h3>
<ColorMenu
:appearances="productType.appearances"
:appearanceIds="details.appearanceIds"
></ColorMenu>
</div>
</div>
</div>
</section>
</template>
<script>
import { mapState } from 'vuex'
import Dropdown from '~/components/details/Dropdown.vue'
import ColorMenu from '~/components/details/ColorMenu.vue'
import ImageCaroussel from '~/components/details/ImageCaroussel.vue'
export default {
created() {
this.$nuxt.$on('selected', ($event) => (this.selected = $event))
this.$nuxt.$on('selectedColor', ($event) => this.setSelectedColor($event))
},
data() {
return {
modal: false,
selected: '',
selectedColor: '',
}
},
async asyncData({ store, params }) {
console.log('asyncfirst')
if (params.sellableId && params.appearanceId && params.ideaId) {
await store.dispatch('details/get_details', {
sellableId: params.sellableId,
appearanceId: params.appearanceId,
ideaId: params.ideaId,
})
let sellableId = params.sellableId
let appearanceId = params.appearanceId
let ideaId = params.ideaId
console.log('asyncsecond!')
return { sellableId, appearanceId, ideaId }
}
},
mounted() {
this.sellableId = this.$route.params.sellableId
this.appearanceId = this.$route.params.appearanceId
this.ideaId = this.$route.params.ideaId
console.log('Mounted!')
},
components: {
Dropdown,
ColorMenu,
ImageCaroussel,
},
computed: {
...mapState({
details: (state) => {
return state.details.details
},
currency: (state) => {
return state.sellable.currency
},
productType: (state) => {
return state.details.productType
},
}),
},
methods: {
checkout: async function (sellableId, size, appearanceId) {
let link = await this.$backendrepositories.basket.checkout(
sellableId,
size,
appearanceId
)
if (link.status === 200 && link.data) {
this.modal = true
setTimeout(() => {
window.location.href = link.data.link
}, 3000)
}
},
setSelectedColor: async function (event) {
this.selectedColor = event
await this.$store.dispatch('details/get_details', {
sellableId: this.sellableId,
appearanceId: this.selectedColor,
ideaId: this.ideaId,
})
},
},
}
</script>
ColorMenu Component:
<template>
<div>
<div
v-for="(cell, index) in appearances"
:key="index"
style="display: inline-block"
>
<label v-if="appearanceIds.includes(cell.id)" class="self-container">
<input type="radio" checked="checked" name="color" />
<span
class="checkmark"
:style="`background-color: ${cell.colors[0].value}`"
#click="select(cell.id)"
></span>
</label>
</div>
</div>
</template>
<script>
export default {
data: function () {
return {
selected: '',
}
},
props: ['appearances', 'appearanceIds'],
methods: {
select(select) {
this.selected = select
this.$nuxt.$emit('selectedColor', this.selected)
},
},
}
</script>
There is a live demo at https://akano-frontend.vercel.app/

How to control multiple checkbox in Vue

I have a checkbox component in Vue:
<template>
<div class="checkbox">
<input class="checkbox-input" name="input" type="checkbox" v-model="checkbox">
</div>
</template>
<script>
export default {
data(){
return {
checkbox: false
};
},
};
</script>
So in the parent component I want to control these checkbox. So here is my parent component:
<div class="card">
<div class="card-header">
<CheckBox />
</div>
<div class="card-body" v-for="item in cart" :key="item.product.id">
<div class="checkbox-area">
<CheckBox />
</div>
</div>
So checkbox in card-body can be added when user clicks to add. So if a user clicks 3 times, 3 checkbox are being added inside of card-body. What I am trying to achieve is, as you see in card-header there is another checkbox, and when this checkbox is clicked, I want to check all the checkboxes inside card-body, and when it is unchecked in card-header, I want to unchcecked everything inside card-body.
So do you have any idea how to achieve this?
Thanks...
You can try like this :
Vue.component('checkbox', {
template: `
<div class="checkbox">
<input class="checkbox-input" name="input" type="checkbox" #change="getCheck" v-model="value">
</div>
`,
props: {
checked: {
type: Boolean,
default: false
}
},
data(){
return {
value: this.checked
};
},
methods: {
getCheck() {
this.$emit("set-checked", this.value)
}
},
watch: {
checked(){
this.value = this.checked
}
}
})
new Vue({
el: '#demo',
data(){
return {
all: false,
cart: [
{id: 1, check: false},
{id: 2, check: false},
{id: 3, check: true},
{id: 4, check: false}
]
};
},
watch: {
cart() {
this.cart.find(c => c.check === false) ? this.all = false : this.all = true
}
},
methods: {
checkAll(val) {
this.all = val
this.cart = this.cart.map(c => {
c.check = val
return c
})
},
checkItem(id) {
this.cart = this.cart.map(c => {
if(c.id === id) {
c.check = !c.check
}
return c
})
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="demo">
<div class="card">
<div class="card-header">
<p>All</p>
<checkbox :checked="all" #set-checked="checkAll" />
</div>
<br/>
<div class="card-body" v-for="item in cart" :key="item.id">
<div class="checkbox-area">
<checkbox :checked="item.check" #set-checked="checkItem(item.id)" />
</div>
</div>
</div>
</div>
First of all you need to add some props to your Component. than a wachter to emit a sync when the Value of the checkbox changes.
<template>
<div class="checkbox">
<input class="checkbox-input" name="input" type="checkbox" v-model="value">
</div>
</template>
<script>
export default {
props: {
checked: {
type: Boolean,
default: false
}
}
data(){
return {
value: this.checked
};
},
watch: {
value(){
this.$emit("update:checked", this.value)
}
}
};
</script>
on the cart you need to watch for theses changes an than you can check/uncheck all the items.
<template>
<div class="card">
<div class="card-header">
<CheckBox :checked.sync="global"/>
</div>
<div class="card-body" v-for="item in cart" :key="item.product.id">
<div class="checkbox-area">
<CheckBox :checked.sync="item"/>
</div>
</div>
</div>
</template>
<script>
export default {
data(){
return {
global: false,
cart: [
false,
false,
false
]
};
},
watch: {
global(){
for (let i = 0; i < this.cart.length; i++) {
this.chart[i] = this.global
}
}
}
};
</script>
I have not tested this code, but this should work...
Using checkbox input as custom components can be a little tricky see if this code can help you:
code sandbox
Vue.component('checkbox', {
template: `
<label>
<input type="checkbox" :value="value" v-model="deltaValue" />
{{ label }}
</label>
`,
name: "checkbox",
model: {
prop: 'modelValue',
event: 'update:modelValue'
},
props: {
label: String,
value: [Object, Boolean, Array],
modelValue: {
type: [Array, Boolean],
default: () => [],
},
},
computed: {
deltaValue: {
get() {
return this.modelValue;
},
set(value) {
this.$emit("update:modelValue", value);
},
},
},
});
new Vue({
el: '#app',
data() {
return {
areAllSelected: false,
checkedItems: [],
cart: [
{ id: 1, name: "tablet"},
{ id: 2, name: "phone"},
{ id: 3, name: "PC" },
],
};
},
watch: {
areAllSelected(areAllSelected) {
if (areAllSelected) {
this.checkedItems = [...this.cart];
return;
}
this.checkedItems = [];
},
checkedItems(items){
if (items.length === this.cart.length) {
this.areAllSelected = true;
return;
}
if (items.length === 0) {
this.areAllSelected = false;
}
}
},
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div class="card">
{{ checkedItems }}
<div class="card-header">
<checkbox label="check all" v-model="areAllSelected" />
</div>
<hr />
<div class="card-body" v-for="product in cart" :key="product.id">
<checkbox
:label="product.name"
:value="product"
v-model="checkedItems"
/>
</div>
</div>
</div>

Vue: how to update cart total price when user increment item in child component

So I create this cart page in Vue. I just don't understand how to update the cart page total price when the quantity of the item child component increase or decreases. If the item component quantity increase, of course, the total price must increase too.
Here's my cart parent component :
<template>
<div class="cart-container">
<h1 class="cart-title-page">Keranjang Anda</h1>
<div class="cart-item-container">
<cart-item v-for="(data, i) in cartData" :item="data" :key="i" />
</div>
<div class="cart-total-wrapper">
<div class="total-text-wrapper">
<p>Total</p>
</div>
<div class="total-amount-wrapper">
<p>Rp. 150.000.000</p>
</div>
</div>
</div>
</template>
<script>
import CartItem from '#/components/cart-item'
export default {
data() {
return {
cartData: [
{
product_name: 'vario ZS1',
price: 1000000,
url_thumbnail: 'https://cdn3.imggmi.com/uploads/2019/10/8/9e27ca9046031f6f21850be39b379075-full.png',
color: '#fff'
},
{
product_name: 'vario ZS1',
price: 1000000,
url_thumbnail: 'https://cdn3.imggmi.com/uploads/2019/10/8/9e27ca9046031f6f21850be39b379075-full.png',
color: '#fff'
},
{
product_name: 'vario ZS1',
price: 1000000,
url_thumbnail: 'https://cdn3.imggmi.com/uploads/2019/10/8/9e27ca9046031f6f21850be39b379075-full.png',
color: '#fff'
}
]
}
},
methods: {
getAllCartItem () {
this.$store.dispatch('cart/checkCartItem')
this.cartData = this.$store.state.cart.cartItem
}
},
created () {
this.getAllCartItem ()
},
components: {
'cart-item': CartItem
}
}
</script>
this is my cart item child component:
<template>
<div class="root-cart-item">
<div class="container-cart-left">
<div class="cart-img-wrapper">
<img :src="item.url_thumbnail" />
</div>
<div class="cart-title-wrapper">
<div class="title-wrapper">
<h3>{{ getProductbrand }}</h3>
<p>{{ item.product_name }}</p>
</div>
</div>
</div>
<div class="container-cart-right">
<div class="cart-amount-wrapper">
<number-input v-model="singleCart.amount" :min="1" :max="singleCart.stok" inline center controls></number-input>
</div>
<div class="cart-price-wrapper">
<p>{{ getProductTotalPrice }}</p>
</div>
<div class="cart-delete-wrapper">
<img src="../assets/delete.svg"/>
</div>
</div>
</div>
</template>
<script>
import ProductImage from './product-image'
import VueNumberInput from '#chenfengyuan/vue-number-input';
export default {
props: {
item: {
type: Object,
required: true
}
},
data () {
return {
singleCart: {
stok: 15,
amount: 1,
totalPrice: 0
}
}
},
computed: {
getProductbrand: function () {
let splittedName = this.item.product_name.split(' ')
return splittedName[0]
},
getProductTotalPrice: function () {
var x = this.singleCart.totalPrice.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ".")
var totalPrice = `Rp. ${x}`
return totalPrice
}
},
watch: {
'singleCart.amount': {
handler: function () {
this.singleCart.totalPrice = this.singleCart.price * this.singleCart.amount
},
deep: true
}
},
components: {
'product-image': ProductImage,
'number-input': VueNumberInput
}
}
</script>>
and if anyone wondering, this is my cart store:
const state = {
cartItem: []
}
const getters = {
getAllCartItem: (state) => {
return state.cartItem
}
}
const mutations = {
updateCartItem: (state, cart) => {
state.cartItems = cart
}
}
const actions = {
checkCartItem: ({ commit }) => {
let item = JSON.parse(localStorage.getItem('cart'))
if (cart) {
commit('updateCartItem', item)
}
}
}
export default {
namespaced: true,
state,
getters,
actions,
mutations
}
like I said, the problem should be quite simple, I just have to update the CSS class .total-amount-wrapper in the parent component, when the quantity in the child component increase or decreases. The total price in the child cart-item component is working, I just have to find a way to count every total price in the child cart-item component, and show it in the parent component.
For update the parent you must use the v-model approach or use the $emit.
In your code you must update the input to use the v-model or you must $emit an event when price change.
The first is simple and you must follow the tutorial you find in the link above, the second is below.
Child Component
watch: {
'singleCart.amount': {
handler: function () {
this.singleCart.totalPrice = this.singleCart.price * this.singleCart.amount
this.$emit("priceChanged", this.singleCart.totalPrice);
},
deep: true
}
}
Parent
<template>
..
<div class="cart-item-container">
<cart-item v-for="(data, i) in cartData" :item="data" :key="i"
#priceChanged="onPriceChanged" />
</div>
</template>
<script>
methods: {
..
onPriceChanged(value) {
this.total += value;
}
}
</scritp>

Categories