How do have unique variables for each dynamically created buttons/text fields? - vue.js

I'm trying to create buttons and vue element inputs for each item on the page. I'm iterating through the items and rendering them with v-for and so I decided to expand on that and do it for both the rest as well. The problem i'm having is that I need to to bind textInput as well as displayTextbox to each one and i'm not sure how to achieve that.
currently all the input text in the el-inputs are bound to the same variable, and clicking to display the inputs will display them all at once.
<template>
<div class="container">
<div v-for="(item, index) in items" :key="index">
<icon #click="showTextbox"/>
<el-input v-if="displayTextbox" v-model="textInput" />
<el-button v-if="displayTextbox" type="primary" #click="confirm" />
<ItemDisplay :data-id="item.id" />
</div>
</div>
</template>
<script>
import ItemDisplay from '#/components/ItemDisplay';
export default {
name: 'ItemList',
components: {
ItemDisplay,
},
props: {
items: {
type: Array,
required: true,
},
}
data() {
displayTextbox = false,
textInput = '',
},
methods: {
confirm() {
// todo send request here
this.displayTextbox = false;
},
showTextbox() {
this.displayTextbox = true;
}
}
}
</script>
EDIT: with the help of #kissu here's the updated and working version
<template>
<div class="container">
<div v-for="(item, index) in itemDataList" :key="itemDataList.id">
<icon #click="showTextbox(item.id)"/>
<El-Input v-if="item.displayTextbox" v-model="item.textInput" />
<El-Button v-if="item.displayTextbox" type="primary" #click="confirm(item.id)" />
<ItemDisplay :data-id="item.item.uuid" />
</div>
</div>
</template>
<script>
import ItemDisplay from '#/components/ItemDisplay';
export default {
name: 'ItemList',
components: {
ItemDisplay,
},
props: {
items: {
type: Array,
required: true,
},
}
data() {
itemDataList = [],
},
methods: {
confirm(id) {
const selected = this.itemDataList.find(
(item) => item.id === id,
)
selected.displayTextbox = false;
console.log(selected.textInput);
// todo send request here
},
showTextbox(id) {
this.itemDataList.find(
(item) => item.id === id,
).displayTextbox = true;
},
populateItemData() {
this.items.forEach((item, index) => {
this.itemDataList.push({
id: item.uuid + index,
displayTextbox: false,
textInput: '',
item: item,
});
});
}
},
created() {
// items prop is obtained from parent component vuex
// generate itemDataList before DOM is rendered so we can render it correctly
this.populateItemData();
},
}
</script>

[assuming you're using Vue2]
If you want to interact with multiple displayTextbox + textInput state, you will need to have an array of objects with a specific key tied to each one of them like in this example.
As of right now, you do have only 1 state for them all, meaning that as you can see: you can toggle it for all or none only.
You'll need to refactor it with an object as in my above example to allow a case-per-case iteration on each state individually.
PS: :key="index" is not a valid solution, you should never use the index of a v-for as explained here.
PS2: please follow the conventions in terms of component naming in your template.
Also, I'm not sure how deep you were planning to go with your components since we don't know the internals of <ItemDisplay :data-id="item.id" />.
But if you also want to manage the labels for each of your inputs, you can do that with nanoid, that way you will be able to have unique UUIDs for each one of your inputs, quite useful.

Use an array to store the values, like this:
<template>
<div v-for="(item, index) in items" :key="index">
<el-input v-model="textInputs[index]" />
</div>
<template>
<script>
export default {
props: {
items: {
type: Array,
required: true,
},
},
data() {
textInputs: []
}
}
</script>

Related

How do you use buefy's b-taginput in a custom component so that it works like v-model, It is only working one way binding?

I'm new to vue and decided to try out buefy for some useful components.
To try and keep my code organized I'm trying to make a custom component using the b-taginput.
I have it so that the component loads with the tags in someArrayofTags, but when I'm typing into the b-taginput, it does not add new tags into someArrayofTags. Hence I lose the two-way binding / updating. I would like to know where I am going wrong and how I could adjust this.
I'm not too well versed to understand how they have implemented it, but i do see that it is composed of autocomplete and b-tags https://github.com/buefy/buefy/blob/dev/src/components/taginput/Taginput.vue
I'm trying to use the custom component as such
<mytaglist v-model="someArrayofTags"></mytaglist>
I know v-model is just v-bind on value and #input events. My code is below.
<template>
<b-field label="tag inputs">
<b-taginput
:value="value"
#input=someMethod($event.target.value)
size="is-small"
ref="ti"
>
<template slot="selected" slot-scope="value">
<b-tag
v-for="(tag, index) in value.tags"
:key="index"
:type="getType(tag)"
rounded
:tabstop="false"
ellipsis
closable
#close="$refs.ti.removeTag(index, $event)"
>
{{ tag }}
</b-tag>
</template>
</b-taginput></b-field
>
</template>
<script>
export default {
props: ['value'],
data() {
return {
normal: ["wnl","clear"]
};
},
methods: {
someMethod(tags) {
alert(tags)
this.$emit("input", tags)
},
getType(tag) {
if (this.normal.includes(tag)) {
return "is-danger";
} else {
return "is-success";
}
},
},
};
</script>
Thanks
After going through the source for buefy, I found that I could watch and update the values based on a v-model within the new component.
The code below works for me, but if anyone could provide a better solution I will leave it open.
<template>
<b-field label="tag inputs">
<b-taginput
v-model="newValue"
size="is-large"
ref="ti"
>
<template slot="selected" slot-scope="value">
<b-tag
v-for="(tag, index) in value.tags"
:key="index"
:type="getType(tag)"
rounded
:tabstop="false"
ellipsis
closable
#close="$refs.ti.removeTag(index, $event)"
>
{{ tag }}
</b-tag>
</template>
</b-taginput></b-field
>
</template>
<script>
export default {
props: ['value'],
data() {
return {
normal: ["wnl","clear","deep & quiet"],
newValue: this.value
};
},
methods: {
getType(tag) {
if (this.normal.includes(tag)) {
return "is-danger";
} else {
return "is-success";
}
},
},
watch: {
newValue(value) {
this.$emit('input', value)
},
value(value) {
this.newValue = value
}
}
};
</script>
<style>
</style>

Vue.js - Inject el elements to html

I have website for online tests.
One of the question that i have created on the test its topic "Fill in the blank", which means fill in the blank spaces words.
The question comes from the server as a string like that "Today is a [1] day, and i should [2] today".
What i want to do is to get that string and replace all the [] with el-input.
I have done something like that
<template>
<div class="d-flex flex-column mg-t-20 pd-10">
<h6 class="tx-gray-800">Fill in the blank areas the missing words</h6>
<div class="mg-t-20" v-html="generateFillBlankQuestion(question.question)" />
</div>
</template>
<script>
export default {
name: 'FillBlank',
directives: {},
props: [ 'question' ],
components: {
},
computed: {},
data() {
return {
input: ''
}
},
filters: {},
created() {
},
methods: {
generateFillBlankQuestion(question) {
var matches = question.match((/\[\d\]/g))
console.log(matches)
matches.forEach((element) => {
console.log(element)
question = question.replace(element, '<el-input />')
})
console.log(question)
return question
}
}
}
On this line question = question.replace(element, '<el-input />') I'm replacing the [] to input.
For some reason when i try to replace it to <el-input> it doesn't render it.
But if i use <input type='text'> it renders it.
Is it possible to inject el elements?
If you are not using the Vue run-time template compiler you can not render Vue components inside v-html. You should do something like this:
<template>
<div class="d-flex flex-column mg-t-20 pd-10">
<h6 class="tx-gray-800">Fill in the blank areas the missing words</h6>
<div class="mg-t-20">
<template v-for="(word,idx) in wordList">
<el-input v-if="word.blank" v-model="word.value" :key="idx" />
<template v-else>{{ word.text }}</template>
</template>
</div>
</div>
</template>
<script>
export default
{
name: 'FillBlank',
props:
{
question:
{
type: String,
default: ''
}
},
computed:
{
wordList()
{
const words = this.question.split(' ');
return words.map(word =>
({
value: '',
text: word,
blank: /^\[\d+\]$/.test(word),
}));
}
}
}

Is possible to generate vue component using for loop inside function?

Is possible to generate vue component using for loop. I am trying to generate and able to get but it's override by new component dynamically it's override component one schema also with component second with is at last generated.
https://jsfiddle.net/3ordn7sj/5/
https://jsfiddle.net/bt5dhqtf/973/
for (var key in app.html) {
Vue.component(key, {
template: `<div><vue-form-generator :model="model"
:schema="schema"
:options="formOptions"
ref="key"></vue-form-generator>{{ key }}</div>`,
mounted() {
this.schema = app.html[key]
},
data: function () {
return {
schema: app.html[key],
key: '',
formOptions: this.formOptions,
model: this.model,
}
},
}
)
}
Is possible to generate vue component using for loop. I am trying to generate and able to get but it's override by new component dynamically it's override component one schema also with component second with is at last generated. In above jsfiddel link my data is there inside created.
I am trying to generate vue component base on this data and I am using vue form generator.In above code what exactly I am trying to do is while my loop running form generated but I don't know how it's first component also getting second component schema and it;s showing on first step also overrides schema data.
I am very confused why this is happening I tried a lot but I am not getting any solution if you have please suggest what I can do for generate component using for loop inside function.
Please try to solve this issue or tell me id it;s not possible.
I did like this
<form-wizard #on-complete="onComplete"
#on-change="handleChange"
validate-on-back
ref="wizard"
:start-index.sync="activeTabIndex"
shape="circle" color="#20a0ff" error-color="#ff4949" v-if="html != 0">
<tab-content v-for="tab in tabs"
v-if="!tab.hide"
:key="tab.title"
:title="tab.title"
:icon="tab.icon">
<component :is="tab.component"></component>
</tab-content>
</form-wizard>
Inside Data I have added for now this tabs option
tabs: [{title: 'Personal details', icon: 'ti-user', component: 'firstTabSchema'},
{title: 'Is Logged In?', icon: 'ti-settings', component: 'secondTabSchema', hide: false},
],
generateNewForm.vue
<template>
<div class="app animated fadeIn">
<loading :active.sync="this.$store.state.isLoading"
:can-cancel="true"
:is-full-page="this.$store.state.fullPage"></loading>
<b-row>
<b-col cols="12" xl="12">
<transition name="slide">
<b-card>
<div slot="header">
<b-button variant="primary" #click="goBack"><i class="icon-arrow-left icons font-1xl"></i>Back</b-button>
</div>
<formcomponent :tree="this.$store.state.formData" />
</b-card>
</transition>
</b-col>
</b-row>
</div>
</template>
<script>
import {store} from '#/components/store'
import formcomponent from '#/components/formcomponent';
import Vue from 'vue'
import Loading from 'vue-loading-overlay';
import 'vue-loading-overlay/dist/vue-loading.css';
import {FormWizard, TabContent} from 'vue-form-wizard'
import 'vue-form-wizard/dist/vue-form-wizard.min.css'
import VueFormGenerator from "vue-form-generator";
/*import VeeValidate from 'vee-validate';*/
Vue.use(VueFormGenerator);
Vue.use(Loading);
export default {
name: 'tables',
store: store,
data: () => {
return {
finalModel: {},
activeTabIndex: 0,
model: {},
count: 0,
}
},
components: {
'loading': Loading,
FormWizard,
TabContent,
formcomponent: formcomponent
},
created() {
},
beforeMount() {
this.$store.dispatch('loadFormData', this.$route.params.id);
},
methods: {
onComplete: function(){
alert('Yay. Done!');
},
goBack() {
this.$router.go(-1)
}
}
}
</script>
formcomponent.vue
<template>
<div>
<form-wizard #on-complete="onComplete"
#on-change="handleChange"
validate-on-back
ref="wizard"
:start-index.sync="activeTabIndex"
shape="circle" color="#20a0ff" error-color="#ff4949" v-if="html != 0">
<tab-content v-for="tab in tabs"
v-if="!tab.hide"
:key="tab.title"
:title="tab.title"
:icon="tab.icon">
<component :is="tab.component"></component>
</tab-content>
</form-wizard>
</div>
</template>
<script>
import Vue from 'vue'
import {FormWizard, TabContent} from 'vue-form-wizard'
import 'vue-form-wizard/dist/vue-form-wizard.min.css'
import VueFormGenerator from "vue-form-generator";
//console.log(Vue.options);
Vue.use(VueFormGenerator);
export default {
components: {
FormWizard,
TabContent,
},
data() {
return {
loadingWizard: false,
error: null,
count: 0,
dash: '-',
firstTime: 0,
model: {},
html: '',
index: '',
activeTabIndex: 0,
tabs: [{title: 'Personal details', icon: 'ti-user', component: 'firstTabSchema'},
{title: 'Is Logged In?', icon: 'ti-settings', component: 'secondTabSchema', hide: false},
],
formOptions: {
validationErrorClass: "has-error",
validationSuccessClass: "has-success",
validateAfterLoad: true,
validateAfterChanged: true,
},
}
},
created() {
this.html = this.tree;
this.index = this.ind;
},
props: ['tree', 'ind'],
methods: {
onComplete: function () {
alert('Yay. Done!');
},
setLoading(value) {
this.loadingWizard = value
},
handleChange(prevIndex, nextIndex) {
console.log(`Changing from ${prevIndex} to ${nextIndex}`)
},
setError(error) {
this.error = error
},
validateFunction: function () {
return new Promise((resolve, reject) => {
console.log(this.$refs.firstTabSchema);
setTimeout(() => {
if (this.count % 2 === 0) {
reject('Some custom error')
} else {
resolve(true)
}
this.count++
}, 100)
})
},
validate() {
return true
},
buildTree(tree, rep = 1) {
var html = '';
var app = this;
var dash = "--";
app.html = tree;
var test = this.formOptions;
for (var key in app.html) {
var isComponentExists = key in Vue.options.components
if(!isComponentExists) {
Vue.component(key, {
template: `<div :class="key"><vue-form-generator :model="model"
:schema="schema"
:options="formOptions"
ref="key"></vue-form-generator>{{ key }}</div>`,
mounted() {
this.schema = app.html[key]
this.key = key
},
data: function () {
return {
schema: app.html[key],
key: '',
formOptions: this.formOptions,
model: this.model,
}
},
}
)
//console.log(Vue.$options);
this.$emit('init')
}
}
}
},
watch: {
tree: {
handler() {
this.html = '';
this.buildTree(this.tree)
},
deep: true
}
}
}
</script>
So if I understand you correctly you are trying to use a list of some kind app.html to dynamically register a set of identical components under different names (key). I think it is possible, but i cannot tell from the code you provided what is going wrong.
I can tell you that this approach to code reuse/abstraction is probably not the right way to go. The whole point of components is that you can reuse functionality with the use of binding props. What you are trying to do is probably better achieved like this:
Vue.component('my-custom-form', {
props: ['key', 'schema', 'formOptions', 'model'],
template: `
<div>
<vue-form-generator
:model="model"
:schema="schema"
:options="formOptions"
:ref="key"
></vue-form-generator>{{ key }}
</div>`,
})
Then in your vue template:
<my-custom-form
v-for="(key, value) in app.html"
:key="key"
:schema="value"
:formOptions="formOptions"
:model="model"
/>
Let me know if that helps. Otherwise, if you are sure you want to stick with your original approach give me some more context for the code and I will see what i can do. Best of luck!
I think i understand a little bit better where you are getting stuck. I see this piece of code in your jsfiddle:
<div id="app">
<div>
<form-wizard #on-complete="onComplete">
<tab-content v-for="tab in tabs"
v-if="!tab.hide"
:key="tab.title"
:title="tab.title"
:icon="tab.icon">
<component :is="tab.component"></component>
</tab-content>
</form-wizard>
</div>
</div>
You don't need to use the component :is syntax to solve this problem. You could also write is as follows:
<div id="app">
<div>
<form-wizard #on-complete="onComplete">
<tab-content v-for="(tab, tabindex) in tabs"
v-if="!tab.hide"
:key="tab.title"
:title="tab.title"
:icon="tab.icon">
<my-custom-form v-if="tabindex == 1" :key="'the first key'" :schema="app.html['the first key']"/>
<my-custom-form v-else-if="tabindex == 2" :key="'the second key'" :schema="app.html['the second key']"/>
</tab-content>
</form-wizard>
</div>
</div>
etc. Let me know if that example is clear.
best

How to pass and change index of array in vue?

I have the gist of how to do this, but I'm a beginner in vue, and I'm struggling with how to put it together. I need Control.vue to update the index in Exhibitor.vue. I know I'll have an $emit event happening in Control when I click on the button to pass the index data to the parent, and I'd have to use props to pass data from Exhibitor to its children, but how? I can't understand how to pass the index of an array with my code.
Exhibitor.vue
<template>
<div id="exhibitor">
<section class="exhibitor_info">
<h1 class="exhibitor_name">{{ exhibitors[index].firstName }} {{ exhibitors[index].lastName }}</h1>
<h2>Tag Number: {{ exhibitors[index].tagNum }} <em>{{ exhibitors[index].species }}</em></h2>
</section>
<div class="frame"><img :src="getImgUrl(exhibitors[index].picture)" alt="Exhibitor-Picture" class="image"></div>
</div>
</template>
<script>
export default {
name: 'Exhibitor',
data() {
return {
exhibitors: [],
index: 0
}
},
created: function() {
this.fetchExhibitors();
},
methods: {
fetchExhibitors() {
let uri = 'http://localhost:8081/exhibitor'
this.axios.get(uri).then(response => {
this.exhibitors = response.data
})
},
getImgUrl: function(pic) {
return require('../assets/' + pic)
}
}
}
</script>
Display.vue
<template>
<div id="display">
<exhibitor></exhibitor>
<buyer></buyer>
</div>
</template>
<script>
import Exhibitor from './Exhibitor.vue';
import Buyer from './Buyer.vue';
export default {
components: {
'exhibitor': Exhibitor,
'buyer': Buyer
}
}
</script>
Control.vue
<template>
<div id="control">
<display></display>
<button v-on:click="incrementLeft">Left</button>
<button v-on:click="incrementRight">Right</button>
</div>
</template>
<script>
import Exhibitor from './Exhibitor.vue';
import Display from './Display.vue';
export default{
props: ['exhibitors', 'buyers', 'index'],
data() {
return {
index: 0
}
},
methods: {
incrementRight: function() {
// Note that '%' operator in JS is remainder and NOT modulo
this.index = ++this.index % this.exhibitors.length
},
incrementLeft: function() {
// Note that '%' operator in JS is remainder and NOT modulo
if (this.index === 0) {
this.index = this.exhibitors.length - 1
} else {
this.index = --this.index % this.exhibitors.length
}
}
},
components: {
'display': Display
}
}
</script>
So you can get what you want to happen and there are two ways of making it happen that I can think of. First I will just clarify the terms relating to this because you seem to have them the wrong way around. Let's look at you tier structure which is like this:
Control.vue
contains: Display.vue
contains: Exhibitors.vue & Buyers.vue.
Therefore Control.vue is the parent of Display.vue which is the parent of Buyers.vue and Exhibitors.vue.
Anyway, What we need to do is control the array of exhibitors (and I guess buyers but you didn't include them in your code so I'll do likewise) which is in Exhibitors.vue from Control.Vue even though they don't have a direct parent child relationship. What I've done is set a prop that is passed to Display.vue which uses scoped slots to render the exhibitors from Exhibitors.Vue. Because the left and right buttons need to know when to stop going I have emitted the array length from Exhibitors.vue to Display.vue and again to Control.vue. It all works so heres some code.
//Control.vue
<template>
<div class="content">
<display v-on:finalLength="setIndexLimit" :i="index"></display>
<button #click="changeDown">Down</button>
<button #click="changeUp">Up</button>
<p>{{indLimit}}</p>
</div>
</template>
<script>
import Display from '#/components/Display'
export default {
components: {
Display
},
data: () => ({
index: 0,
indLimit: 0
}),
methods: {
changeUp() {
if (this.indLimit === this.index+1) {
this.index=0
}
else {
this.index ++
}
},
changeDown() {
if (this.index === 0) {
this.index = this.indLimit - 1
}
else {
this.index --
}
},
setIndexLimit(e) {
this.indLimit = e
}
}
}
</script>
and
//Display.vue
<template>
<div id="display">
<p>From Display</p>
<exhibitors v-on:ExLength="setLength">
<p>{{i}}</p>
</exhibitors>
<exhibitors>
<p slot-scope="{ exhibitors }">{{exhibitors[i].firstName}}</p>
</exhibitors>
<exhibitors>
<p slot-scope="{ exhibitors }">{{exhibitors[i].lastName}}</p>
</exhibitors>
<exhibitors>
<p slot-scope="{ exhibitors }">{{exhibitors[i].tagNum}}</p>
</exhibitors>
<exhibitors>
<p slot-scope="{ exhibitors }">{{exhibitors[i].species}}</p>
</exhibitors>
</div>
</template>
<script>
import Child from '#/components/admin/Exhibitors'
export default {
components: {
Exhibitors
},
props: [
'i'
],
data: () => ({
exhibitLength: null
}),
methods: {
setLength(e) {
this.exhibitLength = e
this.$emit('finalLength',e)
}
}
}
</script>
And finally:
//Exhibitors.vue
<template>
<div>
<slot :exhibitors="exhibitors"><p>I'm the child component!</p></slot>
</div>
</template>
<script>
export default {
data: () => ({
exhibitors: [
{
firstName: 'Joe',
lastName: 'Burns',
tagNum: 1,
species: 'ant'
},
{
firstName: 'Tom',
lastName: 'Yorke',
tagNum: 2,
species: 'bee'
},
{
firstName: 'Flit',
lastName: 'Argmeno',
tagNum: 3,
species: 'giraffe'
}
],
}),
mounted: function () {
this.$nextTick(function () {
let length = this.exhibitors.length
this.$emit('ExLength', length)
})
}
}
</script>
So as I said all that works, you can click the buttons and it will loop through the array contents. You can style it how you want wherever you like. However, it is a bit messy with props and slots and emits and whatnot just to relay a single index number and it would be far easier in my opinion to have a vuex store where 'index' is stored as a state item and can be used and changed anywhere without all the above carry on.

Deleting vue component from list always delete the last element in list

I have read the documentation for rendering the custom components in list using v-for here.
But for some reason, I am not able to get this working.It always delete the last component instead of the one I send in the index. Any idea why it is not working ?
My VUE JS version is : 2.5.16.
Using PHPStorm IDE and running on docker (linux container)
And Laravel mix (I have "laravel-mix": "0.*" entry in package.json) to use webpack and compile the JS modules.
Here is the piece of some of my code
// Parent Component JS
<template>
<ul>
<li
is="child-component"
v-for="(child, index) in componentList"
:key="index"
:myVal="Something...."
#remove="dropField(index)"
#add-custom-field="addField"
></li>
</ul>
</template>
<script>
import childComponent from './ChildComponent';
export default {
name: 'CustomList',
components: {'child-component' :childComponent},
data() {
return {
componentList: []
}
},
methods: {
addField() {
console.log('Handling add-custom-field field...');
this.componentList.push(childComponent);
},
dropField(index) {
console.log(`I am deleting the component with index = ${index} from listview in parent...`);
this.componentList.splice(index, 1);
}
}
}
// Child Component JS
<template>
<div>
<input type="text" v-model="currentValue" /><button #click.prevent="$emit('remove')" > Remove </button>
</div
</template>
<script>
export default {
props: { myVal : '' },
data() { return { currentValue: ''} },
created() {this.currentValue = this.myVal;}
}
</script>
The issue is caused by in-place patch” strategy for v-for. That means Vue will not rebuild all childs when removed one element from componentList.
Check Vue Guide on an “in-place patch” strategy for v-for:
When Vue is updating a list of elements rendered with v-for, by
default it uses an “in-place patch” strategy. If the order of the data
items has changed, instead of moving the DOM elements to match the
order of the items, Vue will patch each element in-place and make sure
it reflects what should be rendered at that particular index.
Actually you already deleted the last item, but the problem is the data property=currentValue of first&second child have been 'a', 'b', when first mounted. Later when Vue re-render (delete the last child), data property=currentValue keeps same value though prop=myVal already changed.
Look at below demo, I added one input and bind myVal, you will see the differences.
Vue.config.productionTip = false
let childComponent = Vue.component('child', {
template: `<div class="item">
<p>Index:{{parentIndex}} => <button #click.prevent="removed()" > Remove </button>
Data:<input type="text" v-model="currentValue" />Props:<input type="text" v-bind:value="myVal" />
</p>
</div>`,
props: { 'myVal':{
type: String,
default: ''
} ,
'parentIndex': {
type: Number,
default: 0
}
},
data() {
return {
currentValue: ''
}
},
mounted() {
this.currentValue = this.myVal
},
methods: {
removed: function () {
this.$emit('remove')
}
}
})
app = new Vue({
el: "#app",
data() {
return {
componentList: ['a', 'b', 'c'],
componentType:childComponent
}
},
methods: {
addField() {
console.log('Handling add-custom-field field...');
this.componentList.push(childComponent);
},
dropField(index) {
console.log(`I am deleting the component with index = ${index} from listview in parent...`);
this.componentList.splice(index, 1);
}
}
})
li:nth-child(odd) {
background-color:#d0d5dd;
}
<script src="https://unpkg.com/vue#2.5.16/dist/vue.js"></script>
<div id="app">
<ul>
<li v-for="(child, index) in componentList"><div
:is="componentType"
:key="index"
:my-val="child"
:parent-index="index"
#remove="dropField(index)"
#add-custom-field="addField"
>{{child}}</div></li>
</ul>
</div>
I discover that if you have another updated :key property (not index) it will work as you want
here's my example
<template>
<div id="app">
<ul>
<li
v-for="(teacher, index) in teachers_list"
v-bind="teacher"
:key="teacher.id"
>
<p>Teacher id {{teacher.id}}</p>
<button #click="deleteTeacher(index)"></button>
</li>
</ul>
</div>
</template>
<script>
export default {
data() {
return {
teachers_list: [
{name: 'teacher a', id: 100},
{name: 'teacher b', id: 200},
{name: 'teacher c', id: 300},
]
}
},
methods: {
deleteTeacher(index) {
console.log(index);
this.teachers_list.splice(index, 1)
}
}
}
</script>