Vue.Js / Vuex - how to declare id of elements - javascript

This is a simple shopping Cart
Actually, when we clic on items, it updated the total number in the Cart.
But i want now to make a better Cart: showing each items in the list. Like when we chose 2 croissiants, it adds 2 croissiants in the Cart.
My problem is, in this course, i didn't really learn how to make a Mutation dependings of the ID of the item. I'm looking for the syntax witch is sending the ID of the item where we clicked, to the store.
Here my my 3 files : (Store's "index", "Home" and it's children "MenuItem")
Store :
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
restaurantName: 'Cafe with A Vue',
shoppingCart: 0,
croissiantNumber: 0,
baguetteNumber: 0,
eclairNumber: 0,
simpleMenu: [
{
id: 1,
name: 'Crossiant',
image: {
source: '/images/crossiant.jp',
alt: 'A crossiant'
},
inStock: true,
quantity: 1,
price: 2.99
},
{
id: 2,
name: 'French Baguette',
image: {
source: '/images/french-baguette.jpe',
alt: 'Four French Baguettes'
},
inStock: true,
quantity: 1,
price: 3.99
},
{
id: 3,
name: 'Éclair',
image: {
source: '/images/eclair.jp',
alt: 'Chocolate Éclair'
},
inStock: false,
quantity: 1,
price: 4.99
}
]
},
getters: {
copyright: state => {
const currentYear = new Date().getFullYear()
return `Copyright ${state.restaurantName} ${currentYear}`
}
},
mutations: {
ADD_ITEMS_TO_SHOPPING_CART(state, amount) {
state.shoppingCart += amount
}
},
actions: {
updateShoppingCart({ commit }, amount) {
commit('ADD_ITEMS_TO_SHOPPING_CART', amount),
commit('ADD_ITEM_TO_SHOPPING_CART', amount)
}
},
modules: {}
})
Home :
<template>
<div>
<h1>{{ restaurantName }}</h1>
<p class="description">
Welcome to {{ restaurantName }}! We are known for our freshly baked bread
and french pastries! Give you morning a warm start or treat yourself in
the middle of the day. Our butter is imported from local farmers in
France. Once you take your first bite, you will see why everyone can't get
enough!
</p>
<section class="menu">
<h2>Menu</h2>
<MenuItem
v-for="item in simpleMenu"
:name="item.name"
:image="item.image"
:price="item.price"
:quantity="item.quantity"
:inStock="item.inStock"
:key="item.name"
:id="item.id"
/>
</section>
<div class="shopping-cart">
<h2>Shopping Cart: {{ shoppingCart }} items</h2>
<h2>Croissiant: {{ croissiantNumber }} items</h2>
<h2 v-if="baguetteNumber">French Baguette: {{ baguetteNumber }} items</h2>
<h2 v-if="eclairNumber">Eclair: {{ eclairNumber }}items</h2>
</div>
<footer class="footer">
<p>{{ copyright }}</p>
</footer>
</div>
</template>
<script>
import MenuItem from '../components/MenuItem'
import { mapGetters, mapState } from 'vuex'
export default {
name: 'Home',
components: {
MenuItem
},
computed: {
...mapGetters({
copyright: 'copyright'
}),
...mapState({
restaurantName: 'restaurantName',
shoppingCart: 'shoppingCart',
croissiantNumber: 'croissiantNumber',
baguetteNumber: 'baguetteNumber',
eclairNumber: 'eclairNumber',
simpleMenu: 'simpleMenu'
})
}
}
</script>
MenuItem :
<script>
import { mapActions } from 'vuex'
import BaseButton from './BaseButton.vue'
export default {
name: 'MenuItem',
components: {
BaseButton
},
props: {
image: {
type: Object,
required: true
},
inStock: {
type: Boolean,
required: true
},
name: {
type: String,
required: true
},
price: {
type: Number,
required: true
},
quantity: {
type: Number,
defaut: 1
}
},
data() {
return {
onSale: false
}
},
computed: {
generatedPrice() {
if (this.onSale) {
return (this.price * 0.9).toFixed(2)
} else {
return this.price
}
}
},
methods: {
...mapActions(['updateShoppingCart'])
},
beforeMount() {
const today = new Date().getDate()
if (today % 2 === 0) {
this.onSale = true
}
}
}
</script>
<template>
<div class="menu-item">
<img class="menu-item__image" :src="image.source" :alt="image.alt" />
<div>
<h3>{{ name }}</h3>
<p>Price: {{ generatedPrice }} <span v-if="onSale">(10% off!)</span></p>
<p v-if="inStock">In Stock</p>
<p v-else>Out of Stock</p>
<div>
<label for="add-item-quantity">Quantity: {{ quantity }}</label>
<input v-model.number="quantity" id="add-item-quantity" type="number" />
<BaseButton #click="updateShoppingCart(quantity, id)" class="test">
Add to shopping cart
</BaseButton>
</div>
</div>
</div>
</template>
So i want to update this value for example :
Croissiant: 0 items
Website preview
Thanks.

You have to pass either object or array to your action to handle multiple parameters.
#click="updateShoppingCart({quantity, id})"
and then in store:
...
actions: {
updateShoppingCart({ commit }, {quantity, id}) {
commit('ADD_ITEMS_TO_SHOPPING_CART', amount),
//here you have id that you can map to item type
}
},
I would suggest having item type that is constant in simpleMenu and not depending on IDs.

Related

require method images not rendered vue js

**The require method images are not rendered with Vuejs. Even though I've add the vue-loader dependency, and the props are delivered correctly. The template is simple - just an array in App component, a Parent and a Child component.
Can anyone help ?
https://codesandbox.io/s/eloquent-grass-j1usw8?file=/src/components/v-carousel-item.vue**
// App
<template>
<v-carousel :carousel_data="sliderItems" />
</template>
<script>
import vCarousel from "./components/v-carousel.vue";
export default {
name: "App",
data() {
return {
sliderItems: [
{ id: 1, name: "img1", img: "1.jpg" },
{ id: 2, name: "img2", img: "2.jpg" },
{ id: 3, name: "img3", img: "3.jpg" },
],
};
},
components: {
vCarousel,
},
};
</script>
//Parent
<template>
<div class="v-carousel"></div>
<v-carousel-item
v-for="item in carousel_data"
:key="item.id"
:item_data="item"
/>
</template>
<script>
import vCarouselItem from "./v-carousel-item.vue";
export default {
components: {
vCarouselItem,
},
props: {
carousel_data: {
type: Array,
default: () => [],
},
},
};
</script>
// Child
<template>
<div class="v-carousel-item">
<img src="require('../assets/images' + item_data.img)" alt="" />
</div>
</template>
<script>
export default {
props: {
item_data: {
type: Object,
default: () => {},
},
},
};
</script>

How to highlight active menu item with Typescript and Vue.js 3?

I have a menu and would like to highlight the currently active menu item.
I think I should write in the first template the following: v-if(selected) and some function maybe.
<template>
<MenuTreeView #selected='collapsedMenuSelected' :items='filteredFirstLevelNavigation' :filter="false"/>
</template>
MenuTreeView:
<template>
<Tree :value="items" filterPlaceholder="Suche" :expandedKeys="expandedKeys" filterMode="lenient" :filter="filter"
selectionMode="single" #nodeSelect="onNodeSelect"></Tree>
</template>
In such cases, you need to store the id of the active element in the data of the parent component. This can be done by saving the id when selecting one of the rows.
Parent
<template>
<div class="menu">
<MenuItem
v-for="row in rows"
:active-id="activeId"
:key="row.id"
#select="handleSelect"
/>
</div>
</template>
<script>
import MenuItem from "./MenuItem";
export default {
name: "TheMenu",
components: {
MenuItem,
},
data() {
return {
activeId: null,
rows: [
{
id: 1,
label: "First",
},
{
id: 2,
label: "Second",
},
{
id: 3,
label: "Third",
},
],
};
},
methods: {
handleSelect(id) {
this.activeId = id;
},
},
};
</script>
Child
<template>
<div
class="menu-item"
:class="{
'menu-item_active': activeId === row.id,
}"
#click="handleSelect"
>
{{ row.label }}
</div>
</template>
<script>
export default {
name: "MenuItem",
props: {
row: {
type: Object,
required: true,
},
activeId: {
type: Number,
required: true,
},
},
methods: {
handleSelect() {
this.$emit("select", this.row.id);
},
},
};
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
.menu-item {
/* your styles */
display: flex;
width: 100%;
}
.menu-item_active {
background-color: red;
}
</style>

How to pass data from component file to view file in vue js

I'm trying to pass data from vue component to vue view file. I tried creating props but it didn't work. I have one component file on path src/components/list.vue and another file on path src/views/select.vue
The flow goes like this: User lands on select.vue page, here on click of input box a pop-up appears which have have list of elements in <ul> <li> tag from list.vue (component file).
What I want to achieve:
Whenever user select any option from list.vue file, modal pop-up should close and selected element should be displayed in input box of select.vue file.
Below is code:
src/views/select.vue
<label class="primary-label mb-2">First Question</label>
<div class="form-block">
<label class="secondary-label mb-1">Question</label>
<b-form-input placeholder="Select Question" v-model="questions.first" class="form-control l-input" #click="onOptionChanged"></b-form-input>
</div>
<div class="form-block">
<label class="secondary-label mb-1">Answer</label>
<b-form-input v-model="answers.first" placeholder="Enter answer" class="form-control l-input"></b-form-input>
</div>
<script>
export default {
data() {
return {
questions: {
first: null,
},
answers: {
first: null,
},
options: [
{ value: null, text: 'Select Question', disabled:true },
{ value: 1, text: 'In what city were you born?' },
{ value: 2, text: 'What is the name of your favorite pet?' },
{ value: 3, text: 'What is your mother\'s maiden name?' },
{ value: 4, text: 'What high school did you attend?' },
{ value: 5, text: 'What is the name of your first school?' },
{ value: 6, text: 'What was the make of your first car?' },
{ value: 7, text: 'What was your favorite food as a child?' },
{ value: 8, text: 'Where did you meet your spouse?' },
],
}
},
methods: {
onOptionChanged() {
var modal_ref = 'myModalRef';
this.$refs[modal_ref].show();
},
},
components: {
SecurityQuestionsList,
},
}
src/components/list.vue
<template>
<main>
<div class="container">
<div class="row">
<div class="col-md-6">
<div class="search-block">
<span class="s-icon fa fa-search"></span>
</div>
<ul class="l-group" v-if="filteredQuestions.length > 0">
<li class="d-flex align-items-center" :key="item.id" v-for="item in filteredQuestions" #click="onOptionSelect(item.question)"
:class="selected === item.id ? 'my-selected-item-class':null">
{{item.question}}
</li>
</ul>
</div>
</div>
</div>
</main>
</template>
<script>
export default {
data() {
return {
search: '',
selected: null,
questions: [
{ id: 1, question: 'In what city were you born?' },
{ id: 2, question: 'What is the name of your favorite pet?' },
{ id: 3, question: 'What is your mother\'s maiden name?' },
{ id: 4, question: 'What high school did you attend?' },
{ id: 5, question: 'What is the name of your first school?' },
{ id: 6, question: 'What was the make of your first car?' },
{ id: 7, question: 'What was your favorite food as a child?' },
{ id: 8, question: 'Where did you meet your spouse?' },
],
}
},
computed: {
filteredQuestions() {
return this.questions.filter(item => {
return item.question.toLowerCase().includes(this.search.toLowerCase());
});
}
},
methods: {
onOptionSelect(selectedId) {
this.selected = selectedId;
console.log(this.selected);
this.$emit('questions-selected', this.selected);
},
}
}
</script>
I am getting selected value in console but not sure how to catch it in search.vue file.
Thanks!
You need to connect your components and pass props, try like following snippet:
Vue.component('list', {
template: `
<main>
<div class="container">
<div class="row">
<div class="col-md-6">
<div class="search-block">
<span class="s-icon fa fa-search"></span>
</div>
<ul class="l-group" v-if="filteredQuestions.length > 0">
<li class="d-flex align-items-center" :key="item.id" v-for="item in filteredQuestions" #click="onOptionSelect(item.question)"
:class="selected === item.id ? 'my-selected-item-class':null">
{{item.question}}
</li>
</ul>
</div>
</div>
</div>
</main>
`,
props: ['search'],
data() {
return {
selected: null,
questions: [
{ id: 1, question: 'In what city were you born?' },
{ id: 2, question: 'What is the name of your favorite pet?' },
{ id: 3, question: 'What is your mother\'s maiden name?' },
{ id: 4, question: 'What high school did you attend?' },
{ id: 5, question: 'What is the name of your first school?' },
{ id: 6, question: 'What was the make of your first car?' },
{ id: 7, question: 'What was your favorite food as a child?' },
{ id: 8, question: 'Where did you meet your spouse?' },
],
}
},
computed: {
filteredQuestions() {
if (this.search) {
return this.questions.filter(item => {
return item.question.toLowerCase().includes(this.search.toLowerCase());
});
} return this.questions
}
},
methods: {
onOptionSelect(selectedId) {
this.selected = selectedId;
this.$emit('questions-selected', selectedId);
},
}
})
new Vue({
el: "#demo",
name: 'select',
data() {
return {
questions: {
first: null,
},
answers: {
first: null,
},
selected: false
}
},
methods: {
onOptionChanged() {
this.selected = true
},
onSelected(val) {
this.questions.first = val
this.selected = false
},
},
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<link type="text/css" rel="stylesheet" href="https://unpkg.com/bootstrap/dist/css/bootstrap.min.css" />
<link type="text/css" rel="stylesheet" href="https://unpkg.com/bootstrap-vue#latest/dist/bootstrap-vue.min.css" />
<script src="https://unpkg.com/bootstrap-vue#latest/dist/bootstrap-vue.min.js"></script>
<script src="https://unpkg.com/bootstrap-vue#latest/dist/bootstrap-vue-icons.min.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.1.1/css/all.min.css" integrity="sha512-KfkfwYDsLkIlwQp6LFnl8zNdLGxu9YAA1QvwINks4PhcElQSvqcyVLLD9aMhXd13uQjoXtEKNosOWaZqXgel0g==" crossorigin="anonymous" referrerpolicy="no-referrer" />
<div id="demo">
<div>
<label class="primary-label mb-2">First Question</label>
<div class="form-block">
<label class="secondary-label mb-1">Question</label>
<b-form-input placeholder="Select Question" v-model="questions.first" class="form-control l-input" #click="onOptionChanged()"></b-form-input>
</div>
<div class="form-block">
<label class="secondary-label mb-1">Answer</label>
<b-form-input v-model="answers.first" placeholder="Enter answer" class="form-control l-input"></b-form-input>
<list :search="questions.first" #questions-selected="onSelected" v-if="selected"></list>
</div>
</div>
</div>

Vue.js (vuex). Computed property returns undefined when reloading the page. (Hard coded array of objects data in vuex)

I am building a shoe shop with Vue and the problem is that I am getting undefined when reloading the page when I am trying to access a getter through computed property on the ShoeDetail component.I am storing some hard coded array of objects data in the vuex state. In the Shoe component I am looping through the data with v-for and outputting in the Men view, that is working for me, all avaliable shoes are displayed. When a user clicks on the shoe image a new route is loaded for each shoe with further details for that shoe which got clicked. I am using the id for dynamic routing. Now i have a getter in vuex
getProductById: (state) => (id) => {
return state.sneakers.find((sneaker) => sneaker.productId === id);
},
then I access it through a computed property:
product() {
return this.$store.getters.getProductById(this.$route.params.id);
},
that works fine and I can output the data stored in vuex state by interpolitaion like:
{{product.brand}}
When I reload the page I get this error:
**[Vue warn]: Error in render: "TypeError: Cannot read property 'brand' of undefined"
found in
---> <Detail> at src/components/ShoeDetail.vue
<App> at src/App.vue
<Root>**
I have tried to use v-if="product" but since im not making an API call this shouldn't be relevant.
I have also installed the createPersistedState but it still doesn't work. I am really stuck here and a hint on why im getting undefined on page reload would be appreciated
My vuex:
import Vue from 'vue';
import Vuex from 'vuex';
import createPersistedState from 'vuex-persistedstate';
Vue.use(Vuex);
export default new Vuex.Store({
plugins: [
createPersistedState({
key: 'keyname',
storage: window.localStorage,
}),
],
state: {
sneakers: [
{
productId: 1,
productno: 1234,
brand: 'nike',
gender: 'men',
size: 5,
price: 150,
image: require('#/assets/images/nike-air-zoom.jpg'),
},
{
productId: 2,
productno: 1235,
brand: 'adidas',
gender: 'men',
size: 10,
price: 140,
image: require('#/assets/images/sneakers.jpg'),
},
{
productId: 3,
productno: 1236,
brand: 'adidas',
gender: 'men',
size: 10,
price: 130,
image: require('#/assets/images/man-holding-nikes.jpg'),
},
{
productId: 4,
productno: 1237,
brand: 'nike',
gender: 'men',
size: 5,
price: 120,
image: require('#/assets/images/nike-air-zoom.jpg'),
},
{
productId: 5,
productno: 1238,
brand: 'adidas',
gender: 'men',
size: 10,
price: 110,
image: require('#/assets/images/sneakers.jpg'),
},
{
productId: 6,
productno: 1239,
brand: 'adidas',
size: 10,
price: 100,
image: require('#/assets/images/man-holding-nikes.jpg'),
},
],
},
getters: {
getProducts: (state) => {
return state.sneakers;
},
getProductById: (state) => (id) => {
return state.sneakers.find((sneaker) => sneaker.productId === id);
},
},
});
The Shoe component:
<template>
<div class="shoe-container">
<div class="shoe-card" v-for="element in products" :key="element.id">
<router-link
:to="{ name: 'ShoeDetail', params: { id: element.productId } }"
><img :src="element.image" tag="img" alt="" class="shoe-card__image"
/></router-link>
<p class="shoe-card__model">{{ element.brand }}</p>
<p class="shoe-card__price">{{ element.price }} $</p>
</div>
</div>
</template>
<script>
export default {
computed: {
products() {
return this.$store.getters.getProducts;
},
product() {
return this.$store.getters.getProductById(this.$route.params.id);
},
},
};
</script>
<style lang="scss" scoped>
#import '../sass/components/shoe';
</style>
The men view:
<template>
<div>
<navbar></navbar>
<sub-nav></sub-nav>
<filter-section></filter-section>
<section class="shoes">
<shoe></shoe>
</section>
</div>
</template>
<script>
import Navbar from "../components/Navbar.vue";
import SubNav from "../components/SubNav.vue";
import FilterSection from "../components/FilterSection.vue";
import Shoe from "../components/Shoe.vue";
export default {
components: {
Navbar,
SubNav,
FilterSection,
Shoe
}
};
</script>
<style lang="scss" scoped>
#import "../sass/views/men";
</style>
The ShoeDetail component:
<template>
<div class="details">
<h1>This is details</h1>
<h2>{{ product.brand }}</h2>
</div>
</template>
<script>
export default {
name: 'detail',
computed: {
product() {
return this.$store.getters.getProductById(this.$route.params.id);
},
},
};
</script>
<style lang="scss" scoped>
#import '../sass/components/shoeDetail';
</style>
Thanks for the answer I was able to fix it by adding parseInt in the computed property of ShoeDetail.
computed: {
product() {
return this.$store.getters.getProductById(
parseInt(this.$route.params.id)
);
},
}
Do this in the ShoeDetail component:
data: () => {
data: '',
},
methods: {
getProduct () {
this.product = this.$store.getters.getProductById(this.$route.params.id);
},
},
mounted () {
this.getProduct();
}
and it should work :)

Vue key not working

I've a page with 4 times the same vue component.
In HMTL:
<div class="column">
<my-comp url="http://example.com/?q=today" :unique-id="1" text="Random text"
locale="en"
statistics-type="count_today"
progress-color="#DA4169"></my-comp>
</div>
<div class="column">
<my-comp url="http://example.com/?q=yesterday" :unique-id="2" text="Random text"
locale="en"
statistics-type="count_yesterday"
progress-color="#DA4169"></my-comp>
</div>
In my vue component data is fetched from the url and displayed in the component.
<template>
<div :key="uniqueId">
<!-- more HTML -->
</div>
</template>
I'm using the key attribute to prevent the components from re-using.
Unfortunately the data from the first component always returns to the default values, when the second component loads its data.
Example after loading all the data:
First my-comp:
amount: 0 <- default
capacity: 0 <- default
Second my-comp:
amount: 9
capacity: 15
EDIT
<template>
<div :key="uniqueId" class="tile">
<p class="tile-title">{{ amount }}</p>
<p class="tile-text text-muted">{{ text }}</p>
<div class="progress" v-if="showProgress">
<div class="progress-bar" role="progressbar"
:aria-valuenow="progress"
aria-valuemax="100"
aria-valuemin="0"
v-bind:style="{ backgroundColor: progressColor, width: progress+'%' }">
</div>
</div>
</div>
</template>
<script>
export default {
props:{
locale: {
type: String,
required: true,
},
text: {
type: String,
required: true
},
progressColor: {
type: String,
required: true
},
url: {
type: String,
required: true
},
statisticsType: {
type: String,
required: true
},
uniqueId:{
type: Number,
required: true
}
},
data: function() {
return {
amount: 0,
total: 0,
showProgress: true,
}
},
computed: {
progress: function(){
return (this.amount/this.total)*100;
}
},
created() {
this.requestStatistics();
},
methods: {
requestStatistics: function(){
self = this;
axios.get(self.url,{
params: {
what: self.statisticsType
}
}).then(function(response){
self.showData(response.data);
}).catch(function(error){
console.log(error);
});
},
showData: function(data) {
if(typeof(data) === 'number'){
this.amount = data;
this.showProgress = false;
}else{
this.amount = data.count;
this.total = data.capacity;
this.showProgress = true;
}
}
}
}
</script>
You need to use v-bind instead of simple html attribute so that :key binding would be respected:
:uniqueId="1"

Categories

Resources