I'm having problems binding input data to object properties. I'm iterating through an object to generate input fields from its attributes, but the data binding won't work using v-model. Here's my code (the console log remains empty):
<div id="app">
<div v-for='value, key in fields'>
{{ key }}: <input v-model='value'>
</div>
<button #click="add">Add</button>
</div>
<script>
new Vue({
el: '#app',
data: {
fields: {
id: 123,
name: 'abc'
}
},
methods: {
add: function(){
console.log('id: ' + this.fields.id)
console.log('name: ' + this.fields.name)
}
}
})
</script>
You will have to use fields[key] with v-model as value will not work there, it is an local variable of v-for.
<div id="app">
<div v-for='(value, key) in fields'>
{{ key }}: <input v-model="fields[key]">
</div>
<button #click="add">Add</button>
</div>
See working demo here.
Related
I have a component that accepts props
<BarChart
header="header name"
dataPoint="data_to_look_at"
size=35
/>
With the dataPoint prop I want to use this in my component so that I can use it (I think, idk if this is the right solution) in an interpolated string like this to access items in an object
// inside of a v-for loop that iterates over an object etc
{{ data[index].attributes.${dataPoint} }}
I'm not sure how to do this and of course the above doesn't work
string interpolation Vue js
Not relevant to my question
How can I solve "Interpolation inside attributes has been removed. Use v-bind or the colon shorthand"? Vue.js 2
Not quite it either
How do interpolate a prop in a interpolation?
Observation : As you are iterating your item list by using v-for loop, No need to access the item by index. You can simply do like this :
<p v-for="data in items" :key="data.id">
{{ data.attributes[datapoint] }}
</p>
Live Demo :
Vue.component('child', {
data: function() {
return {
items: [{
id: 1,
attributes: {
name: 'Alpha'
}
}, {
id: 2,
attributes: {
name: 'Beta'
}
}, {
id: 3,
attributes: {
name: 'Gamma'
}
}]
}
},
props: ['header', 'datapoint'],
template: `<div>
<p v-for="data in items" :key="data.id">{{ data.attributes[datapoint] }}</p>
</div>`
});
var app = new Vue({
el: '#app'
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<child header="Header Name" dataPoint="name"></child>
</div>
i have a number field, that i want to bind value from an array like this :
<div v-for="p in products">
<input type="number" :value="carModel[p.name]" />
</div>
where p.name is a string (product name), car_a :
const app = Vue.createApp({
data() {
return {
products: {...},
carModel:[{car_a:4}, {car_b:2} ]
}
}
...
But this does not works, the input remains empty, while following runs without issue :
<div v-for="p in products">
<input type="number" :value="carModel" />
</div>
const app = Vue.createApp({
data() {
return {
products: {...},
carModel:4
}
}
...
So, my question is, how to bind the value properly from array if i have the key ?
thank you
First off, I wonder if your carModel array item object structure could be improved; perhaps better would be something like:
products: ["car_a", "car_b"],
carModel: [
{
name: "car_a",
value: 4,
},
{
name: "car_b",
value: 2,
},
{
name: "car_c",
value: 3,
},
]
I assume that the Strings held in the products array are a sub-set of the Strings in the carModel array of objects, and since you want an input that is reactive with the data, you will want the input's model to be the values held by each carModel object. So rather than v-for loop over the products array, v-for loop over the carModel but filter out the elements whose Strings are not held by the products array. Since we should not combine v-for with v-if, this filtering should be done in a computed property:
computed: {
filteredCarModel() {
return this.carModel.filter(cm => {
return this.products.includes(cm.name);
});
}
},
so then in the HTML template you can loop over the computed property:
<div v-for="cm in filteredCarModel" :key="cm.name">
<label :for="cm.name">{{ cm.name }} </label>
<input type="number" :id="cm.name" v-model="cm.value" />
</div>
This is key:
This will display proper values in the input elements, and these elements will remain reactive to the model such that changes to the inputs will cause changes to the data model, and visa-versa. Thus the display will be truly bound to the data model, which is what you're asking and what Vue.js is all about.
Here is a sample HTML showing that the data is in fact reactive:
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<html>
<head>
<meta charset=utf-8" />
<title>Vue Example</title>
<script src="https://unpkg.com/vue#next"></script>
</head>
<body>
<h1>Vue Example</h1>
<div id="app">
<h2>Show Data</h2>
<div v-for="cm in carModel" :key="cm.name">
{{ cm.name }} : {{ cm.value }}
</div>
<h2>Change Data</h2>
<div v-for="cm in filteredCarModel" :key="cm.name">
<label :for="cm.name">{{ cm.name }} </label>
<input type="number" :id="cm.name" v-model="cm.value" />
</div>
</div>
<script>
Vue.createApp({
data() {
return {
products: ["car_a", "car_b"],
carModel: [
{
name: "car_a",
value: 4,
},
{
name: "car_b",
value: 2,
},
{
name: "car_c",
value: 3,
},
],
};
},
computed: {
filteredCarModel() {
return this.carModel.filter((cm) => {
return this.products.includes(cm.name);
});
},
},
}).mount("#app");
</script>
</body>
</html>
</html>
Note that if the products array is not a subset of the carModel array, if all the Strings present in products are also present in carModel name fields, then there will be no need to have a filteredCarModel() computed property.
change your carModel to hash
carModel: { 'car_a': 4, 'car_b': 2 }
because what you are trying to do is access array by string(which it should be number as index of the array)
Observations :
products should be an array to iterate via v-for.
As carModel is an array. You can not access object properties directly. Access it via index.
Working Demo :
new Vue({
el: "#app",
data: {
products: [{name: 'car_a'}, {name: 'car_b'}],
carModel:[{car_a: 4}, {car_b: 2}]
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div v-for="(product, index) in products" :key="index">
<input type="number" :value="carModel[index][product.name]"/>
</div>
</div>
I was just playing around with Vue.js and found a problem I cannot explain why it happens. Here is my MWE:
Vue.component('list-entry', {
'template': '<input type="text" :value="t" #input="fireEvent()" ref="text">',
'props': ['t', 'idx'],
'methods': {
fireEvent() {
this.$emit('update:t', {
'value': this.$refs.text.value,
'idx': this.idx
})
}
}
})
new Vue({
el: "#app",
data: () => ({
texts: [
'Foo', 'Bar', 'Baz'
]
}),
methods: {
update(ev) {
console.log('Set ' + ev.value + ' at index ' + ev.idx)
this.texts[ev.idx] = ev.value
console.log(this.texts)
}
}
})
input {
display: block;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div class='container'>
<list-entry
v-for="(t, idx) in texts"
:key="t"
:t="t"
:idx="idx"
#update:t="update($event)"
></list-entry>
</div>
{{ texts }}
</div>
Let me explain it a bit. There is a global data property texts that contains a few strings. For each of these strings, a custom component list-entry is generated. The strings are propagated using v-bind to these components.
Upon change of the text in the inputs, I want to update the global data property. Therefore a update:t event is fired and listened on in the main app. Here the update(ev) function should do the things.
If you run it you will see that in the console, the corresponding messages appear and the array is updated as well. However, in the output on the HTML screen (replaced from {{ texts }}), the value is not updated.
Now I am confused. Is the data property updated or not? Why is it not visible in the mustache output? Why is it correctly output in the console at the same time?
You could achieve the desired behavior with a short code by using Using v-model on Components
Vue.component('list-entry', {
'template': `<input type="text" :value="value" #input="$emit('input', $event.target.value)" />`,
props: ['value'],
})
new Vue({
el: "#app",
data: () => ({
texts: [
'Foo', 'Bar', 'Baz'
]
}),
})
input {
display: block;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div class='container'>
<list-entry v-for="(t, idx) in texts" :key="idx" v-model="texts[idx]"></list-entry>
</div>
{{ texts }}
</div>
I have a text input with v-model binding it's value to data. I would also like to be able to call my parse() function when this v-model value changes in order to update an array that is also on data.
<div id="app">
<input
id="user-input"
type="text"
v-model="userInput">
<ul id="parsed-list">
<li v-for="item in parsedInput">
{{ item }}
</li>
</ul>
</div>
new Vue({
el: '#app',
data: {
userInput: '',
parsedInput: []
}
})
let parse = input => {
return input.split(',')
}
How should I go about updating data.parsedInput with the parse() function using the v-model change? What is the proper Vue way of doing this?
A proper Vue way of a data property that depends on another is with a computed property, that way parsedInput is automatically updated whenever userInput changes:
let parse = input => {
return input.split(',')
}
new Vue({
el: '#app',
data: {
userInput: '',
},
computed: {
parsedInput() {
return parse(this.userInput)
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.2.1/vue.js"></script>
<body>
<div id="app">
<input id="user-input" type="text" v-model="userInput">
<ul id="parsed-list">
<li v-for="item in parsedInput">
{{ item }}
</li>
</ul>
</div>
</body>
As a sidenote: declare the parse function before using it, to prevent is not defined errors.
I'm working on a basic to-do application. Each to-do/task item gets listed as an input item in a Vue <list-item> component, and the <list-item>s are displayed with a v-for pointing to a tasks array.
I'm trying to allow the user to edit each task input, and upon changing the value, have this update the array item (rather than just the input itself). My #change event on the input is firing, but I'm at a loss as to what to do after this point.
https://jsfiddle.net/xbxm7hph/
HTML:
<div class="app">
<div class="add-control-area columns is-mobile is-multiline">
<responsive-container>
<div class="field is-grouped">
<div class="control is-expanded">
<input class="input add-control-text" type="text" placeholder="New Task" v-model="newTask" v-on:keyup.enter="addTask">
</div>
<div class="control">
<a class="button is-white add-control-button" #click="addTask" :disabled="!isThereText">Add Task</a>
</div>
</div>
</responsive-container>
<responsive-container>
<list-item v-for="task, index in tasks" :item="task" :index="index" #task-completed="completeTask(index)" #task-deleted="deleteTask(index)" ></list-item>
</responsive-container>
</div>
</div>
JS:
Vue.component('list-item', {
props: ['item', 'index'],
template: `<div class="task-wrapper">
<input class="task" :value="item" #change="updateTask()">
<div class="task-control delete-task" #click="deleteTask()"></div>
<div class="task-control complete-task" #click="completeTask()"></div>
</div>
`,
methods: {
completeTask: function() {
this.$emit('task-completed', this.index);
},
deleteTask: function() {
this.$emit('task-deleted', this.index);
},
updateTask: function() {
console.log('changed');
}
}
});
Vue.component('responsive-container', {
template: `
<div class="column is-4-desktop is-offset-4-desktop is-10-tablet is-offset-1-tablet is-10-mobile is-offset-1-mobile">
<div class="columns is-mobile">
<div class="column is-12">
<slot></slot>
</div>
</div>
</div>
`
});
var app = new Vue({
el: '.app',
data: {
tasks: [],
completedTasks: [],
newTask: ''
},
methods: {
addTask: function() {
if(this.isThereText) {
this.tasks.push(this.newTask);
this.newTask = '';
this.updateStorage();
}
},
completeTask: function(index) {
this.completedTasks.push(this.tasks[index]);
this.tasks.splice(index, 1);
this.updateStorage();
},
deleteTask: function(index) {
this.tasks.splice(index, 1);
this.updateStorage();
},
updateStorage: function() {
localStorage.setItem("tasks", JSON.stringify(this.tasks));
}
},
computed: {
isThereText: function() {
return this.newTask.trim().length;
}
},
// If there's already tasks stored in localStorage,
// populate the tasks array
mounted: function() {
if (localStorage.getItem("tasks")) {
this.tasks = JSON.parse(localStorage.getItem("tasks"));
}
}
});
Use a v-model directive on your <list-item> component, instead of passing in an item property. You will also need to pass in a reference from the array (tasks[index]), because task in this scope is a copy that is not bound to the element of the array:
<list-item v-for="task, index in tasks" v-model="tasks[index]"></list-item>
In your component definition for the list item, you'll need to now take in a value prop (this is what gets passed when using v-model) and set a data property item to that value. Then, emit an input event on the change to pass the item value (this is what the component is listening for when using v-model):
Vue.component('list-item', {
props: ['value'],
template: `<div class="task-wrapper">
<input class="task" v-model="item" #change="updateTask"></div>
</div>
`,
data() {
return {
item: this.value,
}
},
methods: {
updateTask: function() {
this.$emit('input', this.item);
}
}
});
Here's a fiddle with those changes.
As Bert Evans mentioned, even though this works, Vue requires that components using the v-for directive also use a key attribute (you will get a warning from Vue otherwise):
<list-item
v-for="task, index in tasks"
:key="index"
v-model="tasks[index]"
></list-item>
Also, realize that the index variable in a v-for scope can change, meaning that the item at index 1 might change to index 4 and this can pose some problems as the application gets more complex. A better way would be to store items as an object with an id property. This way you can have an immutable id associated with the item.
You can pass the index and new value to your change event handler:
<input class="task" :value="item" #change="updateTask(index, $event)">
Then access them accordingly:
updateTask: function(index, event) {
console.log(index);
console.log(event.target.value);
}