Given the following looping component:
<MyComponent v-for="(item, i) in items" :key="i" :item="item" />
How do I get the value of key within MyComponent obviously key is a reserved word so it fails to work as a prop.
Pass it as a declared prop (e.g: index):
<MyComponent v-for="(item, i) in items" :key="i" :item="item" :index="i" />
MyComponent.vue:
export default {
props: {
index: Number
}
}
Side note: it's recommended you key lists using a unique identifier (e.g: id) rather than based on their position in the list. For example, if any items get replaced or updated, Vue might not re-render, because the items' position in the array has not changed, even if the item itself has.
This is why list transitions don't work when you key items by index.
However, if your items don't change position and/or do not get updated, index as key does the job.
Related
I have a transition group that render a list of element, i would like to add a component at the end as last list item. The last item is added via slot.
Everything works fine but i get a warning that says "TransitionGroup children must be keyed." I triend to put key attribute on the slot or directly on the component that use it but the warning still remain, any idea?
here my component:
<template>
<TransitionGroup name="list">
<CategoryListItem v-for="item in items" :key="item.id" />
<slot :key="items.length + 1"></slot>
</TransitionGroup>
</template>
how can i programmately assign a key to the component that will be added to the list via slot?
What or where is individual id or any other element that I can call to run a function on component.
Example: using add button I'm dynamically creating colored squares. Each square has close button.
Then I want to delete one of squares, let's say the blue one (third child of template).
v-bind:id='var' doesn't works because then all squares have the same id.
You can use ref, you can define ref in every added component. The ref is array. you can refrence component through index of the ref.
for example;<div v-for="(item) in arr" :key="item.id"> <span ref="testRef"> item.name </span> </div>, you can use 'this.$refs.testRef[index]' find component which you want.
There are multiple options. For instance, in your v-for loop you can get the index this way: (official docs)
<div v-for="(item, index) in myList" :key="index">
{{ item.name }}
</div>
You could then for example use the index in a ref to access the element for the script.
Another option is to pass the event instance to the onclick listener with the special keyword $event, and use this to get the element:
<div ... #click="myFunction($event)">
{{ item.name }}
</div>
And then in your script
myFunction(ev) {
// do something with the element that was clicked on
console.log(ev.target)
}
However, a more 'vue' way would be to not mess with the elements, but only manipulate the underlying data and let vue worry about representing the data with elements on your page. For instance, you could make your squares be represented by a list of objects, containing their own data own the state (opened or closed). So in your script you have:
data() {
return {
squares: [
{ color: "ff0000", opened: true },
...
]
}
},
And in your template something like
<div
v-for="square in squares"
v-show="suare.opened"
#click="square.opened = false"
> ...
I am working on a Vue app made with element UI and I have a bunch of el-select whose el-options need to have default values, meaning the form select fields already have one option pre-selected (of course the user can still choose other options).
I cannot find any attribute in the official doc https://element.eleme.io/#/en-US/component/select#select
But there should be a way to achieve this right?
This is my code
<el-form-item label="some label" prop="someprop">
<el-select v-model="form.status" filterable clearable>
<el-option
v-for="(item, index) in config.status"
:key="index"
:label="item"
:value="index"
how to have default option here??
>
</el-option>
</el-select>
</el-form-item>
Just put in the form.status the indexes that should be pre-selected. Vue will take care of the rest.
data(){
return {
form: { status: ['thisWillBePreSelected'], },
}
}
I created a component that shows table data for various pages. That component uses b-table inside. Now for a couple pages I want to customize rendering of some columns, and Bootstrap Tables allow that using scoped field slots with special syntax:
<template #cell(field)="data">
{{ data.item.value }}
</template>
where field - column name, coming from my array with columns, and data.item - cell item to be rendered.
The problem is that I have different fields for different pages, so this customization should come from parent component, and these templates should be created dynamically.
Here is how I tried to solve it:
Pass via property to MyTableComponent an array with customizable fields and unique slot names
In MyTableComponent dynamically create templates for customization, and inside dynamically create named slots
From parent pass slot data to named slots
MyTableComponent:
<b-table>
<template v-for="slot in myslots" v-bind="cellAttributes(slot)">
<slot :name="slot.name"></slot>
</template>
</b-table>
<script>
...
computed: {
cellAttributes(slot) {
return ['#cell(' + slot.field + ')="data"'];
}
}
...
</script>
Parent:
<MyTableComponent :myslots="myslots" :items="items" :fields="fields">
<template slot="customSlot1">
Hello1
</template>
<template slot="customSlot1">
Hello2
</template>
</MyTableComponent>
<script>
...
items: [...my items...],
fields: [...my columns...],
myslots: [
{ field: "field1", name: "customSlot1" },
{ field: "field2", name: "customSlot2" }
]
...
</script>
Unfortunately, b-table component just ignores my custom slots like if they are not provided. It works if I specify in the MyTableComponent it directly:
<b-table>
<template #cell(field1)="data">
{{ data.item.value }}
</template>
</b-table>
But I need it to be done dynamically via component properties. Please help.
You can use Dynamic Slot Names feature of Vue 2 to pass all (or some) slots from parent to <b-table> inside child like this:
Child:
<b-table>
<template v-for="(_, slotName) of $scopedSlots" v-slot:[slotName]="scope">
<slot :name="slotName" v-bind="scope"/>
</template>
</b-table>
$scopedSlots contains all slots passed to your component.
Now this will work:
<MyTableComponent :items="items" :fields="fields">
<template #cell(field1)="data">
{{ data.item.value }}
</template>
</ MyTableComponent>
UPDATE 2 - Vue 3
To make above code work in Vue 3, just replace $scopedSlots with $slots as suggested by migration guide
UPDATE 1
You can filter $scopedSlots if you want (have some slot specific to your wrapper component you don't want to pass down to <b-table>) by creating computed
I mentioned this possibility in my original answer but it is a bit problematic so it deserves better explanation...
Scoped slots are passed to a component as a functions (generating VNode's when called). Target component just executes those she knows about (by name) and ignores the rest. So lets say your wrapper has b-table (or v-data-table for Vuetify) inside and some other component, let's say pagination. You can use code above inside both of them, passing all slots to each. Unless there is some naming conflict (both components using same slot name), it will work just fine and does not induce any additional cost (all slot functions are already compiled/created when passed to your wrapper component). Target component will use (execute) only the slots it knows by name.
If there is possible naming conflict, it can be solved by using some naming convention like prefixing slot names intended just for b-table with something like table-- and doing filtering inside but be aware that $scopedSlots object does contain some Vue internal properties which must be copied along !! ($stable, $key and $hasNormal for Vue 2 - see the code). So the filtering code below even it's perfectly fine and doesn't throw any error will not work (b-table will not recognize and use the slots)
<b-table>
<template v-for="(_, slotName) of tableSlots" v-slot:[slotName]="scope">
<slot :name="slotName" v-bind="scope"/>
</template>
</b-table>
computed: {
tableSlots() {
const prefix = "table--";
const raw = this.$scopedSlots;
const filtered = Object.keys(raw)
.filter((key) => key.startsWith(prefix))
.reduce(
(obj, key) => ({
...obj,
[key.replace(prefix, "")]: raw[key],
}),
{}
);
return filtered;
},
},
This code can be fixed by including the properties mentioned above but this just too much dependency on Vue internal implementation for my taste and I do not recommend it. If it's possible, stick with the scenario 1...
I wanted to render a list using v-for. It's simple enough, the documentation explains almost every use case. I want it to look like that:
<template v-for="(item, index) in items" :key="index">
<CustomComponent :item="item"/>
<Separator v-if="index !== items.length-1"/>
</template>
Unfortunately, the documentation does not say how to set a key for multiple custom components in one v-for.
Obviously, I don't want to include separator to my custom component, because it is used in other places too. Code I have pasted is generating those errors:
'template' cannot be keyed. Place the key on real elements instead.
I can set a key on component and separator using an index but I got errors: Duplicate keys detected: 'x'. This may cause an update error.
For now, I'm doing it like that but it's an ugly hack and would not work with more components in one template.
<template v-for="(item, index) in items">
<CustomComponent :item="item" :key="(index+1)*-1"/>
<Separator v-if="index !== items.length-1" :key="(index+1)"/>
</template>
Example from documentation explains templates on the list with basic components which does not require keys.
Does anyone know how should I do it correctly?
Ps. It is not recommended to use v-if on v-for. Could someone suggest how to change my code not to use v-if but don't render separator under the last element?
Here is how I was able to generate a key -- you could customize the generateKey method to return whatever you like.
<template>
<div>
<div
v-for="(item, index) in items"
:key="generateKey(item, index)"
>Item {{ index }} : {{ item }}</div>
</div>
</template>
<script>
export default {
data() {
return {
items: ["Sun", "Moon", "Stars", "Sky"]
};
},
methods: {
generateKey(item, index) {
const uniqueKey = `${item}-${index}`;
return uniqueKey;
}
}
};
</script>
Working Example: https://codesandbox.io/s/30ojo1683p
I was talking with a friend and he suggested the simplest and in my opinion the best solution. Just add a component prefix to every key e.g:
<template v-for="(item, index) in items">
<CustomComponent :item="item" :key="'custom_component-'+index"/>
<Separator v-if="index !== items.length-1" :key="'separator-'+index"/>
</template>