v-for : is there a way to get the key for the nested(second) loop besides "Object.keys(obj)[0]" in Vuejs? - vue.js

Here's the markup:
<ul>
<li v-for="(topic,label,index) in guides" :key="index">
<ul>
<strong> {{label}} </strong>
<li v-for="rule in topic">
{{rule.val}},
{{Object.keys(topic)[0]}}
</li>
</ul>
</li>
And here's the data for this list:
data: {
guides: {
"CSS" : {
1502983185472 : {
"modifiedby" : "bkokot",
"val" : "When adding new rule, use classes instead of ID whenever possible"
},
1502983192513 : {
"modifiedby" : "bkokot",
"val" : "Some other rule"
},
},
"Other" : {
1502628612513 : {
"modifiedby" : "dleon",
"val" : "Some test text"
},
1502982934236 : {
"modifiedby" : "bkokot",
"val" : "Another version of text"
},
}
}
}
So as you can see there is a "guides" property which is an object of other objects that do have inner objects too.
All I want is to get the keys from inner (second) loop (numbers "1502983185472" etc).
The only solution that i see right now is "Object.keys(topic)[0]", but is there a more accurate alternative in vuejs for this?
Adding key, index parameters to the second loop(with new unique variable names) doesn't work for me.
Here's a working fiddle: https://jsfiddle.net/thyla/yeuahvkc/1/
Please share your thoughts.
If there is no good solution for this - may it be a nice topic for a feature request in Vuejs repo(unless I'm missing something terrible here)?
Generally if you're curious - that number is a momentjs timestamp - I'm using this data in firebase, and saving initial timestamp as an object key seemed to be a pretty nice solution (since we need a key anyway, to save some space - I can use this key instead of another extra timestamp property in my object, this also makes the instance very 'targetable' in firebase).
Thank you in advance ! Cheers!
p.s: another possible solution is converting inner loop (css, other) from objects into arrays and using time-stamp as another object property, but I'm using firebase - saving this data as an object gives me an ability to quickly access some instance without parsing the entire parent object/array, makes it more easy to filter, search, reupdate, etc - thus converting object into array is not a good solution for instance with very large number of items.

Your fiddle renders the number key of the first entry of a topic for each of the rules in that topic. I'm assuming you want to actually show the number key for each corresponding rule.
That value is passed as the second parameter in the v-for:
<li v-for="(rule, ruleID) in topic">
{{ rule.val }},
{{ ruleID }}
</li>
Here's a working fiddle.
Here's the documentation on using v-for with an object.

This can be solved by as follows in the second loop like
<li v-for="(rule,index) in topic">
{{rule.val}},
{{index}}
</li>
Please refer this fiddle => https://jsfiddle.net/yeuahvkc/7/

Use explicit definitions
Other answers use ".val", but it's not clear where that originates.
Instead, just declare everything, like:
<li v-for="(value, key, index) in rule">
{{key}}: {{value}} - {{index}}
</li>

Related

Any problems with defining variables inside v-for?

AFAIK, first shown here by Vladimir Milosevic
<div
v-for="( id, index, user=usersData[id], doubleId=doubleThat(id) ) in users"
:key="id">
{{ user.name }}, {{ user.age }} years old And DoubleId = {{ doubleId }}
</div>
I expanded his codepen. Looks like you can define several variables local to the loop this way.
Are there any side-effects or risks you see with this technique?
I find this way to be the most convenient and clean out there but since that is apparently not the intended use it may not be future-proof.
EDIT:
Here is another pen which demonstrates the use case better. While here we reference just one computed property in one place, if the number of properties and references grows, it will lead to a verbose code.
In general, I think the clean way to do what you want to do is to bring the data into a format, where you can loop over it without extra steps. This way, you will never need those additional variables in v-for.
The example from your codepen could look like this:
const app = new Vue({
el: '#app',
data: {
years: [2024, 2025, 2120],
usersData: {...}
},
computed: {
// user objects for each year
ageData() {
return Object.fromEntries(this.years.map(year => [year,
Object.values(this.usersData).map(user => this.userInYear(user, year))
] ))
}
},
methods: {
userInYear(user, year){
return {...user, age: user.age + (year - 2023)}
}
},
});
Now you can iterate it without interruption and shenanigans:
<div v-for="(users, year) in ageData" :key="year">
<b>In {{ year }}:</b>
<div v-for="user in users" :key="user.name">
{{ user.name }} will be {{ user.age }} years old.
</div>
</div>
It is easier to read and change, and it has the added benefit that you can inspect ageData in the console.
That said, I think it is very interesting that you can declare additional variables in v-for, I assume it is possible because of the way vue parses and builds expressions from the provided string, I am pretty sure it won't be removed, and if you and the people you work with are comfortable with it (and are prepared to hear the above over and over like just now), by all means, go for it.

How to read json object in vuejs?

Following is my json object:
const faqs = [{'question1':'answer1'},{'question2':'answer2'}]
I am reading this in vuejs in for loop in div as
<div v-for="(value, name, index) in faqs" :key="index">{{ value}}::{{ name }}</div>
but I not getting the required result.
Please guide me what am I doing wrong?
Following is the console log:
Thank you,
Trupti
In this cycle you are iterating over the faqs array, that means, it goes over each contained object one by one. The cycle does not go into objects, for that, you need separate cycle.
In addition to that, your data are not well formed, try to transform them to following:
[{ question: "question 1", answer: "answer 1"}, {question: "question 2", answer: "answer 2"}]
After that you can do:
<div v-for="(value, index) in faqs" :key="index">{{ value.question}}::{{ value.answer }}</div>
This will yield result:
question 1::answer 1
question 2::answer 2
On the other hand if you really want to iterate over properties of contained objects, then you need to do something like:
<div v-for="(value, index) in faqs" :key="index">
<div v-for="(propValue, propName) in value">
{{propName}}::{{propValue}}
</div>
This will work for the data in the original form you have posted. You have first cycle, which goes through each object (question-answer pair) one by one and second nested cycle, which goes over properties of every object.

Using counter flag in v-for loop

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

Is there a way to bind a variable number of queries?

I'm coding an app for managing shift work. The idea is pretty simple: the team is shared between groups. In those groups are specific shifts. I want to get something like that:
Group 1
- shift11
- shift12
- shift13
Group 2
- shift21
- shift22
- shift23
I already made a couple of tests, but nothing is really working as I would like it to: everything reactive, and dynamic.
I'm using vue.js, firestore (and vuefire between them).
I created a collection "shiftGroup" with documents (with auto IDs) having fields "name" and "order" (to rearrange the display order) and another collection "shift" with documents (still auto IDs) having fields "name", "order" (again to rearrange the display order, inside the group) and "group" (the ID of the corresponding shiftGroup.)
I had also tried with firestore.References of shifts in groups, that's when I was the closest to my goal, but then I was stuck when trying to sort shifts inside groups.
Anyway, with vuefire, I can easily bind shiftGroup like this:
{
data () {
return {
shiftGroup: [], // to initialize
}
},
firestore () {
return {
shiftGroup: db.collection('shiftGroup').orderBy('order'),
}
},
}
Then display the groups like this:
<ul>
<li v-for="(group, idx) in shiftGroup" :key="idx">{{group.name}}</li>
</ul>
So now time to add the shifts...
I thought I could get a reactive array of shifts for each of the groups, like that:
{
db.collection('shift').where('group', '==', group.id).orderBy('order').onSnapshot((querySnapshot) => {
this.shiftCollections[group.id] = [];
querySnapshot.forEach((doc) => {
this.shiftCollections[group.id].push(doc.data());
});
});
}
then I'd call the proper list like this:
<ul>
<li v-for="(group, idx) in shiftGroup" :key="idx">
{{group.name}}
<ul>
<li v-for="(shift, idx2) in shiftCollections[group.id]" :key="idx1+idx2">{{shift.name}}</li>
</ul>
</li>
</ul>
This is very bad code, and actually, the more I think about it, the more I think that it's just impossible to achieve.
Of course I thought of using programmatic binding like explained in the official doc:
this.$bind('documents', documents.where('creator', '==', this.id)).then(
But the first argument has to be a string whereas I need to work with dynamic data.
If anyone could suggest me a way to obtain what I described.
Thank you all very much
So I realize this is an old question, but it was in important use case for an app I am working on as well. That is, I would like to have an object with an arbitrary number of keys, each of which is bound to a Firestore document.
The solution I came up with is based off looking at the walkGet code in shared.ts. Basically, you use . notation when calling $bind. Each dot will reference a nested property. For example, binding to docs.123 will bind to docs['123']. So something along the lines of the following should work
export default {
name: "component",
data: function () {
return {
docs: {},
indices: [],
}
},
watch: {
indices: function (value) {
value.forEach(idx => this.$bind(`docs.${idx}`, db.doc(idx)))
}
}
}
In this example, the docs object has keys bound to Firestore documents and the reactivity works.
One issue that I'm trying to work through is whether you can also watch indices to get updates if any of the documents changes. Right now, I've observed that changes to the Firestore documents won't trigger a call to any watchers of indices. I presume this is related to Vue's reactivity, but I'm not sure.

Vue - altering model arrays

I have a simple Vue template consisting of an object with two arrays (dummy/placeholders vs actual data).
data() {
var tableColumns = new Array();
tableColumns.push({"dummyValues": ["date 1", "date 2"], "csvValues": []});
var variables = {
"tableColumns": tableColumns
};
return variables;
}
<td v-for="(item, key, index) in tableColumns">
<span v-if="item.csvValues.length == 0" v-for="dummyValue in item.dummyValues">
{{dummyValue}} <br />
</span>
<span v-else v-for="value in item.csvValues">
{{value}} <br />
</span>
</td>
At first, I only have dummy values. Something happens along the way (I parse a CSV file) and I need to exchange the dummy data I've first rendered with actual values.
I thus append my real data to the real data array (csvValues) and hope the v-else will take care of it.
This doesn't work - why, is my approach wrong?
Apparently, Vue can't switch between two data sources (dummyValues and csvValues).
I had to use a third one, which now holds/cycles elements from either of the first two. So, instead of building my view from one or the other arrays, I'm popping/pushing stuff to my third array (renderingValues) and always iterating that one.
Want to render dummyValues?
- pop everything (previously csvValues) from renderingValues
- push everything from dummyValues to renderingValues
- iterate renderingValues