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/
Related
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>
I want to use a counter flag in v-for inside another v-for loop for counting total run of inside loop.
Here is my template:
<a :href="'#/product/'+list.id" :id="ikeyCounter" v-for="item,ikey in section.list" class="movie item fcosuable">
{{ ikeyCounterPlus() }}
<div class="verticalImage">
<div class="loader hideloading"></div>
<img :src="item.thumb" alt="">
</div>
</a>
data() {
return {
loading: true,
status: null,
list: [],
sections: null,
ikeyCounter: 3
}
},
And method:
ikeyCounterPlus() {
this.ikeyCounter++;
},
but I'm getting wrong result on ikeyCounter variable. Id of a tag started from "15003" to "15150", if I don't call ikeyCounterPlus() inside v-for tag, for loop will run correctly (150 run)
If you want to count your objects, then just count your data. No need to involve DOM.
section.list is an array, so section.list.length should give you desired count.
Also, as mentioned in the answer before, use some unique property of item (for example some sort of id) as the value for :key attribute.
You can't do it like this, Vue.js is reactive framework and you should learn a little bit before asking these kind of questions - https://v2.vuejs.org/v2/guide/reactivity.html
Use your key as id instead
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.
I have a list of objects that can be updated from the database.
So, when I load the list, objects have only id and name.
When I click on an object I load other fields that can be of any length - that's why I don't load them with the objects in the list.
I found that when I update an object it can be difficult to keep reactivity https://v2.vuejs.org/v2/guide/reactivity.html so I need to find some workaround.
this code works almost okay:
axios.get('/api/news', item.id).then(function(response){
if (response){
Object.assign(item, response.data.item);
}
});
But the problem is the fields that have not been presented from the beginning is not 100% reactive anymore. What is going on is a new field has been updated reactively only when I change another, previous one. So, if I show 2 text field with old and new properties, if I change the second property, the field will not be updated until I change the first one.
I got item object from the component:
data () {
return {
items: [],
}
},
and
<div v-for="item in items" #click="selectItem(item)" >
<span>{{item.name}}</span>
</div>
Then item's been passed to the function selectItem.
What is the proper pattern to load new fields and keep them reactive? (NB: it's not the case when I can assign field by field - I want to reuse the same code no matter which object it is, so I need so the solution for updating an object at a time without listing all new fields.)
Note. This code works inside the component.
Completely revised post: Ok, the example you give uses an array, which has its own caveats, in particular that you can't directly set values like vm.items[indexOfItem] = newValue and have it react.
So you have to use Vue.set with the array as the first argument and the index as the second. Here's an example that adds a name property to object items and then uses Vue.set to set the item to a new object created by Object.assign.
new Vue({
el: '#app',
data: {
items: [{
id: 1,
other: 'data'
}, {
id: 2,
other: 'thingy'
}]
},
methods: {
selectItem(parent, key) {
const newObj = Object.assign({}, parent[key], {
name: 'some new name'
});
Vue.set(parent, key, newObj);
setTimeout(() => {parent[key].name = 'Look, reactive!';}, 1500);
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div v-for="item, index in items" #click="selectItem(items, index)">
<span>{{item.name || 'empty'}}</span>
</div>
<pre>
{{JSON.stringify(items, null, 2)}}
</pre>
</div>
Have a look at Change-Detection-Caveats Vue cannot detect property addition or deletion if you use "normal assign" methods.
You must use Vue.set(object, key, value)
Try something like the following:
axios.get('/api/news', item.id).then(function(response){
if (response){
let item = {}
Vue.set(item, 'data', response.data.item)
}
});
Than item.data would than be reactiv.
Can simply use Vue.set to update this.item reactively
axios.get('/api/news', item.id).then(function(response){
if (response){
this.$set(this, "item", response.data.item);
}
});
Can I scope data somehow within a component's template?
For example, if I have the following code:
data() {
a: {test: 'Test A'},
b: {test: 'Test B'}
}
Currently in the template I have to do
<div class="a">{{ a.test }}</div>
<div class="b">{{ b.test }}</div>
Is there any way I can scope data per element? For example, something like:
<div :scope="a">{{ test }}</div><!-- This prints 'Test A' -->
<div :scope="b">{{ test }}</div><!-- This prints 'Test B' -->
I do know that I can extract each item to a component, however, I was wondering if there is a way to do that within the same template? As it does not have own logic etc. so I don't want to extract it to a separate component just to scope the variable. However, it can get tedious repeating the same variable name many times.
For example, I have a form to create a new item, which has a number of inputs. I keep them under a variable (for example) newItem, which looks like
newItem: {
input1: "",
input2: "",
input3: null,
input4: false,
// etc...
}
And in the template I would like to do
<div :scope="newItem">
<input v-model="input1"/>
<!-- etc.. --->
</div>
Instead of
<input v-model="newItem.input1"/>
<!--- etc... --->
NO.
There's no such a way to do. And also v-model needs to be specified to the particular data else it will not work. Otherwise, we can think of v-for.