Vue v-for keys and values - vue.js

I have the following JSON:
{
"data": [
{
"title": "a title here",
"news_url": "https://someurl",
"sentiment": "Neutral",
"type": "Article"
},
{
"title": "a title here",
"news_url": "https://someurl",
"sentiment": "Negative",
"type": "Article"
},
{
"title": "a title here",
"news_url": "https://someurl",
"sentiment": "Neutral",
"type": "Article"
}
]
}
I have defined my data object 'news' like so:
data() {
return {
news: [],
};
},
Now I am trying to v-for through these values so that I can get 3 divs each with the title value.
I am trying the following but I really don;t have much of a clue:
<div v-for = "title in news">
{{ title }}
</div>
I get the error: Elements in iteration expect to have 'v-bind:key' directives.
Any idea what I am doing wrong?
Thanks!

Vue documentation indicate:
It is recommended to provide a key attribute with v-for whenever
possible, unless the iterated DOM content is simple, or you are
intentionally relying on the default behavior for performance gains.
Since it’s a generic mechanism for Vue to identify nodes, the key also
has other uses that are not specifically tied to v-for, as we will see
later in the guide.
 
Don’t use non-primitive values like objects and arrays as v-for keys.
Use string or numeric values instead.
Then you should use the key directive binding it with string or numeric value like this:
<div v-for = "(title, index) in news" :key="index">
{{ title }}
</div>

Related

Is there a way to define which object keys to use instead of "label" and "value" in the CoreUI's CSelect component for Vue?

I'm using CoreUI's CSelect component and, in the documentation is specified that the "options" property must be an array of objects with the "label" and "value" keys.
CSelect API
My question is if is there any property of the CSelect component that allows me to specify which keys to use instead of "label" and "value". This way I wouldn't have to apply the map function on my array.
<CSelect
label="Category"
:options="categories"
placeholder="Select Category"
:value.sync="form.category_id"
... // some properties here
/>
// example of data function
data() {
return {
categories: [{id:1 , name:"Vue" }, {id:2, name:"Angular"}],
};
},
Thanks in advance. Sorry for my English.
[{value:1 , label:"Vue" }, {value:2, label:"Angular"}]
This should help you.

Select option entries are only shown on the first select attempt

I am using vuetify and an I'm trying to populate a select box...
...using this JSON Object:
[
{
"configurator": {
"group": {
"property": [
{
"id": "STATUS",
"value": [
{
"id": "OK",
"text": "OK"
},
{
"id": "NOK",
"text": "not OK",
"selected": "true"
}
]
}
]
}
}
}
]
...and I'm trying to render this component:
<v-list v-for="(item, i) in jsonObjPruef.configurator.group.property" :key="i">
<v-select
v-model="item.value"
:items="item.value"
:label="item.text"
/>
</v-list>
Page loads without warnings and errors and I can select from the expected values ("OK", "not OK").
While choosing e.g. "OK" the following warning is displayed:
[Vue warn]: Invalid prop: type check failed for prop "items". Expected Array, got String with value "OK".
And now if I'm trying to select again I only can pick the value I chose before ("OK").
The select option entries are only shown on the first select attempt.
Any idea what I'm missing here?
tl;dr: v-model means: "where user selection is saved". If you save it over the same exact property which holds your options, the options are gone and the <v-select> is broken.
v-model replaces jsonObjPruef.configurator.group.property with the selected option, when you select it. Which makes the <v-select> no longer have items.
You should specify a different v-model - a model property in which you store the selection, (i.e: results):
<v-list v-for="(item, i) in jsonObjPruef.configurator.group.property" :key="i">
<v-select
v-model="results[i]"
:items="item.value"
:label="item.text"
/>
</v-list>
In data, you need to initialize results : {}.
Now results will hold the selected results and you can place a watcher on it to trigger additional functionality when it changes. It doesn't have to be an object, it could be an array. It depends on what you currently have.
Obviously, you can rename results to something less generic, which makes more sense in your specific example.
If you need more help, please add a mcve.

Which key to use in v-for

I am populating a dropdown with an array, however I am confused as to which key i should use in the v-for.
I have tried multiple keys and even without a key. Everything seems to work, but what would be best practice?
<select class="period-control" v-model="selected">
<option v-for="month in car.months" :key="month.id">{{ month }}</option>
</select>
asyncData(context) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve({
car: [
{
id: "1",
brand: "VW",
model: "Transporter",
variant: "Sport",
thumbnail:
"http://pluspng.com/img-png/cargo-van-png-van-png-transparent-image-1878.png",
mprice: "2700",
dpayment: "5400",
months: [
{ month: "12 måneder" },
{ month: "24 måneder" },
{ month: "36 måneder" }
]
},
{
id: "2",
brand: "Nissan",
model: "Tekna",
variant: "Sport",
thumbnail:
"http://pluspng.com/img-png/cargo-van-png-van-png-transparent-image-1878.png",
mprice: "3000",
dpayment: "5400",
months: ["12 måneder", "24 måneder"]
}
].find(el => el.id === context.params.id)
});
}, 1500);
});
}
Ì have no errors, the dropdown is outputting the correct array.
The 'best practice' requires you to use a key that is unique. In your case, you could use the id of each element in the array. The reason for this is, it allows vue to keep track of all the elements between the dom and virtual dom in the event when any one of them is removed.
As a last resort, you could also use the index of the items, but bear in mind that it might lead to some rendering issues as vue will find it difficult to uniquely indentify elements and track the ones that have been removed.
When Vue is updating a list of elements rendered with v-for, by default it uses an “in-place patch” strategy. If the order of the data
items has changed, instead of moving the DOM elements to match the
order of the items, Vue will patch each element in-place and make sure
it reflects what should be rendered at that particular index. This is
similar to the behavior of track-by="$index" in Vue 1.x.
This default mode is efficient, but only suitable when your list
render output does not rely on child component state or temporary DOM
state (e.g. form input values).
To give Vue a hint so that it can track each node’s identity, and thus
reuse and reorder existing elements, you need to provide a unique key
attribute for each item:
see https://v2.vuejs.org/v2/guide/list.html#Maintaining-State

get selected object from options loop

I'm trying to find a way to bind array of objects within Vue select-element. The case is somewhat as follows:
data: {
ideas: [
{ id: 1, code: "01A", text: "option 1", props: [] },
{ id: 2, code: "02A", text: "option 2 , props: [{ details: "something" }]}
]},
currentForm: {
something: "foo",
else: "bar",
ideaCode: "01A",
text: "option 1"
}
];
... and in HTML ...
<select v-model="currentForm.ideaCode" #change="setCodeAndLabelForForm(???)">
<option v-for="i in ideas" value="i">{{ i.text }}<option>
</select>
Basically I need to be able to track which object user selects, trigger my own change-event, all the while having binding with a single key from another object... selected value / reference-key should be separated from user-selected option/object. Note: currentForm is not same object-type as option! It only contains some of those properties which option happens to have, and which I'm trying to transfer to options by triggering change-event for user-selection.
The problem is I haven't figured out how to pass currently selected value for the function OR how to write something like:
<select v-model="selectedIdea" #change="setCodeAndLabelForForm" :track-by="currentForm.ideaCode">
<option v-for="i in ideas" value="i">{{ i.text }}<option>
</select>
One possible (and working) approach is:
<select v-model="currentForm.ideaCode" #change="setCodeAndLabelForForm">
<option v-for="i in ideas" value="i.ideaCode">{{ i.text }}<option>
</select>
setCodeAndLabelForForm: function() {
var me = this;
this.ideas.forEach(function(i) {
if(i.ideaCode == me.currentForm.ideaCode) {
me.currentForm.ideaCode = i.selectedIdea.ideaCode;
me.currentForm.text = i.text;
... do stuff & run callbacks ...
}
});
}
... but it just seems terrible. Any better suggestions?
You can implement like this:
Create empty object data to track the selected value:
currentForm: {}
Watch currentForm on the model and pass the selected object:
<select v-model="currentForm" #change="setCodeAndLabelForForm(currentForm)">
Pass in the selected value in option: (you were doing right in this step, but I just changed i to idea as it's little confusing looping index)
<option v-for="idea in ideas" :value="idea">{{ idea.text }}<option>
Apply your method:
setCodeAndLabelForForm(selected) {
// Now, you have the user selected object
}
A little bit better workaround: use index in v-for
<select v-model="selIdeaIndex" #change="setCodeAndLabelForForm">
<option v-for="(i,idx) in ideas" value="idx">{{ i.text }}<option>
</select>
For the js:
data: {
selIdeaIndex:null,
ideas: [
{ id: 1, code: "01A", text: "option 1", props: [] },
{ id: 2, code: "02A", text: "option 2 , props: [{ details: "something" }]}
]
},
methods:{
setCodeAndLabelForForm: function() {
var selIdea = this.ideas[this.selIdeaIndex];
//Do whatever you wanna do with this selIdea.
}
}
I don't know if this is the best solution, but I solve this problem using computed properties like this:
In the JavaScript file (ES6):
data () {
return {
options: [
{ id: 1, text: "option 1" },
{ id: 2, text: "option 2" }
],
selectedOptionId: 1
}
},
computed: {
selectedOption () {
return _.find(this.options, (option) => {
return option.id === this.selectedOptionId
});
}
}
In the HTML file:
<select v-model="selectedOptionId">
<option v-for="option in options" :value="option.id" :key="option.id">{{ option.text }}<option>
</select>
The '_' symbol is a common JavaScript library called Lodash and I highly recommend the usage. It can make you save some precious time.
If you know your options will only come from that v-for="i in ideas" then the <option> indexes will be the same as the item indexes.
Thus <select>.selectedIndex will be the index of the selected this.item.
new Vue({
el: '#app',
data: {
ideas: [
{ id: 1, code: "01A", text: "option 1", props: [] },
{ id: 2, code: "02A", text: "option 2" , props: [{ details: "something" }]}
],
currentForm: {ideaCode: "01A", text: "option 1"}
},
methods: {
setCodeAndLabelForForm: function(selectedIndex) {
var selectedIdea = this.ideas[selectedIndex];
this.currentForm = {ideaCode: selectedIdea.code, text: selectedIdea.text};
}
}
})
<script src="https://unpkg.com/vue#2.5.13/dist/vue.js"></script>
<div id="app">
<select v-model="currentForm.ideaCode" #change="setCodeAndLabelForForm($event.target.selectedIndex)">
<option v-for="i in ideas" :value="i.code">{{ i.text }}</option>
</select>
<br> currentForm: {{ currentForm }}
</div>
Differences from yours: #change="setCodeAndLabelForForm($event.target.selectedIndex)" and the setCodeAndLabelForForm implementation.
A humble way is using $ref.
There is a solution using $ref and #change.
Vue.js get selected options' raw object
It's been a while since I asked this question and there have been good suggestions for handling this situation. I cannot remember the exact business-case presented here, but just by a quick glance it looks like I couldn't figure out how to set/initialize right selection afterwards, because handling just the #change event is childs play -- it's the pairing of one single value against list of object-based-options which is harder. What I was most likely looking for was something AngularJS used to have (track-by -property, which matches any given value against selected-option).
Personally now-a-days I would separate UI-logics instead of trying to force 'em to blend together. Most viable approach for myself would be handling list of options and the selected option as one logical area (ie. data: { list: [...options], selectedIdea: Object }) and separate the "currentForm"-object from selection. Let's break this out:
selectedIdea is something which needs to trigger change into currentForm-object. It's not any kind of hybrid-model, it's just a plain object, one of the available selections, pure and simple.
... and once again: Whenever selectedValue === one of the options, the select-dropdown is automatically set to right selection.
"currentForm"-object has a property which can be used to set the selectedIdea. In this case it's the "ideaCode". This ideaCode doesn't automatically do any pairing or such Component logic needs to represent the rules, which trigger selecting the correct option, which matches "ideaCode".
Just an extra-though: selectedIdea and currentForm-object are two different logical elements. They could be even separated to different components if one would want do, and in some cases it's really good thing to separate 'em.
So by these statements I guess I would change my select's v-model to be exactly what it's supposed to be: One of the selected objects (change v-model="currentForm.ideaCode" into v-model="selectedIdea" or such). Then I would simply add watcher for that selectedIdea and make any alterations to currentForm-object from there.
How about initializing that option by currentForm.ideaCode ? Do one of the following on create-method:
Iterate list of available options. When you find option where currentForm.ideaCode == option.code => this.selectedIdea = option
... or use ecmascript find-method to do the same
... or use underscore/lodash find-method to do the same
Another way would be by using computed value, as suggested Augusto Escobar. Also $ref would work, as suggested by feng zhang, but this approach would still require solution for initializing correct option afterwards (when loading editor with initial values). Thanks to Bhojendra Rauniyar as well -- you were right all along, but I just couldn't comprehend the answer as I couldn't have figured out how to backtrack initial selection.
Thanks for all the suggestions over the year!

V-for inside v-for in Vue.js

I'm trying to get object items from inside a parent object using a v-for inside another v-for in Vue.js.
Data structure:
flights: [
{
"airline": "BA",
"airport": "EBJ",
"status": {
"_code": "A",
"_time": "2018-03-02T14:19:00Z"
}
},
etc....
]
<div v-for="flight in flights">
<p>{{flight.airline}}</p>
<p>{{flight.airport}}</p>
<div v-for="st in flight.status">
<p>{{st._code}}</p> // should return 'A'
<p>{{st._time}}</p> // should return '2018-03-02T14:19:00Z'
</div>
</div>
However, neither st._code or st._time return anything. st returns both values ("A" and "2018-03-02T14:19:00Z").
Any idea on how to return the single values inside the status object?
It is possible to use v-for on an object, as you're trying to do with status, but the syntax is slightly different; in cases where iterating over an object is useful you'll generally want to include the key as well as the value:
<div v-for="(val, key) in flight.status">
<p>{{key}}: {{val}}</p>
</div>
would output
<p>_code: A</p>
<p>_time: 2018-03-02T14:19:00Z</p>
In your case you already know the specific keys you want, so it would be easier to not use v-for and instead just use e.g {{flight.status._code}}.
Unless there can be more than one "status" per flight, there's no good reason to wrap status in an array. This will work with your existing data structure:
<div v-for="flight in flights">
<p>{{flight.airline}}</p>
<p>{{flight.airport}}</p>
<p>{{flight.status._code}}</p>
<p>{{flight.status._time}}</p>
</div>
The reason you are not seeing the expected output is because, of this line:
<div v-for="st in flight.status">
That means you are expecting vue to iterated throught this:
"status": {
"_code": "A",
"_time": "2018-03-02T14:19:00Z"
}
and the above is an object, not an array ... so unless status is an array, it won't work.
If you expect your code to work, try changing your array to this:
flights: [
{
"airline": "BA",
"airport": "EBJ",
status: [{
"_code": "A",
"_time" : "2018-03-02T14:19:00Z"
}]
}
]
working demo:
https://jsfiddle.net/943bx5px/82/