Is it possible to make v-for variables dynamic in vue? - vue.js

<div class="col-3" v-for="n in 5" :key="n">
<h3>Table {{n}}</h3>
<draggable class="list-group" :list="`list${n}`" group="people" #change="log">
<div
class="list-group-item"
v-for="`(element, index) in list${n}`"
:key="element.name"
>
{{ element.name }} {{ index }}
</div>
</draggable>
</div>
Why can't I set the v-for or :list as a concatenated string? Is there any way around this?
Full code:
<template>
<div class="row">
<component
v-for="(component, index) in components"
:key="index"
:is="component"
/>
<div class="col-3" v-for="n in listNumber" :key="n">
<h3>Table {{n}}</h3>
<draggable class="list-group" :list="list${n}" group="people" #change="log">
<div
class="list-group-item"
v-for="(element, index) in list${n}"
:key="element.name"
>
{{ element.name }} {{ index }}
</div>
</draggable>
</div>
</div>
</template>
<script>
import draggable from "vuedraggable";
let id = 1;
export default {
name: "two-lists",
display: "Two Lists",
order: 1,
components: {
draggable,
list:[],
},
data() {
return {
list1: [
{ name: "John", id: 1 },
{ name: "Joao", id: 2 },
{ name: "Jean", id: 3 },
{ name: "Gerard", id: 4 }
]
},
{
list2: [
{ name: "Juan", id: 5 },
{ name: "Edgard", id: 6 },
{ name: "Johnson", id: 7 }
],
listNumber: 3,
}
},
created(){
console.log(this.list);
},
methods: {
add: function() {
this.list.push({ name: "Juan" });
},
replace: function() {
this.list = [{ name: "Edgard" }];
},
clone: function(el) {
return {
name: el.name + " cloned"
};
},
}
};
</script>

It's a bit difficult to understand exactly what you're trying to do but it looks like you're trying to get a specific list in the v-for loop.
One way to fix your issue would be to nest your lists inside of an object in your data like this:
data() {
return {
listNumber: 3,
lists: {
list1: [
{ name: "John", id: 1 },
{ name: "Joao", id: 2 },
{ name: "Jean", id: 3 },
{ name: "Gerard", id: 4 }
],
list2: [
{ name: "Juan", id: 5 },
{ name: "Edgard", id: 6 },
{ name: "Johnson", id: 7 }
],
},
};
And then in your code you can just do lists[`list${n}`] like this:
<div class="col-3" v-for="n in listNumber" :key="n">
<h3>Table {{n}}</h3>
<draggable class="list-group" :list="lists[`list${n}`]" group="people" #change="log">
<div
class="list-group-item"
v-for="(element, index) in lists[`list${n}`]"
:key="element.name"
>
{{ element.name }} {{ index }}
</div>
There is a lot more refactoring and other cleanup you could (and should) probably do but this should at least get you over this hurdle.

Related

Changing class on radio button change

My first column (Test1) is active, so the background turns orange. Why does the active class not change to the second column (Test2) when I click the radio button?
I'd like it to change the active class when the radio button is clicked.
I have the following template:
<template v-if="columns" v-for="(item, index) in columns" v-bind="item">
<div class="card-container"
:style="{'height': item.height+'%'}"
:class="{ active: item.active}">
<div class="card-inner">
<input type="radio" v-if="item.selectedDefault" checked v-model="currentInput" name="cardRadioGroup"
:value="item.id" v-on:change="updateDetails(item, index)"/>
<input type="radio" v-else v-model="currentInput" name="cardRadioGroup" :value="item.id"
v-on:change="updateDetails(item, index)"/>
<template v-if="item.gbAmount === null">
Onbeperkt
</template>
<template v-else>
{{ item.gbAmount }} GB
</template>
{{ currentInput }}
</div>
</div>
</template>
With the following Vue code:
import {onMounted} from "vue";
export default {
name: 'Card',
components: {Details},
data() {
const columns =
{
"Test1": {
"id": 1,
"gbAmount": 0,
"price": 5,
"selectedDefault": false,
"informationGet": ["bla", "bla"],
"informationUsing": "Boop.",
"recommended": false,
"height": 16.66,
"active": true,
},
"Test2": {
"id": 2,
"gbAmount": 1,
"price": 10,
"selectedDefault": false,
"informationGet": ["beh", "beh"],
"informationUsing": "Beep",
"recommended": false,
"height": 33.33,
"active": false,
},
}
return {
columns,
currentColumn: {},
checkedCard: [],
currentInput: null,
};
},
methods: {
updateDetails(item, index) {
this.currentColumn = {
...item,
name: index,
active: true,
}
console.log($(this.currentColumn));
},
},
setup() {
return {};
},
};
And CSS:
.active {
background: orange;
}
You update this.currentColumn but in template use this.columns. Correct will be:
updateDetails(item, index) {
Object.keys(this.columns).forEach(key => this.columns[key].active = false);
this.columns[index].active = true;
console.log(this.columns);
}
<template>
<div v-for="column in columns" :key="column.id">
<div
class="card-container"
:style="{ height: column.height + '%' }"
:class="{ active: column.id === currentInput }"
>
<div class="card-inner">
<input
type="radio"
name="cardRadioGroup"
:value="column.id"
v-model="currentInput"
/>
<template v-if="column.gbAmount === null"> Onbeperkt </template>
<template v-else> {{ column.gbAmount }} GB </template>
{{ currentInput }}
</div>
</div>
</div>
</template>
<script lang="ts">
import { ref } from 'vue'
const columns = [
{
id: 1,
gbAmount: 0,
price: 5,
selectedDefault: false,
informationGet: ['bla', 'bla'],
informationUsing: 'Boop.',
recommended: false,
height: 16.66,
},
{
id: 2,
gbAmount: 1,
price: 10,
selectedDefault: false,
informationGet: ['beh', 'beh'],
informationUsing: 'Beep',
recommended: false,
height: 33.33,
},
]
export default {
name: 'Card',
setup() {
const currentInput = ref(columns[0].id)
return {
columns,
currentInput,
}
},
}
</script>

Emitting two properties from child to parent

Can anyone help with this problem? How can I emit two properties from child component to parent on select input change? I can submit the value, see below, but would like to emit the value and the name property of segmentLocations object. This is the child component:
<template>
<div class="container">
<div>
<select v-model="selectedSegmentValue" v-on:change="$emit('selectLocation', $event.target.value)">
<option selected value="">Choose your location...</option>
<option v-for="segmentLocation in segmentLocations"
:value="segmentLocation.value"
:key="segmentLocation.value">
{{ segmentLocation.name }}>
</option>
</select>
</div>
</div>
</template>
<script>
export default {
data() {
return {
segmentLocations: [
{ value: "Residential", name: 'Residential building' },
{ value: "Workplace", name: 'Workplace' },
{ value: "Hospitality", name: 'Hospitality or Retail' },
{ value: "Real Estate", name: 'Real Estate' },
{ value: "Commercial Parking", name: 'Commercial Parking' },
{ value: "Fleets", name: 'Fleets' },
{ value: "Cities & Governments", name: 'Cities & Governments' },
{ value: "Corridor", name: 'Highway, Corridor or Petrol Station' }
],
}
}
};
</script>
And this is the parent:
<template>
<Segments
v-on:selectLocation="quote.selectedSegmentValue = $event"
:selectedValue="quote.selectedSegmentValue">
</Segments>
</template>
<script>
export default {
data() {
return {
quote: {
selectedSegmentValue: "",
selectedSegmentName: ""
},
};
},
</script>
I think the existing answers and mine share a similar technique, but I created a couple of simplified sample components based on your components.
Child component:
<template>
<div class="emit-two-properties">
<div class="form-group">
<label for="segment-location">Segment Location</label>
<select class="form-control" id="segment-location"
v-model="segmentLocation" #change="selectSegmentLocation">
<option v-for="(segLoc, index) in segmentLocations" :key="index"
:value="segLoc">{{ segLoc.name }}</option>
</select>
</div>
</div>
</template>
<script>
export default {
data() {
return {
segmentLocation: {},
segmentLocations: [
{ value: "Residential", name: 'Residential building' },
{ value: "Workplace", name: 'Workplace' },
{ value: "Hospitality", name: 'Hospitality or Retail' },
{ value: "Real Estate", name: 'Real Estate' },
{ value: "Commercial Parking", name: 'Commercial Parking' },
{ value: "Fleets", name: 'Fleets' },
{ value: "Cities & Governments", name: 'Cities & Governments' },
{ value: "Corridor", name: 'Highway, Corridor or Petrol Station' }
],
}
},
methods: {
selectSegmentLocation() {
this.$emit('select-segment-location-event', this.segmentLocation);
}
}
}
</script>
Parent component:
<template>
<div class="parent">
<h4>Parent.vue</h4>
<div class="row">
<div class="col-md-6">
<form #submit.prevent="submitForm">
<emit-two-properties #select-segment-location-event="updateSegmentLocation" />
<button class="btn btn-secondary">Submit</button>
</form>
<p><span>Selected Segment Location Value:</span>{{ segmentLocation.value }}</p>
<p><span>Selected Segment Location Name:</span>{{ segmentLocation.name }}</p>
</div>
</div>
</div>
</template>
<script>
import EmitTwoProperties from './EmitTwoProperties'
export default {
components: {
EmitTwoProperties
},
data() {
return {
segmentLocation: {}
}
},
methods: {
updateSegmentLocation(segLoc) {
this.segmentLocation = segLoc;
}
}
}
</script>
you can create a method to get name and value from event.target (remove value from the end of child emit):
changeSelectedSegment(selected){
this.selectedSegmentName = selected.name
this.selectedSegmentValue = selected.value
}
in the parent change v-on:selectLocation to v-on:selectLocation="changeSelectedSegment($event)"
you can define a method like this (this method emit an object with name and value properties to parent
)
methods: {
selectLocation(event){
if(event.target.value !== ''){
const item = this.segmentLocations.find( item => item.value === event.target.value)
this.$emit('selectLocation', {
name: item.name,
value: event.target.value
})
}
}
},
and change this line :
<select v-model="selectedSegmentValue" v-on:change="$emit('selectLocation', $event.target.value)">
to this:
<select v-model="selectedSegmentValue" v-on:change="selectLocation">

How to fill out just one list with vuedraggable?

I am using the following library
https://sortablejs.github.io/Vue.Draggable/#/custom-clone
specifically using the example:
https://github.com/SortableJS/Vue.Draggable/blob/master/example/components/clone-on-control.vue
<template>
<v-container fluid>
<div class="row">
<div class="col-3">
<h3>Draggable 1</h3>
<draggable
class="row wrap fill-height align-center sortable-list"
style="background: red;"
:list="list1"
:group="{ name: 'people', pull: pullFunction }"
#start="start"
>
<v-card class="list-group-item" v-for="element in list1" :key="element.id">
{{ element.name }}
</v-card>
</draggable>
</div>
<div class="col-3">
<h3>Draggable 2</h3>
<draggable class="dragArea list-group" :list="list2" group="people">
<v-card class="list-group-item" v-for="element in list2" :key="element.id">
{{ element.name }}
</v-card>
</draggable>
</div>
</div>
</template>
<script>
import draggable from "vuedraggable";
export default {
components: {
draggable
},
data(){
return {
list1: [
{ name: "Jesus", id: 1 },
{ name: "Paul", id: 2 },
{ name: "Peter", id: 3 }
],
list2: [
{ name: "Luc", id: 5 },
{ name: "Thomas", id: 6 },
{ name: "John", id: 7 }
],
controlOnStart: true
}
},
methods:{
pullFunction() {
return this.controlOnStart ? "clone" : true;
},
start({ originalEvent }) {
this.controlOnStart = originalEvent.ctrlKey;
}
}
}
</script>
What I need to do is fill only a list, that is, from Draggable 1 to Draggable 2, currently it is from Draggable 1 to Draggable 2 and from Draggable 2 to Draggable 1 but I don't need it that way.
Thank you

Nested Vue.Draggable list breaks when dragging cross-level

I want to have a draggable, nested list with Vue and used the Vue.Draggable component for it. I'm however stuck with updating nested lists.
The rendering is fine, the dragging is fine when you stay inside the same level. But dragging cross-level doesn't seem to do it (error in VueComponent.onDragStart).
html
<template>
<div class="fluid container">
<div class="col-md-6">
<draggable class="list-group" element="ul" v-model="list" :options="dragOptions" :move="onMove" #start="isDragging=true" #end="isDragging=false">
<transition-group type="transition" :name="'flip-list'">
<li class="list-group-item" v-for="element in list" :key="element.order">
<i :class="element.fixed? 'fa fa-anchor' : 'glyphicon glyphicon-pushpin'" #click=" element.fixed=! element.fixed" aria-hidden="true"></i>
{{element.name}}
<span class="badge">{{element.order}}</span>
<draggable v-if="element.notes" class="list-group" element="ul" :options="dragOptions" :move="onMove">
<transition-group class="list-group" :name="'flip-list'">
<li class="list-group-item" v-for="elementDeep in element.notes" :key="elementDeep.order">
{{elementDeep.name}} <span class="badge">{{ elementDeep.order }}</span>
</li>
</transition-group>
</draggable>
</li>
</transition-group>
</draggable>
</div>
<div class="list-group col-md-6">
<pre>{{listString}}</pre>
</div>
</div>
</template>
Vue js
<script>
import draggable from 'vuedraggable'
var folderOneReady = [
{
"name":"LOREM IPSUM",
"order":1,
"fixed":false
},
{
"name":"MAGNA ALIQUA",
"order":2,
"fixed":false
},
{
"name": "DOLOREM LAUDANTIUM",
"notes": [
{
"name": "Note level deep One",
"order":31,
"fixed":false
},
{
"name": "Note level deep two",
"order":32,
"fixed":false
},
{
"name": "Note level deep deep three",
"order":33,
"fixed":false
}
],
"order":3,
"fixed":false
},
{
"name":"SIT AMET",
"order":4,
"fixed":false
},
{
"name":"NEMO",
"order":5,
"fixed":false
},
{
"name":"ACCUSANTIUM",
"order":6,
"fixed":false
},
{
"name":"ESSE",
"order":7,
"fixed":false
},
{
"name":"DOLORES",
"order":8,
"fixed":false
}
];
export default {
name: 'hello',
components: {
draggable,
},
data () {
return {
list: folderOneReady,
editable:true,
isDragging: false,
delayedDragging:false
}
},
methods:{
onMove ({relatedContext, draggedContext}) {
const relatedElement = relatedContext.element;
const draggedElement = draggedContext.element;
return (!relatedElement || !relatedElement.fixed) && !draggedElement.fixed;
}
},
computed: {
dragOptions () {
return {
animation: 1,
group: 'description',
disabled: !this.editable,
ghostClass: 'ghost'
};
},
listString(){
return JSON.stringify(this.list, null, 2);
}
},
watch: {
isDragging (newValue) {
if (newValue){
this.delayedDragging= true
return
}
this.$nextTick( () =>{
this.delayedDragging =false
})
}
}
}
</script>
Anyone can direct me in the right direction?
It may not be the only problem but you need to set the list prop or use v-model for the nested draggable
Try something like:
<draggable v-if="element.notes" class="list-group" element="ul" :options="dragOptions" :move="onMove" :list="element.notes">

VueJS Custom dropdown buttons not working independently of each other

So I have a button dropdown which is working as expected but I have a bug where I can't reuse the same component as they both dont work independently of each other but instead when one is clicked the other changes too.
Please find a JSFiddle below and my code.
Thanks
<div id="app">
<div v-on:click="menuVisible = !menuVisible" class="dropdownBtn">
<div v-bind:class="{ active : menuVisible }"class="dropdownBtn__title">
<span v-if="!btnTitle">exploring</span>
<span v-else>{{ btnTitle }}</span>
</div>
<div v-show="menuVisible" class="dropdownBtn__content">
<ul v-for="reason in reasons">
<li v-on:click="updateTitle(reason)">{{ reason.title }}</li>
</ul>
</div>
</div>
<div v-on:click="menuVisible = !menuVisible" class="dropdownBtn">
<div v-bind:class="{ active : menuVisible }"class="dropdownBtn__title">
<span v-if="!btnTitle">exploring</span>
<span v-else>{{ btnTitle }}</span>
</div>
<div v-show="menuVisible" class="dropdownBtn__content">
<ul v-for="reason in reasons">
<li v-on:click="updateTitle(reason)">{{ reason.title }}</li>
</ul>
</div>
</div>
</div>
new Vue({
el: '#app',
data() {
return {
menuVisible: false,
btnTitle: false,
reasons: [{
title: 'family fun',
value: 1
},
{
title: 'relaxing',
value: 2
},
{
title: 'dining',
value: 3
},
{
title: 'meetings & events',
value: 4
},
{
title: 'what\'s on',
value: 5
},
{
title: 'gold',
value: 6
}]
}
},
methods: {
updateTitle($event) {
this.btnTitle = $event.title
}
}
})
So, for one, you haven't actually made a reusable component for the dropdown. You need to define that using Vue.component. Then you can use the tag for the custom component in the root template.
Secondly, if you are binding to the same data, then that is what will be reflected in both templates. You'll still need to pass separate data to the two separate dropdown components.
Vue.component('dropdown', {
template: `
<div v-on:click="menuVisible = !menuVisible" class="dropdownBtn">
<div v-bind:class="{ active : menuVisible }"class="dropdownBtn__title">
<span v-if="!btnTitle">exploring</span>
<span v-else>{{ btnTitle }}</span>
</div>
<div v-show="menuVisible" class="dropdownBtn__content">
<ul v-for="reason in reasons">
<li v-on:click="updateTitle(reason)">{{ reason.title }}</li>
</ul>
</div>
</div>
`,
props: ['menuVisible', 'btnTitle', 'reasons'],
methods: {
updateTitle($event) {
this.btnTitle = $event.title
}
}
})
new Vue({
el: '#app',
data() {
return {
fooReasons: [
{ title: 'family fun', value: 1 },
{ title: 'relaxing', value: 2 },
{ title: 'dining', value: 3 },
{ title: 'meetings & events', value: 4 },
{ title: 'what\'s on', value: 5 },
{ title: 'gold', value: 6 }
],
barReasons: [
{ title: 'family bar', value: 1 },
{ title: 'bar relaxing', value: 2 },
{ title: 'bar dining', value: 3 },
{ title: 'meetings & bars', value: 4 },
{ title: 'bar\'s on', value: 5 },
{ title: 'gold bar', value: 6 }
]
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.4.4/vue.min.js"></script>
<div id="app">
<dropdown :menu-visible="false" :btn-title="false" :reasons="fooReasons"></dropdown>
<dropdown :menu-visible="false" :btn-title="false" :reasons="barReasons"></dropdown>
</div>