Vue does not delete proper component from list - vue.js

I got :key="index" but it still does not do it as I need.
If you add for example 6 items and select something from select on the third item and hit remove button on that item, it will delete another item, but not this one.
Here is a link to fiddle
https://jsfiddle.net/benderlio/ecyskz83/1/
Vue.config.devtools = true
Vue.component('select-component', {
data: function () {
return {
currentSelectedData: undefined,
}
},
template: `<div>
<select v-model="currentSelectedData">
<option v-for="(item,key) in dataArray" :key="key" :value="key">{{item}}</option>
</select>
<select v-if="currentSelectedData >= 0">
<option v-for="(item,key) in tempData" :key="key" :value="key">{{item}}</option>
</select>
</div>`,
watch: {
currentSelectedData(newValue, oldValue) {
console.log('', newValue, oldValue);
this.$emit("got-selected-value", newValue)
}
},
props: {
'data-array': {
type: Array,
required: true,
},
'temp-data': {
type: Array,
required: true,
},
},
})
new Vue({
el: '#app',
data: {
components: [],
dataFirstSelect: ["sheet3", "sheet4", "sheet5"
],
dataSecondSelect: [1, 2, 3]
},
methods: {
emitedValue(payload) {
console.log('Got the value from child: ', payload);
},
addComp() {
const comp = {
id: new Date().getTime()
};
this.components.push(comp)
},
remove(id) {
// this.components = this.components.filter(i => i.id !== id)
console.log('Remove', id, this.components);
//this.$delete(this.components, id)
this.components.splice(id, 1)
},
log() {
console.log('---------', this.components.map(i => i.id));
}
},
})

It works fine if you use item.id as your key instead of index and put your key on a real DOM element, not a template. Using index as a key is an anti-pattern.
Vue.config.devtools = true
Vue.component('select-component', {
data: function() {
return {
currentSelectedData: undefined,
selectedTemp: undefined
}
},
template: `<div>
<select v-model="currentSelectedData">
<option v-for="(item, key) in dataArray" :value="key">{{item}}</option>
</select>
<select v-if="currentSelectedData >= 0" v-model="selectedTemp">
<option v-for="(item, key) in tempData" :value="key">{{item}}</option>
</select>
<div>{{currentSelectedData}}</div>
<div>{{selectedTemp}}</div>
</div>`,
watch: {
currentSelectedData(newValue, oldValue) {
console.log('', newValue, oldValue);
this.$emit("got-selected-value", newValue)
}
},
props: {
'data-array': {
type: Array,
required: true,
},
'temp-data': {
type: Array,
required: true,
},
},
})
new Vue({
el: '#app',
data: {
components: [],
dataFirstSelect: ["sheet3", "sheet4", "sheet5"],
dataSecondSelect: [1, 2, 3]
},
methods: {
emitedValue(payload) {
console.log('Got the value from child: ', payload);
},
addComp() {
const comp = {
id: new Date().getTime()
};
this.components.push(comp)
},
remove(id) {
// this.components = this.components.filter(i => i.id !== id)
console.log('Remove', id, this.components);
//this.$delete(this.components, id)
this.components.splice(id, 1)
},
log() {
console.log('---------', this.components.map(i => i.id));
}
},
})
.select-component {
display: block;
margin: 10px;
}
.item {
padding: 10px;
margin: 10px;
border: 1px solid gray;
}
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
<div id="app">
<button #click="addComp">Add</button>
<template v-for="(item,index) in components">
<div class="item" :key="item.id">
id: {{item.id}} {{index}}
<button #click="remove(index)">Remove</button>
<select-component class="select-component" v-on:got-selected-value="emitedValue" :data-array='dataFirstSelect'
:temp-data='dataSecondSelect'>
</select-component>
</div>
</template>
</div>

Related

Check if the value change in loop Vuejs

I'm making chat app by Vuejs and want to handle if messages in loop belong to new user for styling color/background-color of user's message.
<template v-for="msg in allMsgs">
<li :key=msg.id> //I want to add some class to handle if next message belong to new user.
<span class="chatname">{{msg.user.name}}</span>
{{msg.content}}
</li>
</template>
https://prnt.sc/114ynuq
Thank you so much
You can use a computed property to determine the position of each message according to the sequence, and then, use class-binding as follows:
new Vue({
el:"#app",
data: () => ({
allMsgs: [
{ id:1, user: { name:'B' }, content:'contentB' },
{ id:2, user: { name:'A' }, content:'contentA' },
{ id:3, user: { name:'A' }, content:'contentA' },
{ id:4, user: { name:'B' }, content:'contentB' },
{ id:5, user: { name:'B' }, content:'contentB' },
{ id:6, user: { name:'A' }, content:'contentA' },
{ id:7, user: { name:'A' }, content:'contentA' }
]
}),
computed: {
messages: function() {
let pos = 1, prev = null;
return this.allMsgs.map((msg, index) => {
// if msg is not the first, and it belongs to a new user, opposite pos
if(index !== 0 && msg.user.name !== prev.user.name) pos *= -1;
msg.position = pos;
prev = msg;
return msg;
});
}
}
});
.chatname { font-weight:bold; }
.left { text-align:left; }
.right { text-align:right; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<template v-for="(msg, index) in messages">
<li
:key=msg.id
:class="{ 'right': msg.position === 1, 'left': msg.position === -1 }"
>
<span class="chatname">
{{msg.user.name}}
</span>
{{msg.content}}
</li>
</template>
</div>

Handle interaction between vue fields

I have prepared a functional code example in JSFiddle of VUE field interaction.
https://jsfiddle.net/JLLMNCHR/2a9ex5zu/6/
I have a custom autocomplete component that works properly, a normal input field, and a 'Load' button which objetive is to load the value entered in the normal input in the autocomplete field.
This 'load' button is not working.
HTML:
<div id="app">
<p>Selected: {{test1}}</p>
<br>
<div>
<label>Test1:</label>
<keep-alive>
<autocomplete v-model="test1" v-bind:key="1" :items="theItems">
</autocomplete>
</keep-alive>
</div>
<br>
<label>Display this in 'test1':</label>
<input type="text" v-model=anotherField>
<button type="button" v-on:click="loadField()">Load</button>
<br>
<br>
<button type="button" v-on:click="displayVals()">Display vals</button>
</div>
<script type="text/x-template" id="autocomplete">
<div class="autocomplete">
<input type="text" #input="onChange" v-model="search"
#keyup.down="onArrowDown" #keyup.up="onArrowUp" #keyup.enter="onEnter" />
<ul id="autocomplete-results" v-show="isOpen" class="autocomplete-results">
<li class="loading" v-if="isLoading">
Loading results...
</li>
<li v-else v-for="(result, i) in results" :key="i" #click="setResult(result)"
class="autocomplete-result" :class="{'is-active':i === arrowCounter}">
{{ result }}
</li>
</ul>
</div>
</script>
VUE.JS:
const Autocomplete = {
name: "autocomplete",
template: "#autocomplete",
props: {
items: {
type: Array,
required: false,
default: () => []
},
isAsync: {
type: Boolean,
required: false,
default: false
}
},
data() {
return {
isOpen: false,
results: [],
search: "",
isLoading: false,
arrowCounter: 0
};
},
methods: {
onChange() {
// Let's warn the parent that a change was made
this.$emit("input", this.search);
// Is the data given by an outside ajax request?
if (this.isAsync) {
this.isLoading = true;
} else {
// Let's search our flat array
this.filterResults();
this.isOpen = true;
}
},
filterResults() {
// first uncapitalize all the things
this.results = this.items.filter(item => {
return item.toLowerCase().indexOf(this.search.toLowerCase()) > -1;
});
},
setResult(result) {
this.search = result;
this.$emit("input", this.search);
this.isOpen = false;
},
onArrowDown(evt) {
if (this.arrowCounter < this.results.length) {
this.arrowCounter = this.arrowCounter + 1;
}
},
onArrowUp() {
if (this.arrowCounter > 0) {
this.arrowCounter = this.arrowCounter - 1;
}
},
onEnter() {
this.search = this.results[this.arrowCounter];
this.isOpen = false;
this.arrowCounter = -1;
},
handleClickOutside(evt) {
if (!this.$el.contains(evt.target)) {
this.isOpen = false;
this.arrowCounter = -1;
}
}
},
watch: {
items: function(val, oldValue) {
// actually compare them
if (val.length !== oldValue.length) {
this.results = val;
this.isLoading = false;
}
}
},
mounted() {
document.addEventListener("click", this.handleClickOutside);
},
destroyed() {
document.removeEventListener("click", this.handleClickOutside);
}
};
new Vue({
el: "#app",
name: "app",
components: {
autocomplete: Autocomplete
},
methods: {
displayVals() {
alert("test1=" + this.test1);
},
loadField() {
this.test1=this.anotherField;
}
},
data: {
test1: '',
anotherField: '',
theItems: [ 'Apple', 'Banana', 'Orange', 'Mango', 'Pear', 'Peach', 'Grape', 'Tangerine', 'Pineapple']
}
});
Any help will be appreciated.
See this new fiddle where it is fixed.
When you use v-model on a custom component you need to add a property named value and watch it for changes, so it can update the local property this.search.

Get object keyname vuejs

I have a data with object, when im using v-for:
data: {
comp: {
title: {
...
},
title2: {
...
},
title3: {
...
},
}
..
div(v-for="item in comp" #click="method(item)")
How i can get name of item (title, title2, title3) in method?
Try this:
new Vue({
el:'#app',
data: {
comp: {
title: {},
title2: {},
title3: {},
}
},
methods: {
itemClicked(item) {
console.log(item);
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div v-for="(val,key) in comp" #click="itemClicked(key)">
{{ key }} - {{ val }}
</div>
</div>

dynamic interpolation computed method name in v-for element in vuejs

In my project vuejs a create a list element with ul li and v-for directive vuejs like this:
<ul>
<li :class="{active: 'isActive+index'}" v-for="(car, index) in cars"></li>
</ul>
Those elements are dynamics. Sometimes are 2 sometimes are 3 or 4 elements
But I need to have a specific logic active class css for each like this:
'isActive+index'
Where this represent a dynamic computed name (already exist). But obviously this code not run and generate basic string word not a link to computed method. I want to execute those computed methods:
computed:
{
isActive1: function ()
{
return myLogic
},
isActive2: function ()
{
return myLogic
},
isActive3: function ()
{
return myLogic
},
isActive4: function ()
{
return myLogic
},
}
How can I link element with dynamic method name for execute computed with vuejs ?
new Vue({
el: '#app',
template: `
<div>
<ul>
<li v-for="(item, index) in cars" :key="index" :class="{ active: statusActive[index] }">
<strong>Car:</strong> {{item.name}} ,
</li>
</ul>
<button #click="changeCars">Change cars</button>
</div>
`,
data() {
return {
cars1: [{
name: "car1",
},
{
name: "car2",
},
{
name: "car3",
},
],
cars2: [{
name: "car1",
},
{
name: "car2",
},
{
name: "car3",
},
{
name: "car4",
},
],
cars3: [{
name: "car1",
},
{
name: "car2",
},
],
carsIndex: 1,
};
},
computed: {
cars() {
return this["cars" + this.carsIndex];
},
statusActive() {
return {
0: this.statusActive0,
1: this.statusActive1,
2: this.statusActive2,
3: this.statusActive3,
};
},
statusActive0() {
return false;
},
statusActive1() {
return true;
},
statusActive2() {
return false;
},
statusActive3() {
return true;
},
},
methods: {
changeCars() {
if (this.carsIndex < 3) {
this.carsIndex++;
} else {
this.carsIndex = 1;
}
},
},
})
.active {
color: red;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.js"></script>
<div id="app"></div>
or
new Vue({
el: '#app',
template: `
<div>
<ul>
<li v-for="(item, index) in cars" :key="index" :class="{ active: isActive(index) }">
<strong>Car:</strong> {{item.name}} ,
</li>
</ul>
<button #click="changeCars">Change cars</button>
</div>
`,
data() {
return {
cars1: [{
name: "car1",
},
{
name: "car2",
},
{
name: "car3",
},
],
cars2: [{
name: "car1",
},
{
name: "car2",
},
{
name: "car3",
},
{
name: "car4",
},
],
cars3: [{
name: "car1",
},
{
name: "car2",
},
],
carsIndex: 1,
};
},
computed: {
cars() {
return this["cars" + this.carsIndex];
},
statusActive0() {
return false;
},
statusActive1() {
return true;
},
statusActive2() {
return false;
},
statusActive3() {
return true;
},
},
methods: {
changeCars() {
if (this.carsIndex < 3) {
this.carsIndex++;
} else {
this.carsIndex = 1;
}
},
isActive(index) {
return this["statusActive" + index];
},
},
})
.active {
color: red;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.js"></script>
<div id="app"></div>

VJs watching array element

I have problem with vm.$watch method it can watch a single variable.
I can't make it to watch array elements.
in the example below i can't watch the change on the two select box bellow:
Vue.directive('select', {
twoWay: true,
bind: function() {
var optionsData
var optionsExpression = this.el.getAttribute('options')
if (optionsExpression) {
optionsData = this.vm.$eval(optionsExpression)
}
// initialize select2
var self = this
$(this.el)
.select2({
data: optionsData
})
.on('change', function() {
self.set(this.value)
})
},
update: function(value) {
$(this.el).val(value).trigger('change')
},
unbind: function() {
$(this.el).off().select2('destroy')
}
})
var vm = new Vue({
el: '#el',
data: {
selected: [{
country: 2
}, {
country: 1
}],
options: [{
id: 1,
text: 'USA'
}, {
id: 2,
text: 'UK'
}]
}
})
vm.$watch('selected', function(newvalue, oldval) {
alert('changed: ' + newvalue)
})
select {
min-width: 300px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<link href="http://cdnjs.cloudflare.com/ajax/libs/select2/4.0.0/css/select2.min.css" rel="stylesheet" />
<script src="http://cdnjs.cloudflare.com/ajax/libs/select2/4.0.0/js/select2.min.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/vue/0.12.9/vue.min.js"></script>
<div id="el">
<p>Selected: {{selected[0].country}}</p>
<select v-select="selected[0].country" options="options">
<option value="0">default</option>
</select>
<p>Selected: {{selected[1].country}}</p>
<select v-select="selected[1].country" options="options">
<option value="0">default</option>
</select>
</div>
You need to specify the deep option on your watch. The array isn't being modified, an object that it contains is being modified.
Vue.directive('select', {
twoWay: true,
bind: function() {
var optionsData
var optionsExpression = this.el.getAttribute('options')
if (optionsExpression) {
optionsData = this.vm.$eval(optionsExpression)
}
// initialize select2
var self = this
$(this.el)
.select2({
data: optionsData
})
.on('change', function() {
self.set(this.value)
})
},
update: function(value) {
$(this.el).val(value).trigger('change')
},
unbind: function() {
$(this.el).off().select2('destroy')
}
})
var vm = new Vue({
el: '#el',
data: {
selected: [{
country: 2
}, {
country: 1
}],
options: [{
id: 1,
text: 'USA'
}, {
id: 2,
text: 'UK'
}]
}
})
vm.$watch('selected', function(newvalue, oldval) {
alert('changed: ' + newvalue)
}, {
deep: true
})
select {
min-width: 300px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<link href="http://cdnjs.cloudflare.com/ajax/libs/select2/4.0.0/css/select2.min.css" rel="stylesheet" />
<script src="http://cdnjs.cloudflare.com/ajax/libs/select2/4.0.0/js/select2.min.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/vue/0.12.9/vue.min.js"></script>
<div id="el">
<p>Selected: {{selected[0].country}}</p>
<select v-select="selected[0].country" options="options">
<option value="0">default</option>
</select>
<p>Selected: {{selected[1].country}}</p>
<select v-select="selected[1].country" options="options">
<option value="0">default</option>
</select>
</div>