Vue how to send index from v-for to method - javascript

I'm trying to pass the index of a v-for so i can change some values, i'm adding the doctor team members dynamically and i have this file input to add their image, although when i add doctors it only changes the first's one image because it always sends index of the first doctor, the file input is inside the v-for loop if that helps.
<v-file-input
hide-input
class="d-none"
id="doctorImage"
truncate-length="1"
#change="doctorImage($event, index, doctor)"
></v-file-input>
Method:
doctorImage(e, index, doctor) {
console.log(index)
this.doctors[index] = {
image: URL.createObjectURL(e),
imageData: e,
name: doctor.name,
specialty: doctor.specialty
}
this.doctorChange += 1
}
The index is always 0, although it displays the index number when i create it dynamically on the doctor's card, why is that happening?

Refer List Rendering using v-for.
<div ... v-for="(doctor, index) in doctors" ... >
{{ index }} // you can use index
...
</div>

Ok so i fixed the issue, that happened because i had a label for doctorImage and the file input id was doctorImage also inside the v-for statement, that made multiple same id's and created a conflict.

I iterate with a limit. Example
If the data were:
{
limit: 3,
currentIndex: 2,
listData:[{name:'Data1'},{name:'Data2'},{name:'Data3'},{name:'Data4'},{name:'Data5'}]
}
In the .vue file it would be the following
<span v-for="index in limit" v-bind:key="index">
{{listData[index+currentIndex].name}}
</span>
I make sure that you control that the sum index + currentIndex does not exceed the limit of your data

Related

Can I make an attribute appear only once in a vue v-for

I have an array of people with associated teams. I want to display all the people in the record, but I only want to display their team name once.
Meaning, if the v-for loop has encountered this particular team name, it should put it in a new temporary array to signify that it should be unique, then when it encounters that team name again, checks it through that temporary array and prevent it from showing again.
Sample HTML Code:
<div id="a-list">
<div v-for="person in people">{{person.Name}}, {{person.Team}}</div>
</div>
Sample Vue Code:
var crew = new Vue({
el: "#a-list",
data: {
people:
[ { "Name": "Richard","Team":"DMS"}, { "Name": "Mark","Team":"VV"}, { "Name": "Steve","Team":"VV"}, {"Name":"Koji","Team":"MZ"}, {"Name":"Jamie","Team":"VV"} ]
}
});
Expected Output:
Richard, DMS
Mark, VV
Steve,
Koji, MZ
Jaimie,
Is this possible to do directly from the v-for loop and not in the JS file?
Edited to show more data that are not sequential
Update: As Fabio has pointed out, the above scenario wouldn't make much sense unless the order of the team is arranged sequentially in the output first. So his answer is correct.
This could be a solution:
<div id="a-list">
<div v-for="(person,index) in people"> {{person.Name}}, {{ ((index == 0) || person.Team != people[index-1].Team) ? person.Team : '' }}</div>
</div>

How to define a temporary variable in Vue.js template

Here is my current template:
<a-droppable v-for="n in curSize" :key="n - 1" :style="{width: `${99.99 / rowLenMap[orderList[n - 1]]}%`, order: orderList[n - 1]}">
<a-draggable :class="{thin: rowLenMap[orderList[n - 1]] > 10}">
<some-inner-element>{{rowLenMap[orderList[n - 1]]}}</some-inner-element>
</a-draggable>
</a-droppable>
The problem is that i have to write rowLenMap[orderList[n - 1]] multiple times, and i'm afraid vue.js engine will also calculate it multiple times.
What i want is something like this:
<a-droppable v-for="n in curSize" :key="n - 1" v-define="rowLenMap[orderList[n - 1]] as rowLen" :style="{width: `${99.99 / rowLen}%`, order: orderList[n - 1]}">
<a-draggable :class="{thin: rowLen > 10}">
<some-inner-element>{{rowLen}}</some-inner-element>
</a-draggable>
</a-droppable>
I think it's not difficult to implement technically because it can be clumsily solved by using something like v-for="rowLen in [rowLenMap[orderList[n - 1]]]". So is there any concise and official solution?
I found a very simple (almost magical) way to achieve that,
All it does is define an inline (local) variable with the value you want to use multiple times:
<li v-for="id in users" :key="id" :set="user = getUser(id)">
<img :src="user.avatar" />
{{ user.name }}
{{ user.homepage }}
</li>
Note : set is not a special prop in Vuejs, it's just used as a placeholder for our variable definition.
Source: https://dev.to/pbastowski/comment/7fc9
CodePen: https://codepen.io/mmghv/pen/dBqGjM
Update : Based on comments from #vir us
This doesn't work with events, for example #click="showUser(user)" will not pass the correct user, rather it will always be the last evaluated user, that's because the user temp variable will get re-used and replaced on every circle of the loop.
So this solution is only perfect for template rendering because if component needs re-render, it will re-evaluate the variable again.
But if you really need to use it with events (although not advisable), you need to define an outer array to hold multiple variables at the same time :
<ul :set="tmpUsers = []">
<li v-for="(id, i) in users" :key="id" :set="tmpUsers[i] = getUser(id)" #click="showUser(tmpUsers[i])">
<img :src="tmpUsers[i].avatar" />
{{ tmpUsers[i].name }}
{{ tmpUsers[i].homepage }}
</li>
</ul>
https://codepen.io/mmghv/pen/zYvbPKv
credits : #vir us
Although it doesn't make sense here to basically duplicate the users array, this could be handy in other situations where you need to call expensive functions to get the data, but I would argue you're better off using computed property to build the array then.
Judging by your template, you're probably best off with a computed property, as suggested in the accepted answer.
However, since the question title is a bit broader (and comes up pretty high on Google for "variables in Vue templates"), I'll try to provide a more generic answer.
Especially if you don't need every item of an array transformed, a computed property can be kind of a waste. A child component may also be overkill, in particular if it's really small (which would make it 20% template, 20% logic and 60% props definition boilerplate).
A pretty straightforward approach I like to use is a small helper component (let's call it <Pass>):
const Pass = {
render() {
return this.$scopedSlots.default(this.$attrs)
}
}
Now we can write your component like this:
<Pass v-for="n in curSize" :key="n - 1" :rowLen="rowLenMap[orderList[n - 1]]" v-slot="{ rowLen }">
<a-droppable :style="{width: `${99.99 / rowLen}%`, order: orderList[n - 1]}">
<a-draggable :class="{thin: rowLen > 10}">
<some-inner-element>{{rowLen}}</some-inner-element>
</a-draggable>
</a-droppable>
</Pass>
<Pass> works by creating a scoped slot. Read more about scoped slots on the Vue.js documentation or about the approach above in the dev.to article I wrote on the topic.
Appendix: Vue 3
Vue 3 has a slightly different approach to slots. First, the <Pass> component source code needs to be adjusted like this:
const Pass = {
render() {
return this.$slots.default(this.$attrs)
}
}
Today I needed this and used <template> tag and v-for like this
I took this code and
<ul>
<li v-for="key in keys"
v-if="complexComputation(key) && complexComputation(key).isAuthorized">
{{complexComputation(key).name}}
</li>
</ul>
Changed it to this
<ul>
<template v-for="key in keys">
<li v-for="complexObject in [complexComputation(key)]"
v-if="complexObject && complexObject.isAuthorized">
{{complexObject.name}}
</li>
</template>
</ul>
And it worked and I was pleasantly surprised because I didn't know this was possible
This seems like the perfect use case of a child component. You can simply pass your complex computed value(s) as a property to the component.
https://v2.vuejs.org/v2/guide/components.html#Passing-Data-to-Child-Components-with-Props
How about this:
<div id="app">
<div
v-for="( id, index, user=getUser(id) ) in users"
:key="id"
>
{{ user.name }}, {{ user.age }} years old
<span #click="show(user)">| Click to Show {{user.name}} |</span>
</div>
</div>
CodePen: https://codepen.io/Vladimir-Miloevi/pen/xxJZKKx
<template>
<div>
<div v-for="item in itemsList" :key="item.id">
{{ item.name }}
<input v-model="item.description" type="text" />
<button type="button" #click="exampleClick(item.id, item.description)">
Click
</button>
</div>
</div>
</template>
<script>
export default {
data() {
return {
items: [
{
id: 1,
name: 'Name1',
},
{
id: 2,
name: 'Name2',
},
],
}
},
computed: {
itemsList() {
return this.items.map((item) => {
return Object.assign(item, { description: '' })
})
},
},
methods: {
exampleClick(id, description) {
alert(JSON.stringify({ id, description }))
},
},
}
</script>
Just tested using vue3 and works, i think it works universally
{{ (somevariable = 'asdf', null) }}
<span v-if="somevariable=='asdf'">Yey</span>
<span v-else>Ney</span>
It outputs nothing while setting your variable.
mandatory:
opening "("
set your variable
closing ", null)"
curSize is an array. Your temporary values comprise a corresponding implied array sizedOrderList = curSize.map(n => orderList[n-1]). If you define that as a computed, your HTML becomes
<a-droppable v-for="n, index in sizedOrderList" :key="curSize[index]" :style="{width: `${99.99 / rowLenMap[n]}%`, order: n}">
<a-draggable :class="{thin: rowLenMap[n] > 10}">
<some-inner-element>{{rowLenMap[n]}}</some-inner-element>
</a-draggable>
</a-droppable>

Vue list items not re-rendered on state change

I have some array of object, when user click button I fetch new array and display display some results.
It works fine until I fetch second array. When I fetch first array with one element and then fetch array with two elements it change (add or remove) only second element.
How I change array value:
fetchAsync(result){
this.issue = result.body;
}
How issues looks like?
const issues = [
{
"id":100,
"key":"DEMO-123",
"summary":"Demo issue description",
"devices":[
{
"id":100,
"name":"iPhone6S",
"browsers":[
{
"id":100,
"name":"Safari",
"displayVariants":[
{
"id":100,
"issueKey":"DEMO-123",
"state":1,
"browserName":"safari",
"user":"user-1",
"landScope":false
}
]
}
]
}
]
}
]
and the value which was changed is issues[].devices[].browsers[].displayVariants[].state
How to force Vue to rerender this component when nested change appear?
[ EDIT ]
I render issues like this:
<tr v-for="issue in issues">
<td>
<div>{{ issue.key }}</div>
<div class="issue-description">[ {{ issue.summary }} ]</div>
</td>
<template v-for="d in issue.devices">
<td v-for="browser in d.browsers">
<!--{{ d }}-->
<device v-for="variant in browser.displayVariants"
:variant="variant"
:browserId="browser.id"
:issueKey="issue.key"
:issueId="issue.id"
:deviceId="d.id"></device>
</td>
</template>
</tr>
and device template
<template>
<svg viewBox="0 0 30 30" class="mobileSVG" #click="changeState" :class="[state, {landscape: variant.landScope}]">
<use xlink:href="#mobile"/>
</svg>
</template>
I think adding keys to your list will solve the problem:
https://v2.vuejs.org/v2/guide/list.html#key
Vue tries to make minimum changes to the DOM, and think that the first item has not changed, so it is not re-rendered. In your case you already have the id, using that as key should solve the issue.
Vue cannot detect the following changes made to the array.
Here is the documentation.
When you directly set an item with the index, e.g.
vm.items[indexOfItem] = newValue
When you modify the length of the array, e.g. vm.items.length = newLength
vm refers to component instance.
To overcome the limitation 1 do:
Vue.set(items, indexOfItem, newValue)
For limitation 2:
items.splice(newLength)
So in your case you could do
this.$set(this.issues[0].devices[whateverIndex].browsers[anyIndex].displayVariants, indexOfVariant, valueOfVariant)

How do I pass the current item in a for loop to a method in vue.js 2?

I am building (as an exercise) a shopping cart in vue.js 2. I have my shop items and order items stored in my vue data array and a button rendered in a for loop for each shop item to add the item to the order (ex. push).
Here is the section of code that houses my list of items from my shop array in my vue data:
<fieldset>
<legend>Shop</legend>
<section v-if="shop">
<article v-for="(item, index) in shop">
<header><h1>{{ item.title }}</h1></header>
<p>{{ item.description }}</p>
<footer>
<ul>
<li>${{item.price}}</li>
<!-- find a way to set input name -->
<li><label>Quantity <input type="number" name=""></label></li>
<li><button v-on:click="addItemToOrder($event)">Add to Order</button></li>
</ul>
</footer>
</article>
</section>
<p v-else>No Items to Display</p>
</fieldset>
here is my vue element:
new Vue({
el: '#my-order',
data:{
'shop':[
{
'title':'Website Content',
'description':"Order your Website content by the page. Our search-engine-optimized web content puts you ahead of the competition. 250 words.",
'price':25,
'sku':'web001'
},
{
'title':'Blog Post',
'description':"We write blog posts that position your website as a go-to resource for expert knowlegde.",
'price':50,
'sku':'blog001'
},
{
'title':'Twitter Post'
},
{
'title':'Product Description'
}
],
'customizations':null,
'order':{
'items':null,
'total':null
},
'customer':null,
'payment':null
},
methods:{
addItemToOrder: function(){
/* Here is where I need to append the item to the cart */
}
}
})
How do I pass the item in the for loop to the order (eg: append it to order.items)?
You just need to pass the item in as a parameter to the function.
v-on:click="addItemToOrder(item)"
Then you can use it your Vue component
addItemToOrder: function(item){
this.order.items.push(item);
}
Make sure you initialize order.items to an empty array inside your components data so that you can push to it.
'order':{
'items': [],
'total': 0
},
In general, it is a good idea to initialize your data to the correct data type if you know what it will be.
I realise this is a bit late however in case anyone else happens across this thread...
You need to pass in the event as well as the item
in your vue code
someMethod : function(e, item){}
in your html
<a v-on:click="someMethod($event, $data)"></a>

Vue.js -- How to prevent data modification between components

I must say I'm still pretty new to Vue.js.
I build a Point of Sale system with the help of this tutorial.
I have made some modifications to it, for example the items are
fetched with an api call and you now can edit the price of an item in the transaction. It also required some fixing after updating to latest vue version, for example v-repeat to v-for etc.
Now I have a problem that is if you edit the price of an item in the transaction
it also changes the price of the item in the item list where you click to add the items.
Due to reasons I can't provide a whole image of the app or large parts of code.
Here's a screencap trying to explain what's happening
In the item list template the items are rendered from items array with v-for.
In the transaction template the items are rendered from itemList array with v-for.To clarify: itemList array contains the items added to the transaction.
I can't understand why this is happening or better yet how to prevent it. I can consider providing parts of the code if you need it.
EDIT:
In the comments Hector guessed that I'm referencing the same array in both of the templates, but that is not the case.
In the item list component I reference the items array by passing a prop.
<item-list :items="items" :add="onItemClick"></item-list>
In the transaction component I reference the lineItems array by passing a prop.
<transaction :items="lineItems" :edit-number-of-items="editNumberOfItems" :edit-price-each="editPriceEach" :edit-total-qty="editTotalQty" :edit-taxed-price="editTaxedPrice" :editing="editing" :remove-item="removeItem"></transaction>
The first three cells of the transaction table:
<tbody>
<tr v-for="item in items">
<!-- Code -->
<td>{{ item.item.id }}</td>
<!-- Name -->
<td>
<span>{{ item.item.name }}</span>
</td>
<!-- Price Each -->
<td>
<span class="editable-item" v-if="!item.editingPriceEach" #click="editPriceEach(item)">{{ item.item.priceEach }}</span>
<input v-if="item.editingPriceEach" #keyup.enter="editPriceEach(item)" type="text" v-model="item.item.priceEach" autofocus>
<button v-if="item.editingPriceEach" #click="editPriceEach(item)">OK</button>
</td>
In <tr v-for="item in items"> items is referring to the prop name items not the array called items in the data option.
Also for example the function editPriceEach passed to the transaction component as a prop looks like this:
editPriceEach: function (lineItem) {
lineItem.editingPriceEach = !lineItem.editingPriceEach;
}
As you can see it triggers the editingPriceEach property which is then used in the onItemClick function. It's used to add the items from the item list to the transaction:
onItemClick: function (item) {
var found = false;
for (var i = 0; i < this.lineItems.length; i++) {
if (this.lineItems[i].item === item) {
this.lineItems[i].numberOfItems++;
found = true;
break;
}
}
if (!found) {
this.lineItems.push({
item: item,
numberOfItems: 1,
editingNumberOfItems: false,
editingName: false,
editingPriceEach: false,
editingTaxedPrice: false,
editingTotalQty: false
});
}
},
Thank you for your patience with the small amount of code.

Categories