I have been wrestling with an issue with the way v-model works when you have two inputs in the same child component. I am passing in an array of objects from a parent component and need to update different parts of an object in an array of objects with the two input fields. I can get one input field to work but have no idea how to have both inputs effect different items in the object (which is in an array). I am new to Vue and don't totally understand the functionality of passing an array of objects and affecting specific items in those objects.
Parent Component
<template>
<div class="budgetGroup">
<header><input title="CardTitle" type="text" v-bind:placeholder="budgetItemHeading"></header>
<div class="budgetItemContainer">
<div class="budgetItemRow">
<!--creates new component when click event happens and places below-->
<div v-for="(input, index) in budgetRows" :key="index">
<budgetItemRowContent v-model="budgetRows"></budgetItemRowContent>
<progress data-min="0" data-max="100" data-value="20"></progress>
</div>
</div>
</div>
<footer class="budgetGroupFooter">
<div class="budgetGroupFooter-Content budgetGroupFooter-Content--Narrow">
<button class="addBudgetItem" id="addBudgetItem" v-on:click="createNewContent()">
<svg xmlns="http://www.w3.org/2000/svg" width="8" height="8" viewBox="0 0 8 8">
<path fill="#FD0EBF" d="M3 0v3h-3v2h3v3h2v-3h3v-2h-3v-3h-2z"></path>
</svg>
Add Item
</button>
</div>
</footer>
</div>
export default {
name: 'budgetGroup',
components: {
budgetItemRowContent,
BudgetItemButton,
},
data: () => {
return {
budgetItemHeading: 'Housing',
// creates array containing object for budget row information
budgetRows: [
{inputbudget: '', amountbudgeted: 0, remaining: 0, id: uniqId()},
],
};
},
methods: {
// creates new budgetRow when button is clicked
createNewContent() {
this.budgetRows.push({inputbudget: '', amountbudgeted: 0, remaining: 0, id: uniqId() });
},
},
};
Child Component- needing both inputs to affect different items in object
<template>
<div class="budgetItemRowContent">
<div class="budgetItemRow-Column">
<div class="budgetItemLabel">
//Input that is suppose to update InputBudget in object
<input type="text" maxlength="32" placeholder="Label" class="input-Budget-Inline-Small budgetItemRow-Input">
</div>
</div>
<!--input that will update amoundbudgeted -->
<div class="budgetItemRow-Column">
<div class="amountBudgetedInputContainer">
//Input that will be updating amountBudgeted in object
<input v-model.number="amount" class="amountBudgetedNumber budgetItemRow-Input input-Budget-Inline-Small" type="number" placeholder="$">
</div>
</div>
<div class="budgetItemRow-Column">
<span class="budgetItemSecondColumnMoney-Spent">
<span class="money-symbol">$</span>
<span class="money-integer"></span>
<!--<span class="money-decimal">.</span>-->
<!--<span class="money-fractional">04</span>-->
</span>
</div>
</div>
<script>
export default {
//only one prop that is being updated(dont know how to have two seperate items in object to get updated
props: ['value'],
computed: {
amount: {
set(newVal) {
this.$emit('input', newVal);
},
get() {
return this.value;
},
},
},
};
Related
I have a custom component called HobbyForm which is a simple form with two controls, a checkbox and an input, this component is being called from a parent component called Content, along with other similar 'form' components.
<template>
<form>
<div class="row align-items-center">
<div class="col-1">
<Checkbox id="isHobbyActive" :binary="true" v-model="isActive"/>
</div>
<div class="col-5">
<InputText id="hobby" placeholder="Hobby" type="text" autocomplete="off" v-model="hobby"/>
</div>
</div>
</form>
</template>
<script>
export default {
name: 'HobbyForm',
data() {
return {
hobby: {
isActive: false,
hobby: null
}
}
},
}
</script>
My Content component is something like:
<template>
<language-form></language-form>
<hobby-form v-for="(hobbie, index) in hobbies" :key="index" v-bind="hobbies[index]"></hobby-form>
<Button label="Add Hobby" #click="addHobby"></Button>
</template>
<script>
export default {
name: "Content",
components: {
LanguageForm,
HobbyForm
},
data() {
return {
language: '',
hobbies: [
{
isActive: false,
hobby: null
}
]
};
},
methods: {
addHobby() {
this.hobbies.push({
isActive: false,
hobby: null
});
}
},
};
</script>
The idea is to be able to add more instances of the HobbyForm component to add another hobby record to my hobby data property; but I don't know how to keep track of these values from my parent without using an emit in my child components, since I don't want to manually trigger the emit, I just want to have the data updated in my parent component.
How should I access my child component's data from my parent and add it to my array?
In the current form passing parent data into a child component via v-bind="hobbies[index]" makes no sense as the child component (HobbyForm) has no props so it does not receive any data from the parent...
To make it work:
Remove data() from the child HobbyForm
Instead declare a prop of type Object
Bind form items to the properties of that Object
Pass the object into each HobbyForm
<template>
<form>
<div class="row align-items-center">
<div class="col-1">
<Checkbox id="isHobbyActive" :binary="true" v-model="hobby.isActive"/>
</div>
<div class="col-5">
<InputText id="hobby" placeholder="Hobby" type="text" autocomplete="off" v-model="hobby.hobby"/>
</div>
</div>
</form>
</template>
<script>
export default {
name: 'HobbyForm',
props: {
hobby: {
type: Object,
required: true
}
}
}
</script>
Even tho props are designed to be one way only so child should not mutate prop value, this is something else as you do not mutate prop value, you are changing (via a v-model) the properties of the object passed via a prop (see the note at the bottom of One-Way Data Flow paragraph)
Also change the parent to:
<hobby-form v-for="(hobby, index) in hobbies" :key="index" v-bind:hobby="hobby"></hobby-form>
Demo:
const app = Vue.createApp({
data() {
return {
hobbies: [{
isActive: false,
hobby: null
}]
};
},
methods: {
addHobby() {
this.hobbies.push({
isActive: false,
hobby: null
});
}
},
})
app.component('hobby-form', {
props: {
hobby: {
type: Object,
required: true
}
},
template: `
<form>
<div class="row align-items-center">
<div class="col-1">
<input type="checkbox" id="isHobbyActive" v-model="hobby.isActive"/>
</div>
<div class="col-5">
<input type="text" id="hobby" placeholder="Hobby" autocomplete="off" v-model="hobby.hobby"/>
</div>
</div>
</form>
`
})
app.mount('#app')
<script src="https://unpkg.com/vue#3.1.5/dist/vue.global.js"></script>
<div id='app'>
<hobby-form v-for="(hobby, index) in hobbies" :key="index" v-bind:hobby="hobby"></hobby-form>
<button #click="addHobby">Add Hobby</button>
<hr/>
<pre> {{ hobbies }} </pre>
</div>
Hi everyone i have a big trouble i need to modify the value of checkbox from parent to another parent to child , is not this difficulty i know but i'm at the start and after spent 2 days for this i can't try anything else , now i'll add all the code the child is named "UICheckbox" and i pass it to "ToDoListItem" and this last is passed to "ToDoList" , and every Todos is an in an array into "store.js"
UICheckbox
<template>
<div class="checkbox-container">
<input
type="checkbox"
:id="id"
:v-model="localTodo"
>
<label :for="id">
<p class="font-bold">{{ text }}</p>
</label>
</div>
</template>
<script>
export default {
props: [
"id",
"value",
"text"
],
data() {
return {
localTodo: this.value
}
},
watch: {
localTodo:{
handler(newVal, oldVal) {
this.$emit("change", newVal)
},
deep: true,
}
}
}
</script>
<style>
</style>
ToDoListItem
<template>
<div class="flex flex-row w-full my-2">
<UICheckbox
:v-model="localTodo.checked"
:id="todo.id"
:text="todo.text"
#change="localTodo.cheked = $event"
/>
<delete-button :todo="todo" />
</div>
</template>
ToDoList
<to-do-list-item
:todo="todo"
#click="changeChecked(todo)"
#change="todo.checked = $event"
/>
UPDATE
Was able to make it work, but got one last problem. Updated code is here:
VueJs not working on first click or first event
-----------------------------------------------------------
I've been trying to find out a way for the components inside a loop to not act as one.
I have a loop (3 divs), and inside the loop, I have 2 textboxes. But whenever I enter a value in any of them, the value is populated to everyone.
Can anyone help me separate those components?
I'm trying to make the parent div (1st loop) dynamic. So the children components (2nd loop) should be acting separately with their own grandparent components (textbox).
Here's my code:
<div id="app">
<div v-for="(ctr, c) in 3" :key="c">
<button #click="input_add">1st</button>
<div>
<div v-for="(input, act) in inputs" :key="act.id">
<input type="text" v-model="input.name">
<input type="text" v-model="input.time">
<button #click="input_remove(act)">Delete</button>
<button #click="input_add">Add row</button>
</div>
</div>
{{ inputs }}
</div>
</div>
const app = new Vue({
el: "#app",
data: {
inputs: [],
counter: 0,
},
methods: {
input_add() {
this.inputs.push({
id: this.counter + 1,
day: null,
name: null,
time: null,
})
this.counter += 1
},
input_remove(index) {
this.inputs.splice(index,1)
this.counter -= 1
}
}
});
Result:
as I mentioned in the comment, you should create a component for the iterated item.
parent component:
<div v-for="(item, index) in array" :key="index">
<child :item="item" />
</div>
Now you sent the item as prop. Let's catch it in child.
child components:
<div>
<input type="text" v-model="input.name">
<input type="text" v-model="input.time">
<button #click="input_remove(act)">Delete</button>
<button #click="input_add">Add row</button>
</div>
{{ inputs }}
props: [item], // I am not sure you need it or not, BUT just showing how to do it.
data() {return { // your datas };},
methods: {
// your methods...
},
//and else...
Now each iterated item can control self only. I am hope it make sense now.
then build the buttons an input in child component. After that you can apply the events for just clicked item.
You should use Array of Objects. Here's a codesandbox. This way everytime you add a new object to the array, a new index is created with a new name and time ready to be filled in.
<template>
<div id="app">
<img width="25%" src="./assets/logo.png">
<div v-for="item in basic" :key="item.id">
<button #click="addRow">Add row</button>
<input type="text" v-model="item.name">
<input type="text" v-model="item.time">
{{ item.name }} - {{ item.time }}
</div>
</div>
</template>
<script>
export default {
name: "App",
data() {
return {
id: 1,
basic: [{ name: "", time: "" }]
};
},
methods: {
addRow() {
console.log("added");
this.id += 1;
this.basic.push({
name: "",
time: ""
});
}
}
};
</script>
I'm making cart app with Vue. And trying to make quantity counter, but when I click - or + button, all of items quantity also increase or decrease.
So it seems like I need to give each key for buttons but I don't know how to do that.
new Vue({
el: '#app',
data(){
return {
foods: [{
id: 1,
imgUrl: 'https://image.shutterstock.com/image-photo/healthy-food-clean-eating-selection-260nw-761313058.jpg',
title: 'Food',
price: '5,00'
}],
num:0
}
},
methods:{
increase(index){
this.num++
},
decrease(index){
this.num --
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div
class="items" v-for="(food,index) in foods"
v-bind:food="food"
v-bind:key="food.id"
>
<img class="foodImg" v-bind:src="food.imgUrl" />
<h4>{{food.title}}</h4>
<p>{{food.price}}€</p>
<button :class="index" class="minus" #click="decrease">-</button>
{{num}}
<button :class="index" class="add" #click="increase">+</button>
<button type="submit">Add to cart</button>
</div>
</div>
Your num variable shouldn't be in your component and instead you should attach it to your food items. Otherwise the num variable is shared across all of them.
Please don't forget to give your food items the num variable before you pass the foods array to your component so its not initially empty.
try this:
<div class="items" v-for="(food,index) in foods" v-bind:food="food" v-bind:key="food.id">
<img class="foodImg" v-bind:src="food.imgUrl"/>
<h4>{{food.title}}</h4>
<p>{{food.price}}€</p>
<button :class="index" class="minus" #click="increase(food)">-</button>
{{food.num}}
<button :class="index" class="add" #click="decrease(food)">+</button>
<button type="submit">Add to cart</button>
</div>
Script
<script>
export default {
name:'Products',
props:['foods'],
methods:{
increase(food){
food.num++
},
decrease(index){
food.num--
}
}
}
I'm trying to use a combination of v-for and v-model to get a two-way data bind for some input forms. I want to dynamically create child components. Currently, I don't see the child component update the parent's data object.
My template looks like this
<div class="container" id="app">
<div class="row">
Parent Val
{{ ranges }}
</div>
<div class="row">
<button
v-on:click="addRange"
type="button"
class="btn btn-outline-secondary">Add time-range
</button>
</div>
<time-range
v-for="range in ranges"
:box-index="$index"
v-bind:data.sync="range">
</time-range>
</div>
<template id="time-range">
<div class="row">
<input v-model="data" type="text">
</div>
</template>
and the js this
Vue.component('time-range', {
template: '#time-range',
props: ['data'],
data: {}
})
new Vue({
el: '#app',
data: {
ranges: [],
},
methods: {
addRange: function () {
this.ranges.push('')
},
}
})
I've also made a js fiddle as well https://jsfiddle.net/8mdso9fj/96/
Note: the use of an array complicates things: you cannot modify an alias (the v-for variable).
One approach that isn't mentioned often is to catch the native input event as it bubbles up to the component. This can be a little simpler than having to propagate Vue events up the chain, as long as you know there's an element issuing a native input or change event somewhere in your component. I'm using change for this example, so you won't see it happen until you leave the field. Due to the array issue, I have to use splice to have Vue notice the change to an element.
Vue.component('time-range', {
template: '#time-range',
props: ['data']
})
new Vue({
el: '#app',
data: {
ranges: [],
},
methods: {
addRange: function () {
this.ranges.push('')
},
}
})
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/2.4.2/vue.min.js"></script>
<div class="container" id="app">
<div class="row">
Parent Val
{{ ranges }}
</div>
<div class="row">
<button
v-on:click="addRange"
type="button"
class="btn btn-outline-secondary">Add time-range
</button>
</div>
<time-range
v-for="range, index in ranges"
:data="range"
:key="index"
#change.native="(event) => ranges.splice(index, 1, event.target.value)">
</time-range>
</div>
<template id="time-range">
<div class="row">
<input :value="data" type="text">
</div>
</template>
To use the .sync modifier, the child component has to emit an update:variablename event that the parent will catch and do its magic. In this case, variablename is data. You still have to use the array-subscripting notation, because you still can't modify a v-for alias variable, but Vue is smart about .sync on the array element, so there's no messy splice.
Vue.component('time-range', {
template: '#time-range',
props: ['data'],
methods: {
emitUpdate(event) {
this.$emit('update:data', event.target.value);
}
}
})
new Vue({
el: '#app',
data: {
ranges: [],
},
methods: {
addRange: function () {
this.ranges.push('')
},
}
})
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/2.4.2/vue.min.js"></script>
<div class="container" id="app">
<div class="row">
Parent Val
{{ ranges }}
</div>
<div class="row">
<button
v-on:click="addRange"
type="button"
class="btn btn-outline-secondary">Add time-range
</button>
</div>
<time-range
v-for="range, index in ranges"
:data.sync="ranges[index]"
:key="index">
</time-range>
</div>
<template id="time-range">
<div class="row">
<input :value="data" type="text" #change="emitUpdate">
</div>
</template>