It is my first project in vue. I am getting the cart-data from the server. i want to change quantity from the vue. When i click change quantity i.e up arrow and down arrow, change is reflected in the server database. but in UI i have to reload the page to see change which i do not want that.
i want to see the change in without reloading when button is click. and i want to run TotalCartPrice method without clicking any button. I mean when i open cart it should be automatically run. what is wrong?
cart.vue
<template>
<CartItem v-for="cart in carts" :key="cart.id" :carts="cart" />
</template>
<script>
import CartItem from "../components/cart/cartItem";
export default {
name: "CartPage",
components: {
CartItem
},
computed: {
...mapGetters(["carts"])
},
created() {},
methods: {
TotalCartPrice() {
var total = 0;
for (var i = 0; i < this.carts.length; i++) {
total += this.carts[i].product.price * this.carts[i].quantity;
}
return total;
}
}
};
CartItem.vue
<template>
<div>
<h5>${{ carts.product.price }}</h5>
<div class="product_count">
<input
disabled
name="qty"
maxlength="12"
:value="carts.quantity"
class="input-text qty"
/>
<button
#click="addProduct()"
class="increase items-count"
type="button"
>
<i class="lnr lnr-chevron-up"></i>
</button>
<button
#click="removeProduct()"
class="reduced items-count"
type="button"
>
<i class="lnr lnr-chevron-down"></i>
</button>
</div>
</div>
</template>
<script>
export default {
name: "cartItem",
props: {
carts: {
required: true,
type: Object
}
},
data() {
return {
cartDetail: {
product: this.carts.product.id,
quantity: null,
customer: null,
checkout: false
}
};
},
computed: {
...mapGetters(["authUser"])
},
methods: {
...mapActions(["addTocart"]),
addProduct() {
this.cartDetail.quantity = 1;
this.cartDetail.customer = this.authUser.id;
this.addTocart(this.cartDetail);
},
removeProduct() {
this.cartDetail.quantity = -1;
this.cartDetail.customer = this.authUser.id;
this.addTocart(this.cartDetail)
}
}
};
</script>
Cart.js
const state = {
carts: []
};
const getters = {
carts: state => state.carts
};
const actions = {
async addTocart({ commit }, data) {
const JsonData = JSON.parse(JSON.stringify(data));
const response = await axios.post("/api/v1/cart/view-set/", JsonData);
return response;
},
async cart({ commit }, data) {
if (data !== "null") {
const response = await axios.get(`/api/v1/cart/cartdetial/${data}`);
commit("setCarts", response.data);
return response;
}
}
};
const mutations = {
setCarts: (state, carts) => {
state.carts = carts;
}
};
export default {
state,
getters,
actions,
mutations
};
TotalCartPrice should be a computed property:
TotalCartPrice: function() {
var total = 0;
for (var i = 0; i < this.carts.length; i++) {
total += this.carts[i].product.price * this.carts[i].quantity;
}
return total;
}
From the docs on the difference between computed and methods:
...computed properties are cached based on their reactive dependencies. A computed property will only re-evaluate when some of its reactive dependencies have changed. This means as long as message has not changed, multiple access to the reversedMessage computed property will immediately return the previously computed result without having to run the function again.
This way, your cart price will update whenever this.carts is updated.
Related
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®ion=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>
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/
I am using vue #keyup on input, this uses method with axios call:
<template>
<div>
<input type="text" placeholder="Where are you from?" v-model="from" #keyup="getPlace">
<ul>
<li v-for="place in places">{{ place.name }}<li>
</ul>
</div>
</template>
<script>
export default {
data() {
return {
places: [],
from: ''
}
},
methods() {
if(this.from.length <= 3) { this.places = []; return; }
getPlace() {
axios.get('/places?name='+this.from).then((res)=>{
this.places = [];
for(var i = 0; i<res.data.length; i++) {
this.places.push(res.data[i]);
}
}).catch((err)=>{console.log(err)});
}
}
};
</script>
Now this works but it has a big problem, each call it updates array of places but it is late, so the method is called and array has been returned to [] but after the response is back it is populating the array for each keyup (if you type fast)... I am switching from jquery front to this and I never ever had a problem with this :O
This doesn't answer the "how to abort method", but you could use a different approach for your example: Fetching all places at the beginning (mounted()) and filter them based on the input in the frontend by using a computed property.
var app = new Vue({
el: '#app',
data() {
return {
places: [],
from: ''
}
},
mounted() {
this.getPlaces();
},
computed: {
placesList() {
let result = []
let places = this.places
let from = this.from
if (from !== '') {
result = this.places.filter(element => {
return element.name.toLowerCase().includes(from.toLowerCase())
})
} else {
result = places
}
return result
}
},
methods: {
getPlaces() {
// axios call..
this.places = [{
id: 1,
name: "Germany"
}, {
id: 2,
name: "USA"
}, {
id: 3,
name: "Spain"
}]
}
}
})
<script src="https://cdn.jsdelivr.net/npm/vue#2.5.17/dist/vue.js"></script>
<div id="app">
<input type="text" placeholder="Where are you from?" v-model="from">
<br /> input text: {{ from }}
<hr>
<ul>
<li v-for="place in placesList" :key="place.id">
{{ place.name }}
</li>
</ul>
</div>
I have this 3 components in VueJS. The problem i want to solve is: When i click at vehicle component, it needs to be selected (selected = true) and other vehicles unselected.
What i need to do for two-way data binding? Because i'm changing this selected property in VehiclesList.vue component and it also need to be changed in Monit.vue (which is a parent) and 'Vehicle.vue' need to watch this property for change class.
Also problem is with updating vehicles. In Monit.vue i do not update full object like this.vehicles = response.vehicles, but i do each by each one, and changing only monit property.
Maybe easier would be use a store for this. But i want to do this in components.
EDITED:Data sctructure
{
"m":[
{
"id":"v19",
"regno":"ATECH DOBLO",
"dt":"2017-10-09 13:19:01",
"lon":17.96442604,
"lat":50.66988373,
"v":0,
"th":0,
"r":0,
"g":28,
"s":"3",
"pow":1
},
{
"id":"v20",
"regno":"ATECH DUCATO_2",
"dt":"2017-10-10 01:00:03",
"lon":17.96442604,
"lat":50.6698494,
"v":0,
"th":0,
"r":0,
"g":20,
"s":"3"
},
]
}
Monit.vue
<template>
<div class="module-container">
<div class="module-container-widgets">
<vehicles-list :vehicles="vehicles"></vehicles-list>
</div>
</div>
</template>
<script>
import VehiclesList from '#/components/modules/monit/VehiclesList.vue';
export default {
name: "Monit",
data (){
return {
vehicles: null
}
},
components: {
VehiclesList
},
methods: {
getMonitData(opt){
let self = this;
if (this.getMonitDataTimer) clearTimeout(this.getMonitDataTimer);
this.axios({
url:'/monit',
})
.then(res => {
let data = res.data;
console.log(data);
if (!data.err){
self.updateVehicles(data.m);
}
self.getMonitDataTimer = setTimeout(()=>{
self.getMonitData();
}, self.getMonitDataDelay);
})
.catch(error => {
})
},
updateVehicles(data){
let self = this;
if (!this.vehicles){
this.vehicles = {};
data.forEach((v,id) => {
self.vehicles[v.id] = {
monit: v,
no: Object.keys(self.vehicles).length + 1
}
});
} else {
data.forEach((v,id) => {
if (self.vehicles[v.id]) {
self.vehicles[v.id].monit = v;
} else {
self.vehicles[v.id] = {
monit: v,
no: Object.keys(self.vehicles).length + 1
}
}
});
}
},
},
mounted: function(){
this.getMonitData();
}
};
</script>
VehiclesList.vue
<template>
<div class="vehicles-list" :class="{'vehicles-list--short': isShort}">
<ul>
<vehicle
v-for="v in vehicles"
:key="v.id"
:data="v"
#click.native="select(v)"
></vehicle>
</ul>
</div>
</template>
<script>
import Vehicle from '#/components/modules/monit/VehiclesListItem.vue';
export default {
data: function(){
return {
isShort: true
}
},
props:{
vehicles: {}
},
methods:{
select(vehicle){
let id = vehicle.monit.id;
console.log("Select vehicle: " + id);
_.forEach((v, id) => {
v.selected = false;
});
this.vehicles[id].selected = true;
}
},
components:{
Vehicle
}
}
</script>
Vehicle.vue
<template>
<li class="vehicle" :id="data.id" :class="classes">
<div class="vehicle-info">
<div class="vehicle-info--regno font-weight-bold"><span class="vehicle-info--no">{{data.no}}.</span> {{ data.monit.regno }}</div>
</div>
<div class="vehicle-stats">
<div v-if="data.monit.v !== 'undefined'" class="vehicle-stat--speed" data-name="speed"><i class="mdi mdi-speedometer"></i>{{ data.monit.v }} km/h</div>
</div>
</li>
</template>
<script>
export default {
props:{
data: Object
},
computed:{
classes (){
return {
'vehicle--selected': this.data.selected
}
}
}
}
</script>
Two-way component data binding was deprecated in VueJS 2.0 for a more event-driven model: https://v2.vuejs.org/v2/guide/components.html#One-Way-Data-Flow
This means, that changes made in the parent are still propagated to the child component (one-way). Changes you make inside the child component need to be explicitly send back to the parent via custom events: https://v2.vuejs.org/v2/guide/components.html#Custom-Events or in 2.3.0+ the sync keyword: https://v2.vuejs.org/v2/guide/components.html#sync-Modifier
EDIT Alternative (maybe better) approach:
Monit.vue:
<template>
<div class="module-container">
<div class="module-container-widgets">
<vehicles-list :vehicles="vehicles" v-on:vehicleSelected="onVehicleSelected"></vehicles-list>
</div>
</div>
</template>
<script>
import VehiclesList from '#/components/modules/monit/VehiclesList.vue';
export default {
name: "Monit",
data (){
return {
vehicles: null
}
},
components: {
VehiclesList
},
methods: {
onVehicleSelected: function (id) {
_.forEach((v, id) => {
v.selected = false;
});
this.vehicles[id].selected = true;
}
...other methods
},
mounted: function(){
this.getMonitData();
}
};
</script>
VehicleList.vue:
methods:{
select(vehicle){
this.$emit('vehicleSelected', vehicle.monit.id)
}
},
Original post:
For your example this would probably mean that you need to emit changes inside the select method and you need to use some sort of mutable object inside the VehicleList.vue:
export default {
data: function(){
return {
isShort: true,
mutableVehicles: {}
}
},
props:{
vehicles: {}
},
methods:{
select(vehicle){
let id = vehicle.monit.id;
console.log("Select vehicle: " + id);
_.forEach((v, id) => {
v.selected = false;
});
this.mutableVehicles[id].selected = true;
this.$emit('update:vehicles', this.mutableVehicles);
},
vehilcesLoaded () {
// Call this function from the parent once the data was loaded from the api.
// This ensures that we don't overwrite the child data with data from the parent when something changes.
// But still have the up-to-date data from the api
this.mutableVehilces = this.vehicles
}
},
components:{
Vehicle
}
}
Monit.vue
<template>
<div class="module-container">
<div class="module-container-widgets">
<vehicles-list :vehicles.sync="vehicles"></vehicles-list>
</div>
</div>
</template>
<script>
You still should maybe think more about responsibilities. Shouldn't the VehicleList.vue component be responsible for loading and managing the vehicles? This probably would make thinks a bit easier.
EDIT 2:
Try to $set the inner object and see if this helps:
self.$set(self.vehicles, v.id, {
monit: v,
no: Object.keys(self.vehicles).length + 1,
selected: false
});
I'm learning Due and I'm trying something that should be easy but doesn't work and I'm sure there is something I don't understand.
I have to add a case to a modal...
I simplify the code: I have an array of products from my parent passed the a props and to chunk them in columns I use a computed variabile.
In the computed variable I also add to my object array an attribute active for every object, and I need to use that attribute to add the class.
I cannot change the value: when I click the button the product.active value is changed if I look the console but in my template no, it is false. Why
<template>
<div class="columns" v-for="products in processedProducts">
<div class="column" v-for="product in products">
<pre>{{product.active}}</pre>
<a v-on:click="activeteModal(product)">Pricy History</a>
<price-history :asin="product.asin" :active="product.active"></price-history>
</div>
</div>
</template>
<script>
import PriceHistory from '../components/PriceHistory'
export default {
props: ['results','search','maxprice','discount'],
name: 'product',
components: {
PriceHistory
},
methods: {
activeteModal: function(product){
console.log(product.active);
product.active = !product.active;
console.log(product.active);
}
},
computed: {
processedProducts() {
let products = this.results.map((obj) => {
obj.active = false;
return obj;
})
// Put Array into Chunks
let i, j, chunkedArray = [], chunk = 5;
for (i=0, j=0; i < products.length; i += chunk, j++) {
chunkedArray[j] = products.slice(i,i+chunk);
}
return chunkedArray;
}
}
}
</script>
Computed objects update lazy, set the array as a data property then it will update reactive.
Furthermore computed-objects are by default getter-only.
You better trigger a method that fills the product array when the component is mounted like this:
export default {
props: ['results','search','maxprice','discount'],
name: 'product',
components: {
PriceHistory
},
data: function () {
return {
products :[]
}
},
mounted: function(){
this.processedProducts();
},
methods: {
activeteModal: function(product){
console.log(product.active);
product.active = !product.active;
console.log(product.active);
},
processedProducts() {
let products = this.results.map((obj) => {
obj.active = false;
return obj;
})
// Put Array into Chunks
let i, j, chunk = 5;
for (i=0, j=0; i < products.length; i += chunk, j++) {
this.products[j] = products.slice(i,i+chunk);
}
}
}
}