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
Related
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.
I am struggling to figure out how to create a :key value for my v-for loop & I believe that is why the order of my list(s) are random.
This is what my template looks like:
<div v-for="(items, index) in documents">
<h1>{{ index }}</h1> // 2020-06-02
...
<table class="table">
<tr>
<th>Name</th>
</tr>
<tr v-for="(item, index) in items" :key="index">
<td>{{ item.name }}</td> // Foo
</tr>
</table>
</div>
My documents prop looks like this:
{
2020-06-01: ['name': 'Foo', ...],
2020-06-02: ['name': 'Bar', ...],
2020-06-03: ['name': 'Baz', ...],
}
My props are returning the documents in the correct order, but they are not shown on screen in the correct order. For example, I'll see 2020-06-03, 2020-06-01, 2020-06-02 or a different combination. I think it's because I don't have a :key so Vue doesn't know how to order things.
Since I am using the index in my outter loops, I'm not sure how to get a unique key.
What I think I need is something like:
<div v-for="(items, date, index) in documents" :key="index">...</div>
But, that's not really how it works.
I've also tried something like:
<div v-for="(items, index) in documents" :key="`i-${index}`">...</div>
No luck there either.
Thank you for any suggestions!
EDIT
Thank you for your feedback! Here's a few notes.
I'm getting documents from another source. I'm passing it is as props to my component. Currently, I'm using a computed property to try to alter the actual object; maybe add a key or something. No luck just yet...
Just as you've stated, it is coming in as an object. Each key of the object is a date. Each date, then has an array that I'm also looping through.
Here is what documents looks like (also added a screenshot). You can see that the dates are in order. The props are straight from my external service, so things look great so far.
{
2020-06-01: ['name': 'Foo', ...],
2020-06-02: ['name': 'Bar', ...],
2020-06-03: ['name': 'Baz', ...],
}
[
you can go with index2 or whatever else you would like
I tried making index be date just to help keep things straight and yeah.. I tried adding a third argument in the v-for just in case there was an unknown secret I could tap into (sadly no) :)
the format of your prop is messing me up mostly.
Same here, I'm just not able to understand why the order looks good in my props, but when looping through things, its pretty random.
show what you want the table to look like
This is the result I'm trying to accomplish.
<h2>2020-06-01</h2>. // this is the "missing" index (outer loop)
<table>
<tr>
<th>Name</th>
<th>Email</th>
</tr>
<tr> // this is the array that exists within each date
<td>Foo</td>
<td>foo#example.com</td>
</tr>
</table>
Edit/Solution
Thank you so much #Goofballtech! Your answer really helped me think about what I had and directions to try. Here's what I ended up with. sortedDocuments is a computed property; this.documents is a reference to the documents props that is getting passed in.
sortedDocuments() {
const data = this.documents;
return Object.keys(data)
.map((date) => {
return {
date: date,
data: data[date],
}
})
.sort(function (a, b) {
let dateA = new Date(a.date);
let dateB = new Date(b.date);
return dateA - dateB;
})
.reverse();
},
:key is used to watch for changes, not necessarily for order.
2 things.
if you want to maintain a specific order the general solution is to use an array. Order of object (although there is a bit of method to the madness) cannot necessarily be counted upon.
I believe you should used a computed property to modify these as you require then iterate over that array instead of using the object directly for what it seems you are trying to do. Will be back will an edit adding code in a moment.
Edit1 -
General information about indexes in v-for. The word index is not some magic keyword. If you have nested v-for you can do this:
<div v-for="(stuff, index) in stuffing">
display stuff here, index is your key
<div v-for="otherStuff, skdjdfhfyjsksj) in theOtherThings">
skdjdfhfyjsksj is your key value here
</div>
</div>
index is shown in examples to be clear but you can go with index2 or whatever else you would like there. It's getting passed as the second argument and that's where the magic happens.
Back to the base issue...So the format of your prop is messing me up mostly. Sorting the existing entries is straight forward but there really isn't a key:value array in javascript. Those are objects. So where are you getting the documents prop from originally? Is that something you are creating to pass down or is it coming that way from an external source?
sorting of the dates can be done like this in computed, this doesn't include the rest of the data yet as I'm not sure if you need to convert those on the fly or if we can change the prop before it gets here. I changed your document arrays to objects because the code would never show anything with that data formatted as an array as it complained about a missing ] since it did not expect the array key:value formatting.
https://jsfiddle.net/goofballtech/hmr6uw2a/65/
computed: {
sortedDates(){
return Object.keys(this.documents)
.sort((a,b) => a > b ? 1 : 0)
}
},
Final request for this edit, can you show what you want the table to look like? Having that context would help in finding the optimal data structure to achieve the results you need.
Edit2 -
Assuming the object can be an array of objects, and there is potential to have more than one name/email per date array. The below would work.
It all hinges on those funky arrays though.
Might be easier for you to copy the raw return from the API and just go in in to sterilize the data so i can see how the arrays are formatted.
https://jsfiddle.net/goofballtech/hmr6uw2a/141/
<div v-for="(docDate, index) in sortedDates" :key="index">
<h3>
{{docDate.date}}
</h3>
<table class="table">
<tr>
<th>Name</th>
<th>Email</th>
</tr>
<tr v-for="(item, index2) in docDate.data.length" :key="index2">
<td>{{documents[docDate.date][index2].name}}</td>
<td>{{documents[docDate.date][index2].email}}</td>
</tr>
</table>
</div>
I am pretty new to vue, and am trying to use it in a bootstrap modal. The relevant div in the modal is as follows.
<div class="form-group row">
<label for="priceQCField" class="col-sm-2 col-form-label">Price<span class="red"> *</span></label>
<input type="number" step="0.01" class="form-control col-sm-4" id="priceQCField" name="priceQCField" min="0" v-model="job.price">
</div>
I read some other questions about vue returning strings rather than numbers, so I have converted the job.price to a number inside my method to call the modal
showPriceJob: function (job) {
this.job = job;
this.job.price = parseFloat(this.job.price);
$('#mdlPriceJob').modal('show');
},
However, job.price refuses to appear in the input field either as a string or a number. I know it is available to the modal as I can see it using <span>{{job.price}}</span>.
Can anyone advise me please?
Additional - I think it is a display issue - if I change the input field, the entry in the <span> changes
2nd update - initial table
<tr class="light-grey" v-for="job in jobs" v-on:click="viewJob(job)">
<td>{{job.id}}</td>
<td>{{job.customerName}}</td>
<td>{{job.description}}</td>
<td v-bind:class="job.dueDate | dateColour">{{job.dueDate | dateOnly}}</td>
<td>£{{job.price}} {{job.isEstimate | priceEstimated}}</td>
<td>{{job.delivery}}</td>
</tr>
Upd.
According to your comments to my answer you are using v-for and you can't use this.job within your method. You should give us more code to see the whole picture.
Upd.2
You have showed more code but I didn't see any v-for so I am confused. You can try to use something like this if job is a property of appData.jobs:
showPriceJob: function (job) {
this.appData.jobs.job = Object.assign({}, job);
this.appData.jobs.job = parseFloat(this.appData.jobs.job.price);
$('#mdlPriceJob').modal('show');
},
But I'm not sure about this because I don't see where job is declared.
Upd.3
Oh! Wait! You have this code:
data: appData.jobs, but data should be in this format:
data: function(){
return {
appData: {
jobs: [],
},
}
},
Or show me what is your appData.jobs variable is.
I am building a simple quiz with vuejs. I've been working in https://codepen.io/jasonflaherty/pen/NBaJLO on this. I am able to get the correct responses to the checked input, however, it is not scoped to this.input and cascades to all radio buttons. I tried to use bind class with:
<span class="" v-bind:class="{ 'badge badge-success' : isCorrect, 'badge badge-danger' : isWrong, 'showthis' : showIt }">{{response.correct}}</span>
and am currently using:
v-if="isCorrect"
v-if="isWrong"
However, it is the same issue with the scope bound to the same input vs all of them.
What am I missing in vue to make this distinction?
You need to track at the question level rather than the global level.
Here are a couple simple changes you can make. Notice the input changes for value and v-model and also the conditions for the correct/wrong spans. This creates a property, selection on each question to track the currently selected answer for this specific question.
<li v-for="response in question.responses">
<label class="form-check-label">
<input class="form-check-input" type="radio"
:key="index"
:name="question.group"
:value="response.text"
v-model="question.selection">
{{response.text}}
<span v-if="question.selection === response.text && response.correct ==='Correct!' ">
Correct
</span>
<span v-else-if="question.selection === response.text && response.correct !=='Correct!' ">
Wrong
</span>
</label>
</li>
You could clean it up some more by altering your data model. There is no reason to save the correct text in the data. You can simply have the answer as a property and determine the textual response based on the selection.
var quizquestions = {
questions: [
{
text: "Subtract 219 from 500.",
group: "qone",
responses: [281, 719, 218, -219],
answer: 281, // correct answer
selection: null, // for v-model to track selected
},
]
};
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>