How to pass props to input value in Vuejs - vue.js

I have a parent component as a Cart. Here I defined quantity and I want to pass this quantity to the child component's input value which is Counter. So here how I am passing it and here is my parent component, Cart:
<Counter quantity="item.quantity"/>
And here is my child component, Counter:
<template>
<div id="counter">
<button class="minus" #click="countDown"><i :class="quantity == 0 ? 'disabled' : ''" class="fas fa-minus"></i></button>
<div class="count-number"><input class="counter-content" type="number" v-model="quantity"></div>
<button class="plus" #click="countUp"><i class="fas fa-plus"></i></button>
</div>
</template>
<script>
export default {
props: {
quantity: Number
},
methods: {
countUp() {
this.quantity++;
},
countDown() {
if(this.quantity > 0) {
this.quantity--;
}
},
}
}
</script>
I am quite new in Vue, so maybe I am doing something wrong when I pass the props. So could you help me about this?

Try (with the : colon sign)
<Counter :quantity="item.quantity"/>
Before you were just passing the string "item.quanity"
I see you're modifying your prop directly:
countUp() {
this.quantity++;
},
countDown() {
if(this.quantity > 0) {
this.quantity--;
}
},
This is not how you do it in Vue. You need to use two way binding.
countUp() {
this.$emit('input', this.quantity+1)
}
countDown() {
this.$emit('input', this.quantity-1)
}
and in your parent component:
<Counter :quantity="item.quantity" #input="(payload) => {item.quantity = payload}"/>
By the way, the Vue styleguide recommends to use multi-word component names: https://v2.vuejs.org/v2/style-guide/#Multi-word-component-names-essential (Cart = bad, MyCart = good)

We cannot change the value that we get from props, so I created a variable and put props there when mounting
Try it
<Counter :quantity="item.quantity"/>
and
<template>
<div id="counter">
<button class="minus" #click="countDown"><i :class="sum == 0 ? 'disabled' : ''" class="fas fa-minus"></i></button>
<div class="count-number"><input class="counter-content" type="number" v-model="sum"></div>
<button class="plus" #click="countUp"><i class="fas fa-plus"></i></button>
</div>
</template>
<script>
export default {
props: {
quantity: Number
},
data: () => ({
sum: 0
}),
mounted() {
this.sum = this.quantity;
},
methods: {
countUp() {
this.sum++;
},
countDown() {
if(this.sum > 0) {
this.sum--;
}
},
}
}
</script>

Related

v-on does not working why not receive $emit in custom component

I'm create a an component which represents my money field.
My target is on add element in list, set zero on money field to add next element in list...
But, my problem is that not working when send using $emit event to clear input to improve usability.
$emit works as described on image bellow
My money field:
<template>
<div class="input-group" #clear="clearInputField()">
<span>{{ title }}</span>
<input ref="displayMoney" type="text" v-model="displayMoney" #focus="isActive = true" #blur="isActive = false" />
</div>
</template>
<script>
export default {
props: {
title: String,
},
data() {
return {
money: 0,
isActive: false,
};
},
methods: {
clearInputField() {
console.log("Its work event");
this.money = 0;
this.displayMoney = "";
},
},
computed: {
displayMoney: {
get: function () {
if (this.isActive) {
return this.money;
} else {
return this.money.toLocaleString("pt-br", { style: "currency", currency: "BRL" });
}
},
set: function (modifiedMoney) {
let newMoney = parseFloat(modifiedMoney.replace(/[^\d.]/g, "."));
if (isNaN(newMoney) || newMoney.length == 0) {
newMoney = 0;
}
this.$emit("input", newMoney);
return (this.money = parseFloat(newMoney));
},
},
},
};
</script>
My principal component
<template>
<div class="wish-list">
<div class="row">
<div class="input-group">
<span>Digite sua meta: </span>
<input ref="descriptionWish" type="text" v-model="descriptionWish" />
</div>
<MoneyField title="Valor (R$): " v-model="valueWish" #keyup.native.enter="addWish" />
<button id="btnCalculate" #click="addWish()">Adicionar a lista de desejos</button>
</div>
<div class="list-items">
<ul>
<li v-for="wish in wishes" :key="wish">{{ wish }}</li>
</ul>
</div>
</div>
</template>
<script>
import MoneyField from "./Fields/MoneyField";
export default {
components: {
MoneyField,
},
data() {
return {
wishes: [],
valueWish: 0,
descriptionWish: "",
};
},
methods: {
addWish() {
if (!isNaN(this.valueWish) && this.valueWish > 0 && this.descriptionWish.length > 0) {
this.wishes.push(
`${this.descriptionWish} => ${this.valueWish.toLocaleString("pt-BR", { currency: "BRl", style: "currency" })}`
);
this.descriptionWish = "";
console.log("addWish");
this.valueWish = 0;
this.$emit("clear");
this.$refs.descriptionWish.focus();
}
this.valueWish = 0;
},
},
};
</script>
I still don't understand much about vueJS, but I believe it's something related to parent and child elements, but I've done numerous and I can't get my answer.
sorry for my bad english .
The emit sends an event from the child to the parent component not as you've done, to run a method from the child component you could add a ref in the component inside the parent one like :
<MoneyField title="Valor (R$): "
ref="moneyField" v-model="valueWish" #keyup.native.enter="addWish" />
then run this.$refs.moneyField.clearInputField() instead this.$emit("clear")

Vue 3 : issue in getting onchange event from child component

I made a component to provide a select including a button that reset the select to the initial state (no option selected).
I get well the onchange event in parent when an option is selected but nothing when the reset button is clicked, although the select is reset. In my use case the list is still filtered even when nothing is selected
Here is the parent file :
<template>
<liste-filter
:nom="'marque'"
:label="'Choose a make'"
:liste="marques"
v-model="selMarque"
#change="changefilter"></liste-filter>
<h1/>
<div v-for="vh in vehicules" :key="vh.lib" class="w-full">
{{ vh.lib }}
</div>
<div v-if="vehicules.length === 0">The list is empty</div>
</template>
<script>
import ListeFilter from "./ListeFilter.vue"
export default {
components: {
ListeFilter
},
data() {
return {
marques:[{id:"Renault",libelle:"Renault"},{id:"Peugeot",libelle:"Peugeot"}],
selMarque: "",
vehicules: [],
tabData:[{"lib":"Renault","modeles":[{"lib":"Clio"},{"lib":"Captur"}]},{"lib":"Peugeot","modeles":[{"lib":"208"},{"lib":"308"},{"lib":"3008"}]}]
};
},
methods: {
changefilter() {
this.vehicules = [];
for (var i = 0; i < this.tabData.length; i++) {
if(this.selMarque != "" && this.tabData[i].lib == this.selMarque) {
this.vehicules = this.tabData[i].modeles;
}
}
}
}
}
</script>
And here is the component file ListeFilter.vue :
<template>
<span class="relative">
<select
:name="nom"
:id="nom"
:label="label"
v-model="sel"
class="w-64 text-sm"
>
<option disabled value="">{{ label }}</option>
<option v-for="elem in liste" :value="elem.id" :key="elem.id">
{{ elem.libelle }}
</option>
</select>
<button
v-show="sel"
#click="reset"
>X</button>
</span>
</template>
<script>
export default {
props: ["nom", "label", "liste", "modelValue"],
emits: ["update:modelValue"],
computed: {
sel: {
get() {
return this.modelValue;
},
set(value) {
this.$emit("update:modelValue", value);
},
},
},
methods: {
reset() {
this.sel = ""
},
},
};
</script>
I also made a Vue SFC Playground to test HERE
Thanks in advance for your help.
PS : if you know a component that does the same I take it ! (vue-select does not seem Vue 3 compliant)
I think you should be listening for the #update:modelValue="changefilter" event instead of the change event #change="changefilter".
It's documented here. https://v3-migration.vuejs.org/breaking-changes/v-model.html#overview
Try changing your App.vue to this.
Here is a Playground
<template>
<liste-filter
:nom="'marque'"
:label="'Choose a mark'"
:liste="marques"
v-model="selMarque"
#update:modelValue="changefilter"
>
</liste-filter>
<h1/>
<div v-for="vh in vehicules" :key="vh.lib" class="w-full">
{{ vh.lib }}
</div>
<div v-if="vehicules.length === 0">The list is empty</div>
</template>
<script>
import ListeFilter from "./ListeFilter.vue"
export default {
components: {
ListeFilter
},
data() {
return {
marques:[{id:"Renault",libelle:"Renault"},{id:"Peugeot",libelle:"Peugeot"}],
selMarque: "",
vehicules: [],
tabData:[{"lib":"Renault","modeles":[{"lib":"Clio"},{"lib":"Captur"}]},{"lib":"Peugeot","modeles":[{"lib":"208"},{"lib":"308"},{"lib":"3008"}]}]
};
},
methods: {
changefilter() {
console.log('change happened');
this.vehicules = [];
for (var i = 0; i < this.tabData.length; i++) {
if(this.selMarque != "" && this.tabData[i].lib == this.selMarque) {
this.vehicules = this.tabData[i].modeles;
}
}
}
}
}
</script>

VueJS Render Dynamic Component with route query

I am trying to have render different components based on the route query produced by clicking tabs. I have tried a dynamic component approach but it is working only on initial load. When I switch by clicking tabs which changes the route query it does not change to the other component. Below are my code and files
Show.vue
<div>
<b-container fluid class="bg-white">
<b-row class="topTab types" v-if="$refs.chart">
<b-col
:class="{ active: currentTab === index }"
v-for="(tabDisplay, index) in $refs.chart.tabDisplays"
:key="index"
>
<router-link
:to="{ query: { period: $route.query.period, tab: index } }"
>
{{ tabDisplay }}
</router-link>
</b-col>
</b-row>
</b-container>
<component v-bind:is="currentExample" ref="chart" />
</div>
export default {
props: {
group: String,
cp: String,
name: String,
id: [Number, String]
},
computed: {
currentExample() {
return () =>
import(
`#/components/Trend/example/Charts/${this.group}/${this.id}/Base.vue`
);
},
}
}
Below is the component used above snippet
Base.vue
<template>
<component v-bind:is="currentData"> </component>
</template>
<script>
export default {
data: function() {
return {
tabDisplays: {
1: "example1",
2: "example2",
3: "example3",
4: "example4",
5: "example5",
6: "example6"
}
};
},
computed: {
currentData() {
return () =>
import(
`#/components/Trend/example/Charts/${this.$route.params.group}/${
this.$route.params.id
}/Tab${this.$route.query.tab === "6" ? "6" : "1-5"}.vue`
);
}
}
};
</script>
I am expecting that if I click the example6 link. It will show my #/components/Trend/example/Charts/${this.group}/${this.id}/Tab6.vue on the <component>
Dynamic components :is expects and identifier not a fully fledged component.
You should import the component under the components property in your parent component and then use the computed property to only get the identifier.
components: {
'comp{group}{id}{tab}': () => import(
`#/components/Trend/example/Charts/{group}/{id}/Tab{tab}.vue`)
}
Note: {group}, {id} and {tab} are only placeholders for your actual values. You need to import all of them.
And use the currentData to only get the identifier:
computed: {
currentData() {
return `comp${this.$route.params.group}${this.$route.params.id}{this.$route.query.tab === "6" ? "6" : "1-5"}`
}
}
I have fixed it by just being a simple.
Base.vue
<template>
<component v-bind:is="currentData"> </component>
</template>
<script>
import Tab from "#/components/Trend/example/Charts/abc/1/Tab1-5.vue";
import Tab6 from "#/components/Trend/example/Charts/abc/1/Tab6.vue";
export default {
computed: {
currentData() {
if (this.$route.query.tab === "6") {
return Tab6;
} else {
return Tab;
}
}
}
};
</script>
My structure allows me to be simple and straightforward on this level

How to pass and change index of array in vue?

I have the gist of how to do this, but I'm a beginner in vue, and I'm struggling with how to put it together. I need Control.vue to update the index in Exhibitor.vue. I know I'll have an $emit event happening in Control when I click on the button to pass the index data to the parent, and I'd have to use props to pass data from Exhibitor to its children, but how? I can't understand how to pass the index of an array with my code.
Exhibitor.vue
<template>
<div id="exhibitor">
<section class="exhibitor_info">
<h1 class="exhibitor_name">{{ exhibitors[index].firstName }} {{ exhibitors[index].lastName }}</h1>
<h2>Tag Number: {{ exhibitors[index].tagNum }} <em>{{ exhibitors[index].species }}</em></h2>
</section>
<div class="frame"><img :src="getImgUrl(exhibitors[index].picture)" alt="Exhibitor-Picture" class="image"></div>
</div>
</template>
<script>
export default {
name: 'Exhibitor',
data() {
return {
exhibitors: [],
index: 0
}
},
created: function() {
this.fetchExhibitors();
},
methods: {
fetchExhibitors() {
let uri = 'http://localhost:8081/exhibitor'
this.axios.get(uri).then(response => {
this.exhibitors = response.data
})
},
getImgUrl: function(pic) {
return require('../assets/' + pic)
}
}
}
</script>
Display.vue
<template>
<div id="display">
<exhibitor></exhibitor>
<buyer></buyer>
</div>
</template>
<script>
import Exhibitor from './Exhibitor.vue';
import Buyer from './Buyer.vue';
export default {
components: {
'exhibitor': Exhibitor,
'buyer': Buyer
}
}
</script>
Control.vue
<template>
<div id="control">
<display></display>
<button v-on:click="incrementLeft">Left</button>
<button v-on:click="incrementRight">Right</button>
</div>
</template>
<script>
import Exhibitor from './Exhibitor.vue';
import Display from './Display.vue';
export default{
props: ['exhibitors', 'buyers', 'index'],
data() {
return {
index: 0
}
},
methods: {
incrementRight: function() {
// Note that '%' operator in JS is remainder and NOT modulo
this.index = ++this.index % this.exhibitors.length
},
incrementLeft: function() {
// Note that '%' operator in JS is remainder and NOT modulo
if (this.index === 0) {
this.index = this.exhibitors.length - 1
} else {
this.index = --this.index % this.exhibitors.length
}
}
},
components: {
'display': Display
}
}
</script>
So you can get what you want to happen and there are two ways of making it happen that I can think of. First I will just clarify the terms relating to this because you seem to have them the wrong way around. Let's look at you tier structure which is like this:
Control.vue
contains: Display.vue
contains: Exhibitors.vue & Buyers.vue.
Therefore Control.vue is the parent of Display.vue which is the parent of Buyers.vue and Exhibitors.vue.
Anyway, What we need to do is control the array of exhibitors (and I guess buyers but you didn't include them in your code so I'll do likewise) which is in Exhibitors.vue from Control.Vue even though they don't have a direct parent child relationship. What I've done is set a prop that is passed to Display.vue which uses scoped slots to render the exhibitors from Exhibitors.Vue. Because the left and right buttons need to know when to stop going I have emitted the array length from Exhibitors.vue to Display.vue and again to Control.vue. It all works so heres some code.
//Control.vue
<template>
<div class="content">
<display v-on:finalLength="setIndexLimit" :i="index"></display>
<button #click="changeDown">Down</button>
<button #click="changeUp">Up</button>
<p>{{indLimit}}</p>
</div>
</template>
<script>
import Display from '#/components/Display'
export default {
components: {
Display
},
data: () => ({
index: 0,
indLimit: 0
}),
methods: {
changeUp() {
if (this.indLimit === this.index+1) {
this.index=0
}
else {
this.index ++
}
},
changeDown() {
if (this.index === 0) {
this.index = this.indLimit - 1
}
else {
this.index --
}
},
setIndexLimit(e) {
this.indLimit = e
}
}
}
</script>
and
//Display.vue
<template>
<div id="display">
<p>From Display</p>
<exhibitors v-on:ExLength="setLength">
<p>{{i}}</p>
</exhibitors>
<exhibitors>
<p slot-scope="{ exhibitors }">{{exhibitors[i].firstName}}</p>
</exhibitors>
<exhibitors>
<p slot-scope="{ exhibitors }">{{exhibitors[i].lastName}}</p>
</exhibitors>
<exhibitors>
<p slot-scope="{ exhibitors }">{{exhibitors[i].tagNum}}</p>
</exhibitors>
<exhibitors>
<p slot-scope="{ exhibitors }">{{exhibitors[i].species}}</p>
</exhibitors>
</div>
</template>
<script>
import Child from '#/components/admin/Exhibitors'
export default {
components: {
Exhibitors
},
props: [
'i'
],
data: () => ({
exhibitLength: null
}),
methods: {
setLength(e) {
this.exhibitLength = e
this.$emit('finalLength',e)
}
}
}
</script>
And finally:
//Exhibitors.vue
<template>
<div>
<slot :exhibitors="exhibitors"><p>I'm the child component!</p></slot>
</div>
</template>
<script>
export default {
data: () => ({
exhibitors: [
{
firstName: 'Joe',
lastName: 'Burns',
tagNum: 1,
species: 'ant'
},
{
firstName: 'Tom',
lastName: 'Yorke',
tagNum: 2,
species: 'bee'
},
{
firstName: 'Flit',
lastName: 'Argmeno',
tagNum: 3,
species: 'giraffe'
}
],
}),
mounted: function () {
this.$nextTick(function () {
let length = this.exhibitors.length
this.$emit('ExLength', length)
})
}
}
</script>
So as I said all that works, you can click the buttons and it will loop through the array contents. You can style it how you want wherever you like. However, it is a bit messy with props and slots and emits and whatnot just to relay a single index number and it would be far easier in my opinion to have a vuex store where 'index' is stored as a state item and can be used and changed anywhere without all the above carry on.

Validate Child component Vue vee-validate

App (parent)
Hi I have these component (Child)
TextComponent
InfoErrorForm
When I press submit from the parent component App is not validate this form. So I tried to validate with inject $validator in the child component (TextComponent), and provide but not show message error .
If you can help me to validate children form inisde parent component.
This is my code
AppComponent
<template>
<div>
<!-- Form validar numero max input -->
<form :class="{'was-validated': error_in_form_save_progress}" >
<card-shadow v-for="(texto,key) in sections_template.texts" :key="key" >
<texto-component
:orden="key+2"
v-model="sections_template.texts[key].content"
:tituloComponente="texto.title"
:inputName="texto.title" >
<template slot="section_show_error_validate_input">
<info-error-form
:listErrors='errors'
:name_field = "texto.title"
v-show = "error_in_form_save_progress" >
</info-error-form>
</template>
</texto-component>
</card-shadow>
</form>
<div class="row foot_button" >
<div class="offset-md-3 col-md-3">
<button class="btn" #click.prevent="save_progrees"> Guardar Cambios</button>
</div>
</div>
</div>
</template>
<script>
export default {
provide() {
return {
$validator: this.$validator,
};
},
data: function(){
return {
sections_template: {
texts:[
{
section_template_id: 1,
type: "texto",
title: "fundamentacion",
content: ""
},
{
section_template_id: 2,
type: "texto",
title: "sumilla",
content: ""
}
] },
error_in_form_save_progress: true
}
},
methods:{
save_progrees(){
this.$validator.validateAll().then((result) => {
if (result) {
this.error_in_form_save_progress = false;
alert("se guardaran cambios");
return
}
this.error_in_form_save_progress = true;
});
}
}
}
</script>
I found solution with this code. In my parent component i add provide and i send the $validator
export default {
components:{
...
},
provide() {
return {
$validator: this.$validator,
}
},
In my child component i received this
inject: ['$validator'],
In my parent component i add this method to invoque validation
methods:{
save_progrees(){
var validationArray = this.$children.map(function(child){
return child.$validator.validateAll();
});
window.Promise.all(validationArray).then((v) => {
v.some( element => { if ( element == false ) { throw "exists error in child component";} });
this.error_in_form_save_progress = false;
alert("datos guardados");
return
}).catch(() => {
this.show_message_error_validation();
});
},
show_message_error_validation(){
this.error_in_form_save_progress = true;
}
}
Finally to show error in component info-error I use this code
<template>
<div class="row" v-if="errors.items">
<div class="col-md-12">
<template v-for="(e,key) in errors.items" >
<div class="text-danger" v-if="e.field ==name_field" :key="key"> {{e.msg}} </div>
</template>
</div>
</div>
</template>
In your child component do watch for this.errors and in the watch, put this.$emit
Something like this below :
watch: {
errors: function (value) {
this.$emit('TextComponent', value)
}
}
And then catch it on your parent component see it here https://v2.vuejs.org/v2/api/#vm-emit