Vue js, how to bind input value from array? - javascript

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>

Related

interpolate a prop in interpolation vue js

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>

Show fetched data after grouping in list in vue template

I'm making timeline with users activities in vue template. I would like to list fetched data by grouping it by created_at.
I have fatched data with axios from laravel controller. Resulting array is bellow.
activities:Array[2]
0:Object
causer_id:1
created_at:"2019-09-20 08:55:29"
description:"updated"
id:1
subject_type:"App\User"
1:Object
causer_id:1
created_at:"2019-10-20 08:58:10"
description:"updated"
id:2
subject_type:"App\User"
How to group array by created_at, populate "timeline-day" with value, and then fill "timeline-box" with activities from that day?
<div class="timeline-day">
***created_at***
</div>
<div class="timeline-box" >
<div class="box-content" v-for="(activity, i) in activities" :key="i">
<div class="box-item">{{ activity.subject_type }}</div>
<div class="box-item">{{ activity.description }}</div>
<div class="box-item">{{ activity.created_at }}</div>
</div>
Something like this should do it.
new Vue({
el: '#app',
data () {
return {
activities: [
{
causer_id: 1,
created_at: "2019-09-20 08:55:29",
description: "updated",
id: 1,
subject_type: "App\User"
}, {
causer_id: 1,
created_at: "2019-09-20 09:54:25",
description: "updated",
id: 3,
subject_type: "App\User"
}, {
causer_id: 1,
created_at: "2019-10-20 08:58:10",
description: "updated",
id: 2,
subject_type: "App\User"
}
]
}
},
computed: {
days () {
const map = {}
for (const activity of this.activities) {
const day = activity.created_at.slice(0, 10)
map[day] = map[day] || { created_at: day, activities: [] }
map[day].activities.push(activity)
}
return Object.keys(map).sort().map(day => map[day])
}
}
})
.timeline-day {
border-top: 1px solid #000;
font-weight: bold;
}
<script src="https://unpkg.com/vue#2.6.10/dist/vue.js"></script>
<div id="app">
<template v-for="day in days">
<div class="timeline-day">
{{ day.created_at }}
</div>
<div class="timeline-box">
<div class="box-content" v-for="(activity, i) in day.activities" :key="i">
<div class="box-item">{{ activity.subject_type }}</div>
<div class="box-item">{{ activity.description }}</div>
<div class="box-item">{{ activity.created_at }}</div>
</div>
</div>
</template>
</div>
It strips out the date using activity.created_at.slice(0, 10).
For each day it builds up an object containing the date and the array of activities. These are all stored in map. Once it's finished it puts those objects in an array for use by the template.
While I have included a sort() for the dates it doesn't sort the activities, so in practice they would need to already be sorted. You could add in extra sorting if that assumption doesn't hold.
Sorting the activities first lends itself to a slightly different algorithm. The temporary map wouldn't be needed and the objects can just be held in the final array from the get go. As the activities are sorted the only object we need access to is the one at the end of the array. Either the next activity will be added to that last object or a new object needs creating.

Dynamics inputs, the v-model update all values in v-for

I try this following code with Vue.js 2:
<div id="app">
<div v-for="(item, index) in items">
<div>
<input type="text" v-model="items[index].message">
<input type="text" v-model="items[index].surface">
</div>
</div>
<button #click="addNewfield">Add</button>
</div>
var app = new Vue({
el: '#app',
data: {
item: {
message: 'test',
surface: 45
},
items: [],
},
mounted() {
this.items.push(this.item)
},
methods: {
addNewfield() {
this.items.push(this.item);
}
}
})
The goal is to create news input when user click on Add button. I tried different ways like :
<input type="text" v-model="item.message">
But it doesn't work. If you write in "message" input, all "message" inputs will be updated.
How can I only updated the concerned value ?
Thanks for help !
This is happening because objects in Javascript are stored by reference. This means that every time you push this.item onto the array, it's adding a reference to the exact same object as the last.
You can avoid this by creating a new object each time:
methods: {
addNewfield() {
const obj = {
message: 'test',
surface: 45
}
this.items.push(obj);
}
}
Another option would be to clone the original object each time like:
methods: {
addNewfield() {
const clone = Object.assign({}, this.item);
this.items.push(clone);
}
}

Making object of items in decending order data object in vue.js

In vue.js, in my data object I have an object of items that are sorted in one order. I want to make it in decending order.
My vue template looks like below snippet -
<div class="form-element form-element-checkbox" :key="key" v-for="(value, key) in items">
<input
:id="getID(value)"
type="checkbox"
:value="value"
#change="updateFilter"
v-model="selections"
:checked="isSelected(value)">
<label class="chk-small" :for="getID(value)">
<span :aria-label="`Rated ${key}.0 out of 5`" class="star-rating noBlank" :style="{ width: `${key * 20}px` }"></span>
</label>
</div>
And items object looks like
data() {
return {
items: {
5: 'item_5',
4: 'item_4',
3: 'item_3'
},
selections: []
};
},
Storing data in that order does't work.Tried to use items.slice().reverse() also. What might be wrong here ? I'm not able to get.
If you cannot / do not want to change your items dictionary into an array, you can simply use Object.keys() to extract its keys and work on them.
new Vue({
el: '#app',
data() {
return {
items: {
// Note: even though you write them in descending order,
// the JS engine will list them in ascending order.
5: 'item_5',
4: 'item_4',
3: 'item_3',
},
};
},
});
<script src="https://unpkg.com/vue#2"></script>
<div id="app">
<p>itemsKeys: {{Object.keys(items)}}</p>
<ol>
<li v-for="(key, keyIndex) of Object.keys(items).reverse()">
keyIndex: {{keyIndex}} / item key: {{key}} / item value: {{items[key]}}
</li>
</ol>
</div>

how to see changes in new data generated in a vue component?

Well I hope to explain, I'm generating this data from a component, when I click the checkbox changes in the generated data are not reflected, but when clicking the button with a data already in the instance changes are made, I appreciate if you explain Why or do they have a solution?
this my code
js
Vue.component('fighters', {
template: '#fighters-template',
data() {
return {
items: [
{ name: 'Ryu' },
{ name: 'Ken' },
{ name: 'Akuma' }
],
msg: 'hi'
}
},
methods: {
newData() {
this.items.forEach(item => {
item.id = Math.random().toString(36).substr(2, 9);
item.game = 'Street Figther';
item.show = false;
});
this.items.push()
},
greeting() {
this.msg = 'hola'
}
}
});
new Vue({
el: '#app'
})
html
<main id="app">
<fighters></fighters>
</main>
<template id="fighters-template">
<div class="o-container--sm u-my1">
<ul>
<li v-for="item in items">
<input type="checkbox" v-model="item.show">
{{ item.name }}</li>
</ul>
<button class="c-button c-button--primary" #click="newData()">New Data</button>
<h2>{{ msg }}</h2>
<button class="c-button c-button--primary" #click="greeting()">Greeting</button>
<hr>
<pre>{{ items }}</pre>
</div>
</template>
this live code
https://jsfiddle.net/cqx12a00/1/
Thanks for you help
You don't declare the show variables that your checkboxes are bound to, so they are not reactive – Vue is not aware when one is updated.
It should be initialized like so:
items: [
{ name: 'Ryu', show: false },
{ name: 'Ken', show: false },
{ name: 'Akuma', show: false }
]
You might think that newData would fix it, since it assigns a show member to each item, but Vue cannot detect added properties so they're still not reactive. If you initialized the data as I show above, then the assignment would be reactive.
If you want to add a new reactive property to an object, you should use Vue.set.

Categories