v-for inside v-for issue - javascript

I would like to ask if someone see the problem in my code.
Everything render ok, no problem with the render, the issue is that every time I click in one panel content all the panel-content of every group with the same index expand or collapse and not only the one that I clicked.
Any idea?? Thanks a lot.
<div v-for="(group, i) in groups" :key="i" class="pb-4">
<p class="text-h5 primary text-center white--text">{{ group.title }}</p>
<div v-if="group.links">
<a v-for="(link, u) in group.links" :key="u" :href="link.src" class="black--text"> {{ link.title }}</a>
</div>
<v-expansion-panels v-model="activesPanels" multiple accordion dense flat>
<v-expansion-panel v-for="(item, v) in group.fact" v-show="shouldRender(v)" :key="v">
<v-expansion-panel-header class="px-0">{{ item.title }}</v-expansion-panel-header>
<v-expansion-panel-content>
<type :fact="item" :models="models" />
</v-expansion-panel-content>
</v-expansion-panel>
</v-expansion-panels>
</div>

The reason for this issue is that you are using the same v-model variable activesPanels for every group. The activesPanels should belong to each group to maintain their opening and closing state individually.
Also, use the unique properties for template keys to avoid errors i.e, "Duplicate keys detected".
Here is the working demo-
<!DOCTYPE html>
<html>
<head>
<link href="https://cdn.jsdelivr.net/npm/#mdi/font#6.x/css/materialdesignicons.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/vuetify#2.x/dist/vuetify.min.css" rel="stylesheet">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no, minimal-ui">
</head>
<body>
<div id="app">
<v-app id="inspire">
<div v-for="(group, i) in groups" :key="i">
<p class="primary text-center white--text">{{ group.title }}</p>
<div v-if="group.links">
<a v-for="link in group.links" :key="link.src" :href="link.src" class="black--text"> {{ link.title }}</a>
</div>
<v-expansion-panels v-model="group.activesPanels" multiple accordion dense flat>
<v-expansion-panel v-for="item in group.fact" :key="item.title">
<v-expansion-panel-header class="pa-0">{{ item.title }}</v-expansion-panel-header>
<v-expansion-panel-content class="pa-0">
Hello
</v-expansion-panel-content>
</v-expansion-panel>
</v-expansion-panels>
</div>
</v-app>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue#2.x/dist/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vuetify#2.x/dist/vuetify.js"></script>
<script>
new Vue({
el: '#app',
data () {
return {
groups: [
{
title: "group1",
activesPanels: [],
fact: [
{
title: 'fact1'
}
],
links:[
{
src: "https://google.com",
title: "Hello"
},
{
src: "https://twitter.com",
title: "Hello"
}
]
},
{
title: "group2",
activesPanels: [],
fact: [
{
title: 'fact2'
}
],
links:[
{
src: "https://www.shorturl.at/",
title: "Hello"
},
{
src: "https://www.npmjs.com/package/vuex-map-fields",
title: "Hello"
}
]
}
]
}
},
vuetify: new Vuetify(),
})
</script>
</body>
</html>

You should avoid using indexes as keys in your v-for loop. The keys need to be unique for every element. By using index you have probably 2 elements with index 0 as a key. Vue cannot keep track of changes in the DOM that way. It is getting even worse if you edit the array and items in the array get another index.
So, you can use a unique value for the key, maybe an id or link.url and item.title in your code.
See docs:
https://vuejs.org/guide/essentials/list.html#maintaining-state-with-key

Related

In Vue2.7, v-slot is not available, how to solve the problem?

When I run the following code, I get the following results (Google Chrome running screenshot).
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>17-Learning of Scoped Slots and Named Slots</title>
<style>
.h1Class{
color:red;
}
</style>
</head>
<body>
<div id="div_new">
<h1 class="h1Class">first set</h1>
<cpn></cpn>
<h1 class="h1Class">second set</h1>
<cpn>
<!-- first method -->
<template slot="slotName" slot-scope="planallScope">
<!-- second method after Vue2.6 -->
<!-- <template v-slot:slotName="planallScope" > -->
<h4>{{planallScope.planall[0]}}</h4>
<h4>{{planallScope.planall[1]}}</h4>
<h4>{{planallScope.planall[2]}}</h4>
</template>
</cpn>
</div>
<template id="template_div">
<div>
<slot v-bind:planall="plan" name="slotName">
<ul>
<li v-for="item in plan"> {{ item }}</li>
</ul>
</slot>
</div>
</template>
<script src="https://cdn.jsdelivr.net/npm/vue#2.7.7/dist/vue.js"></script>
<script>
const templateDivText = {
template: '#template_div',
data() {
return {
plan: ['C#', 'Java', 'JavaScript']
}
},
}
const app_new = new Vue({
el: '#div_new',
components: {
'cpn': templateDivText,
},
})
</script>
</body>
</html>
The running result is as follows:
enter image description here
When I use v-slot, the code is as follows:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>17-Learning of Scoped Slots and Named Slots</title>
<style>
.h1Class{
color:red;
}
</style>
</head>
<body>
<div id="div_new">
<h1 class="h1Class">first set</h1>
<cpn></cpn>
<h1 class="h1Class">second set</h1>
<cpn>
<!-- first method -->
<!-- <template slot="slotName" slot-scope="planallScope"> -->
<!-- second method after Vue2.6 -->
<template v-slot:slotName="planallScope" >
<h4>{{planallScope.planall[0]}}</h4>
<h4>{{planallScope.planall[1]}}</h4>
<h4>{{planallScope.planall[2]}}</h4>
</template>
</cpn>
</div>
<template id="template_div">
<div>
<slot v-bind:planall="plan" name="slotName">
<ul>
<li v-for="item in plan"> {{ item }}</li>
</ul>
</slot>
</div>
</template>
<script src="https://cdn.jsdelivr.net/npm/vue#2.7.7/dist/vue.js"></script>
<script>
const templateDivText = {
template: '#template_div',
data() {
return {
plan: ['C#', 'Java', 'JavaScript']
}
},
}
const app_new = new Vue({
el: '#div_new',
components: {
'cpn': templateDivText,
},
})
</script>
</body>
</html>
The running result is as follows:
enter image description here
So in Vue2.7, v-slot is not available, how to solve the problem?
Your syntax is correct, except one little detail: you can't use camelCase for slot names.
To be fair, I don't precisely know why, it has to do with template compilation and with the fact the slot names get parsed as element attributes in <template v-slot:slot-name"scope">. Vue's styling guideline does strongly advise on using kebab-case for attributes, directives and the likes, when used in templates or JSX.
However, name="slotName" + <template #slot-name="scope"> doesn't seem to work for slots.
In short, name="slotName" (+ <template #slotName="scope") does not work, but name="slot-name" (+ <template #slot-name="scope") does.
See it working, in Vue 2.7.7:
const templateDivText = Vue.defineComponent({
template: '#template_div',
data() {
return {
plan: ['C#', 'Java', 'JavaScript']
}
},
})
const app_new = new Vue({
el: '#div_new',
components: {
'cpn': templateDivText,
},
})
<script src="https://cdn.jsdelivr.net/npm/vue#2.7.7/dist/vue.min.js"></script>
<div id="div_new">
<cpn>
<template #slot-name="{planall}">
<h4>{{planall[0]}}</h4>
<h4>{{planall[1]}}</h4>
<h4>{{planall[2]}}</h4>
</template>
</cpn>
</div>
<template id="template_div">
<div>
<slot name="slot-name" :planall="plan">
<ul>
<li v-for="item in plan"> {{ item }}</li>
</ul>
</slot>
</div>
</template>
Notes:
:planAll="" is shorthand for v-bind:planAll=""
<template #slot-name=""> is shorthand for <template v-slot:slot-name="">
when you only have one slot, you can remove the slot name altogether (it defaults to name="default")

How to use vue-select with b-pagination?

I'm trying to work with pagination using the v-pagination component from bootstrap-vue. It's working more or less as I want, however when I click on a certain page the component is closing.
I would like to know how I can prevent v-select from closing before I select one of the options.
My code:
<v-select
id="municipio"
v-model="municipioState"
:options="municipiosPaginaAtual"
:filterable="false"
placeholder="Selecione seu município"
label="municipio"
#search="buscarMunicipio"
>
<div slot="no-options">
Selecione o Estado!
</div>
<li
slot="list-footer"
class="pagination"
#click.prevent=""
>
<b-pagination
v-model="municipioPaginacao"
:total-rows="municipiosFiltradosQuantidade"
:per-page="limit"
first-number
last-number
/>
</li>
</v-select>
If there is a workaround or other option than v-select I would be pleased to know.
you can use #mouseup.native.capture.stop in the b-pagination tag to prevent closing the dropdown of the vue-select.
Also, in my opinion, you can use vue-multiselect for a better user experience, but it's up to you and your need. I just wanted to suggest it to you as another option.
I provided a simple snippet to show this:
Vue.component("v-select", VueSelect.VueSelect);
new Vue({
el: '#app',
data() {
return {
books: [{
title: "Old Man's War"
},
{
title: "The Lock Artist"
},
{
title: "HTML5"
},
{
title: "Right Ho Jeeves"
},
{
title: "The Code of the Wooster"
},
{
title: "Thank You Jeeves"
}
],
perPage: 2,
currentPage: 1,
rows: 6
}
},
})
<link href="https://unpkg.com/vue-select#3.16.0/dist/vue-select.css" rel="stylesheet"/>
<script src="https://unpkg.com/vue-select#3.16.0/dist/vue-select.js"></script>
<link type="text/css" rel="stylesheet" href="https://unpkg.com/bootstrap#4.5.3/dist/css/bootstrap.min.css" />
<link type="text/css" rel="stylesheet" href="https://unpkg.com/bootstrap-vue#2.21.2/dist/bootstrap-vue.css" />
<script src="https://unpkg.com/vue#2.6.12/dist/vue.min.js"></script>
<script src="https://unpkg.com/bootstrap-vue#2.21.2/dist/bootstrap-vue.min.js"></script>
<div id="app">
<v-select :options="books" label="title">
<li slot="list-footer">
<b-pagination #mouseup.native.capture.stop v-model="currentPage" :total-rows="rows" :per-page="perPage"></b-pagination>
</li>
</v-select>
</div>

VSCode Issue: Vue example doesn't work in Firefox / Chrome, but it works in jsfiddle

When I run the below code in Firefox or Chrome it gives me the following result:
{‌{ value }}
{‌{ value }}
{‌{ element }}
{‌{ element }}
{‌{ element }}
{‌{ element }}
Here is the actual code, when I run it in jsfiddle or stack overflow, it works just fine:
new Vue({
el: '#app',
data: {
testData: {
name: 'TESTOBJECT',
id: 10,
data: [1.67, 1.33, 0.98, 2.21]
}
}
});
<script src="https://cdn.jsdelivr.net/npm/vue#2.6.11"></script>
<div id="app">
<ul>
<li v-for="value in testData">
<template v-if="Array.isArray(value)">
<div v-for="element in value">{{ element }}</div>
</template>
<template v-else>
{{ value }}
</template>
</li>
</ul>
</div>
What could be the issue?
This is a VSCode issue. It does not show all characters, but stores them anyway. So it showed me the following:
{{ value }}
But what actually was saved inside the file and also processed by the browser was this:
To prevent this error, you can install the following VSCode extension: Highlight Bad Chars
Usually doing this as simple importing vuejs, taking your code, it works:
HTML
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="https://cdn.jsdelivr.net/npm/vue#2.6.11"></script>
</head>
<body>
<div id="app">
<ul>
<li v-for="value in testData">
<template v-if="Array.isArray(value)">
<div v-for="element in value">{{ element }}</div>
</template>
<template v-else>
{{ value }}
</template>
</li>
</ul>
</div>
<script src="app.js"></script>
</body>
</html>
app.js
new Vue({
el: "#app",
data: {
testData: {
name: "TESTOBJECT",
id: 10,
data: [1.67, 1.33, 0.98, 2.21]
}
}
});

v-select update var without v-model

Because I'm using v-select in a loop and I need to mark each of the loop iteration. I want to be able to add the selected value to an array, to check if the value exists and then add it or change it, something like this:
<v-card-text>
<ul>
<li v-for="ap in ['1','2','3']" :key="app">
<v-select
:items="findelem(appi)"
item-text="number"
item-value="number"
#input="check_update_array(_mark_ , _VALUE_)"
></v-select>
</li>
</ul>
</v-card-text>
</v-card>
and have check_update_array(mark , VALUE) as a method that checks and updates the array
is this possible ?
Edit:
I'll try to be more explicit, I want to do a v-for and to iterate over an array
for each selection I want to send to a function the position (value in iteration) and the selected value:
<ul>
<li v-for="ap in ARRAY">
<v-select
...
#input="check_update_array(ap, SELECTED_VALUE)"
></v-select>
</li>
</ul>
if I do #input="check_update_array" the SELECTED_VALUE is passed as param to the function and if I do #input="check_update_array(ap)" the position in iteration is passed.
Please have a look at the following code.
I took your code as a basis.
<!DOCTYPE html>
<html>
<head>
<link href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/#mdi/font#4.x/css/materialdesignicons.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/vuetify#2.x/dist/vuetify.min.css" rel="stylesheet">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no, minimal-ui">
</head>
<body>
<div id="app">
<v-app>
<v-card>
<v-card-text>
<ul>
<li v-for="(ap, index) in ['1','2','3']" :key="index">
<v-select
:items="findelem()"
item-text="number"
item-value="number"
label="My Select"
#input="check_update_array"
></v-select>
</li>
</ul>
<p>
{{ elements }}
</p>
</v-card-text>
</v-card>
</v-app>v-app>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue#2.x/dist/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vuetify#2.x/dist/vuetify.js"></script>
<script>
new Vue({
el: '#app',
vuetify: new Vuetify(),
data: function() {
return {
elements: []
}
},
methods: {
check_update_array(value) {
this.elements.push(value);
},
findelem(appi) {
return [{'number':'some element'},{'number':'another element'},{'number':'third element'}]
}
}
})
</script>
</body>
https://jsfiddle.net/ewatch/gn3eyxp2/
However some context was missing for me from your code regarding the "appi" parameter for the findelem function and the parameters "mark" and "VALUE" for the check_update_array function.
The function that is bind to the #input event receives the selected value as parameter already (see documentation here: https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/input_event).
I hope it helps.
Please let me know if you have any questions.
Best regards,
ewatch
Assuming you have a data named 'selected_array'
data() {
return {
selected_array:[]
}
}
Now in your component, it should be look like this.
<v-card>
<v-card-text>
<ul>
<li v-for="ap in ['1','2','3']" :key="app">
<v-select
:items="findelem(appi)"
item-text="number"
item-value="number"
#chanage="check_update_array"
return-object
></v-select>
</li>
</ul>
</v-card-text>
</v-card>
Then in your check_update_array function, you code should look like this.
check_update_array(data){
let item_exist = false
this.selected_array.forEach(item => { // check if value exists.
if (item.value == data.value) {
item.mark = data.mark
item_exist = true
return
}
})
if (!item_exist) { // if not existing then append
this.selected_array.push(
{mark:value, value:value}
)
}
item_exist = false // reset value
}
Just make sure that your findelem(appi) is an array of objects containing mark and value elements

Change value of v-model and update v-text-field

I made a fiddle where I tried to change one value of an array via code while it's used as v-model of a v-text-field from Vuetify.
The problem is, when I write something in the text field it does change the value correctly immediately, so I made a button that sets the value of the array in that specific index, it doesn't seem to be working, so I made another button to print it and it does work.
The first button changes the value to 0 as I'd like but in the v-text-field it still has the last typed value.
How to change the value of the v-text-field when the "reset" button is pressed?
new Vue({
el: '#app',
data() {
return {
model: [1, 2]
}
},
methods: {
restart() {
this.model[0] = 0
},
showInConsole() {
console.log("Current value:", this.model[0])
}
}
})
<!DOCTYPE html>
<html>
<head>
<link href='https://fonts.googleapis.com/css?family=Roboto:300,400,500,700|Material+Icons' rel="stylesheet">
<link href="https://unpkg.com/vuetify/dist/vuetify.min.css" rel="stylesheet">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no, minimal-ui">
</head>
<body>
<div id="app">
<v-layout row wrap>
<v-flex xs4>
<div class="headline">Change value of model[0]</div>
<div class="headline text-xs-center">{{model[0]}}</div>
</v-flex>
<v-flex xs8>
<v-text-field solo v-model="model[0]"></v-text-field>
</v-flex>
<v-flex xs12>
<v-btn #click="restart ()">Reset to 0</v-btn>
<v-btn #click="showInConsole ()">Show in console current value</v-btn>
</v-flex>
</v-layout>
</div>
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<script src="https://unpkg.com/vuetify/dist/vuetify.js"></script>
</body>
</html>
Link to my fiddle
It's Vue reactivity problem.
Basically, Vue cannot detect the changes to an array when you directly set an item with the index
You should use Vue.$set
restart () {
this.$set(this.model, 0, 0)
}

Categories

Resources