Vuejs - Same component with different data - vue.js

I am trying to re-use the same component, but load the components with different data. I thought simply providing unique id's would do the trick, but no luck. I switched from a Vuex store for this data, to using a dataShare This is what I'm doing:
The components:
<logoHeader :panel=0 title="Add your logo / header" id="top" topPadding="pt-10" />
<logoHeader :panel=1 title="Add your main image" id="main" topPadding="pt-0"/>
So its the exact same component, with some different props and different ids
This is the logoHeader component:
<template>
<v-row
:class="topPadding"
align="center"
justify="center"
>
<v-col
align="center"
justify="center"
cols="12"
>
<v-hover v-slot:default="{ hover }">
<v-card
:elevation="hover ? 12 : 0"
class="mx-auto"
max-width="600"
>
<v-img
v-if="showImage"
:src="imageUrl"
max-width="600px"
class="pointer"
#click="panel = 0"
>
</v-img>
<v-icon
v-if="!showImage"
class="my_dark_purple_text"
size="100"
#click="sendToPanel"
>add_box</v-icon>
<p class="my_dark_purple_text">{{ title }}</p>
<p>URL {{ imageUrl }}</p>
<p>Show image? {{ showImage }}</p>
</v-card>
</v-hover>
</v-col>
</v-row>
</template>
<script>
import {mapGetters} from 'vuex';
import {mapActions} from 'vuex';
import {dataShare} from '../../../packs/fresh-credit.js';
export default {
props: ['panel', 'title', 'topPadding'],
data() {
return {
imageUrl: "",
showImage: false,
}
},
created() {
dataShare.$on('imageUrl', (data) => {
this.imageUrl = data;
});
dataShare.$on('showImage', (data) => {
this.showImage = data;
});
},
computed: {
...mapGetters('emailPanel', [
'returnPanel'
]),
},
methods: {
...mapActions('emailPanel', [
'updatePanel'
]),
sendToPanel() {
this.updatePanel(this.panel);
},
},
}
</script>
And then finally this is where the data enters the system:
<template>
<v-expansion-panel-content>
<h1 class="subtitle-1 font-weight-bold">Only images files accepted</h1>
<v-file-input
v-model="image"
accept="image/*"
label="Image Upload"
prepend-icon="add_a_photo"
color='#68007d'
></v-file-input>
<v-btn
:disabled="disableUpload"
color="#68007d"
class="white--text"
#click="sendImage"
>Submit</v-btn>
</v-expansion-panel-content>
</template>
<script>
import axios from 'axios';
import {dataShare} from '../../../../packs/fresh-credit.js';
export default {
data() {
return {
image: null,
disableUpload: true,
}
},
watch: {
image: function() {
if(this.image.size > 0){
this.disableUpload = false;
}
else{
this.disableUpload = true;
}
}
},
computed: {
},
methods: {
sendImage() {
let formData = new FormData();
formData.append('file', this.image);
axios.post('/admin/features/images', formData,
{
headers: {
'Content-Type': 'multipart/form-data'
}
}
).then(response => {
dataShare.$emit('imageUrl', response.data);
dataShare.$emit('showImage', true);
});
}
},
}
</script>
Where did I go astray?

Add the key property to the components and set it to different values (for example 1 and 2). If the key has different values Vue will differentiate them when rendering. Here is a basic explanation.

Related

using vuetify overlay in datatable

So I have a datatable of images that I want to expand in an overlay tag on click.
To do that, I created an array for each column of the table and I mapped it to its corresponding image.
Here's the code :
<template>
<v-app>
<app-navbar />
<v-main>
<div class="text-center">
<h3>
test {{ $route.params.name }}, {{ $route.query.status }},{{
$route.query.tag
}}
</h3>
</div>
<v-data-table
:headers="headers"
:items="imagesref"
:items-per-page="5"
class="elevation-1"
>
<template v-slot:[`item.index`]="{ index }">
{{index+1}}
</template>
<template v-slot:[`item.ref`]="{ index }">
<v-img :src="imagesref[index]" max-width="750" max-height="750" #click="expref[index] = !expref[index]"/>
<v-overlay :value="expref[index]"><v-img :src="imagesref[index]" max-width="1300" max-height="900" #click="expref[index] = !expref[index]"/> </v-overlay>
</template>
<template v-slot:[`item.test`]="{ index }">
<v-img :src="imagestest[index]" max-width="750" max-height="750" #click="exptest[index] = !exptest[index]"/>
<v-overlay :value="exptest[index]"><v-img :src="imagestest[index]" max-width="1300" max-height="900" #click="exptest[index] = !exptest[index]"/> </v-overlay>
</template>
<template v-slot:[`item.res`]="{ index }">
<v-img :src="imagesresult[index]" max-width="750" max-height="750" #click="expres[index] = !expres[index]"/>
<v-overlay :value="expres[index]"><v-img :src="imagesresult[index]" max-width="1300" max-height="900" #click="expres[index] = !expres[index]"/> </v-overlay>
</template>
<template #[`item.Scrubber`]="{ index }">
<nuxt-link :to="{ path: 'scrubber', query: { imageref: imagesref[index],imagetest:imagestest[index],imageres:imagesresult[index] }}">Show Scrubber</nuxt-link>
</template>
</v-data-table>
</v-main>
</v-app>
</template>
<script>
import appNavbar from "../../../components/appNavbar.vue"
import axios from "axios"
export default {
components: { appNavbar },
name: "App",
data() {
return {
expref:[],
exptest:[],
expres:[],
items: [],
imagesref: [],
imagestest: [],
imagesresult: [],
headers: [
{ text: 'index',value: 'index',sortable:false},
{ text: 'Imagesref', value: 'ref',sortable:false },
{ text: 'Imagestest', value: 'test',sortable:false },
{ text: 'Imagesresult', value: 'res',sortable:false },
{ text: 'Scrubber', value: 'Scrubber',sortable:false },
]
}
},
async created() {
try {
const res = await axios.get(`http://localhost:3004/tests`, {
params: { name: this.$route.params.name },
})
this.items = res.data
this.imagesref = res.data[0].refimages
this.imagestest = res.data[0].testimages
this.imagesresult = res.data[0].resultimages
for (let i of this.imagesref){
this.expref.push(false);
this.exptest.push(false);
this.expres.push(false);
}
} catch (error) {
console.log(error)
}
}
}
</script>
<style scoped>
</style>
When I tested it, after I click on the image the corresponding variable in the array changes its value to true but the overlay is not getting displayed but somehow when I change the value manually on devtools it works.Does someone have any idea what's going on and how can i make it work ?

Vuejs Error: Property or method "cart" is not defined on the instance but referenced during render. Property"cartItems" is not defined

I have an error trying to get data from my vuex store.
Property or method "cart" is not defined on the instance but referenced during render. Make sure that this property is reactive, either in the data option, or for class-based components, by initializing the property.
Property or method "cartItems" is not defined on the instance but referenced during render. Make sure that this property is reactive, either in the data option, or for class-based components, by initializing the property
pages/cart.vue
<template>
<div>
<v-container>
<section>
<h2 class="my-4">Your Cart</h2>
<v-divider class="my-4"></v-divider>
<h3>Total Amount: ${{ cart }}</h3>
<CartItem
v-for="item in cartItems"
:key="item.productId"
:prod-id="item.productId"
:name="item.name"
:image="item.image"
:price="item.price"
:qty="item.qty"
></CartItem>
</section>
</v-container>
</div>
</template>
<script>
import CartItem from '../components/CartItem'
import { mapGetters } from 'vuex'
export default {
components: {
CartItem,
computed: {
...mapGetters('cart',['totalSum'])
},
cartItems(){
return this.$store.getters['cart/products'];
}
}
}
</script>
<style scoped>
h2{
text-align: center;
}
h3{
text-align: center;
}
</style>
./components/CartItem.vue
<template>
<v-container class="my-5">
<v-row>
<v-col
sm="6"
md="4"
>
<v-card outlined>
<v-img :src="image" height="200px" />
<v-card-title> {{ name}} </v-card-title>
<v-card-subtitle> ${{ price }}</v-card-subtitle>
<v-card-subtitle> Quantity <strong>{{ qty }}</strong> </v-card-subtitle>
<v-card-actions>
<div>Total: ${{ itemTotal }}</div>
<v-btn #click="remove()" color="success" outlined >
<v-icon small left> add </v-icon>
Remove
</v-btn>
</v-card-actions>
</v-card>
</v-col>
</v-row>
</v-container>
</template>
<script>
export default {
props: ['prodId', 'name', 'image', 'price', 'qty'],
computed: {
itemTotal() {
return( this.price * this.qty).toFixed(2);
}
},
methods: {
remove(){
this.$store.dispatch('cart/removeFromCart', {productId: this.prodId})
}
}
}
</script>
store/modules/cart.js
export default {
namespaced: true,
state(){
return{
items: [],
total: 0,
qty: 0
}
},
mutations: {
addProductToCart(state, payload) {
const productData = payload;
const productInCartIndex = state.items.findIndex(
(ci) => ci.productId === productData.id
);
if (productInCartIndex >= 0) {
state.items[productInCartIndex].qty++;
} else {
const newItem = {
productId: productData.id,
title: productData.title,
image: productData.image,
price: productData.price,
qty: 1,
};
state.items.push(newItem);
}
state.qty++;
state.total += productData.price;
},
removeProductFromCart(state, payload) {
const prodId = payload.productId;
const productInCartIndex = state.items.findIndex(
(cartItem) => cartItem.productId === prodId
);
const prodData = state.items[productInCartIndex];
state.items.splice(productInCartIndex, 1);
state.qty -= prodData.qty;
state.total -= prodData.price * prodData.qty;
},
},
actions: {
addToCart(context, payload) {
const prodId = payload.id;
const products = context.rootGetters['prods/products'];
const product = products.find(prod => prod.id === prodId);
context.commit('addProductToCart', product);
},
removeFromCart(context, payload) {
context.commit('removeProductFromCart', payload);
}
},
getters: {
products(state) {
return state.items;
},
totalSum(state) {
return state.total;
},
quantity(state) {
return state.qty;
}
}
}
The property cartItems should be added to the computed option like :
export default {
components: {
CartItem,
},
computed: {
...mapGetters('cart',['totalSum']),
cartItems(){
return this.$store.getters['cart/products'];
}
}
}

Vue Passing Data from component to another component

I am making a "create account" flow for user and I am not able to pass data from one component to another. The first component has radio buttons with options of "tenant", "landlord", "contractor". Once the user selects "tenant", then the data should pass to the next step where they fill out a form with name and all that good stuff.. once they submit, it should all go together to the back end.
acc-for.vue with radio buttons component below.
<template>
<div>
<v-app
style="background-image: url('https://blog.modsy.com/wp-content/uploads/2019/06/D2_Full.jpg')"
>
<v-container class="pa-12">
<v-row>
<v-card class="pa-16">
<v-card-title>
Are you a?
</v-card-title>
<v-radio-group v-model="selectedValue" #change="selectedAcc">
<v-radio
v-for="account in accountType"
:key="account.name"
:value="account.name"
:label="account.name"
></v-radio>
</v-radio-group>
<v-row>
<v-btn rounded color="black" class="white--text" href="/login"
>Back</v-btn
>
<v-btn
rounded
color="black"
class="white--text"
#click="selected(accountSelected)"
>Next</v-btn
>
<!-- href="/create-acc" -->
</v-row>
</v-card>
</v-row>
</v-container>
</v-app>
</div>
</template>
<script>
import { mapMutations } from "vuex";
export default {
data() {
return {
selectedValue: false,
};
},
computed: {
accountType() {
return this.$store.state.accountType;
},
selected() {
return this.$store.state.selectedAccType;
},
},
methods: {
...mapMutations(["SELECTED_ACCOUNT_TYPE"]),
selectedAcc(e) {
this.$emit("selected-accountType", e);
},
},
};
</script>
<style></style>
createAccount.vue this component has the form for the fName and lName and all that good stuff..
<template>
<div>
<v-app
style="background-image: url('https://blog.modsy.com/wp-content/uploads/2019/06/D2_Full.jpg')"
>
<v-container class="pa-12">
<v-row>
<v-card class="pa-16">
<v-card-title>
{{ selectedTypeUser }}
</v-card-title>
<v-form>
<v-text-field
class="pa-4"
type="text"
v-model="newUser_fName"
label="First Name"
/>
<v-text-field
class="pa-4"
type="text"
v-model="newUser_lName"
label="Last Name"
/>
<v-text-field
class="pa-4"
type="text"
v-model="newUser_email"
label="Email"
/>
<v-text-field
class="pa-4"
type="text"
v-model="newUser_password"
label="Password"
/>
<v-row>
<v-btn rounded color="black" class="white--text" href="/acc-for"
>Back</v-btn
>
<v-btn
#click="registerUser"
rounded
color="black"
class="white--text"
>Next</v-btn
>
</v-row>
</v-form>
</v-card>
</v-row>
</v-container>
</v-app>
</div>
</template>
<script>
import { mapMutations } from "vuex";
import axios from "axios";
export default {
data() {
return {
newUser_fName: "",
newUser_lName: "",
newUser_email: "",
newUser_password: "",
};
},
methods: {
...mapMutations(["ADD_USER"]),
registerUser() {
let config = {
headers: {
"Content-Type": "application/json",
},
};
axios
.post(
"http://localhost:7876/createUser",
{
fName: this.newUser_fName,
lName: this.newUser_lName,
email: this.newUser_email,
password: this.newUser_email,
},
config
)
.then((response) => {
console.log(response.statusText);
})
.catch((e) => {
console.log("Error: ", e.response.data);
});
},
},
};
</script>
store.js (state) is below
// import vue from "vue";
import vuex from "vuex";
import axios from "axios";
import Vue from "vue";
Vue.use(vuex, axios);
export default new vuex.Store({
state: {
users: [],
accountType: [
{ name: "Tenant" },
{ name: "Landlord" },
{ name: "Contractor" }
],
selectedAccType: [],
},
mutations: {
ADD_USER: (state, payload) => {
state.users.push(payload.user);
},
SELECTED_ACCOUNT_TYPE: (state, payload) => {
state.selectedAccType.push(payload)
}
},
actions: {
addUser: ({ commit }, payload) => {
commit("ADD_USER", payload);
},
selectAcc: ({ commit }, payload) => {
commit("SELECTED_ACCOUNT_TYPE", payload)
}
},
// getters: {
// addAccountType(state, e) {
// state.accountType.push(e)
// },
// },
});
I see this on the button:
<v-btn rounded
color="black"
class="white--text"
#click="selected(accountSelected)"
>Next</v-btn
>
But I'm not finding a method with that name in your attached code. Double check that your names are all matched up and that you are calling the correct function on the click event.

want to use vuetify snackbar as a global custom component in vuejs

i used snackbar to show success messages in vuejs. i want to make a global custom snackbar component.
<template>
<div name="snackbars">
<v-snackbar
v-model="snackbar"
:color="color"
:timeout="timeout"
:top="'top'"
>
{{ text }}
<template v-slot:action="{ attrs }">
<v-btn dark text v-bind="attrs" #click="snackbar = false">
Close
</v-btn>
</template>
</v-snackbar>
</div>
</template>
<script>
export default {
props: {
snackbar: {
type: Boolean,
required: true,
},
color: {
type: String,
required: false,
default: "success",
},
timeout: {
type: Number,
required: false,
default: 3000,
},
text: {
type: String,
required: true,
},
},
};
</script>
then i import this as a component in my every form like this.
<SnackBar :snackbar="snackbar" :color="color" :text="text" />
but my issue is i can't use snackbar as a prop in my child component. it shows me this error.
Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value. Prop being mutated: "snackbar"
how can i fix this issue. can anyone help me?
I realize this is old, but thanks to google, I am going to add my solution.
I use this, because I don't see the point of using vuex for a snackbar. It's more work then needed.
Create a vue component named vtoast
<template>
<v-snackbar
:color="color"
:timeout="timer"
v-model="showSnackbar"
bottom
right
>
<v-icon left>{{icon}}</v-icon>{{message}}
</v-snackbar>
</template>
<script>
export default {
name: "vtoast",
data() {
return{
showSnackbar: false,
message: '',
color: 'success',
icon: 'mdi-check',
timer: 3000
}
},
methods:{
show(data) {
this.message = data.message || 'missing "message".'
this.color = data.color || 'success'
this.timer = data.timer || 3000
this.icon = data.icon || 'mdi-check'
this.showSnackbar = true
}
}
}
</script>
Somewhere in the root of your main app, add the following. (I usually put mine in App.vue)
<template>
...
<!-- toast -->
<vtoast ref="vtoast"/>
...
</template>
<script>
import vtoast from '#/your/vtoast/directory/vtoast'
export default{
name: 'App', //or whatever your root is
components:{
vtoast
},
mounted() {
this.$root.vtoast = this.$refs.vtoast
},
}
</script>
And access it like so...
this.$root.vtoast.show()
this.$root.vtoast.show({message: 'Ahoy there!'})
i found a way to fix my solution using vuex.
<template>
<div name="snackbars">
<v-snackbar v-model="show" :color="color" :timeout="timeout" :top="'top'">
{{ text }}
<template v-slot:action="{ attrs }">
<v-btn dark text v-bind="attrs" #click="show = false">
Close
</v-btn>
</template>
</v-snackbar>
</div>
</template>
<script>
export default {
created() {
this.$store.subscribe((mutation, state) => {
if (mutation.type === "snackbar/SHOW_MESSAGE") {
this.text = state.snackbar.text;
this.color = state.snackbar.color;
this.timeout = state.snackbar.timeout;
this.show = true;
}
});
},
data() {
return {
show: false,
color: "",
text: "",
timeout: 0,
};
},
};
</script>
in my vuex module i wrote like this
export default {
namespaced: true,
state: {
text: "",
color: "",
timeout: "",
},
mutations: {
SHOW_MESSAGE(state, payload) {
state.text = payload.text;
state.color = payload.color;
state.timeout = payload.timeout;
},
},
actions: {
showSnack({ commit }, payload) {
commit("SHOW_MESSAGE", payload);
},
},
};
then i import snackbar child component into my parent component and send data like this.
...mapActions("snackbar", ["showSnack"]),
saveDetails() {
this.showSnack({
text: "Successfully Saved!",
color: "success",
timeout: 3500,
});
}
Another solution is to use a computed value with getter and setter.
Using options api
<template>
<v-snackbar v-model="show" :color="color">
{{ message }}
<template v-slot:action="{ attrs }">
<v-btn text v-bind="attrs" #click="show = false">Close</v-btn>
</template>
</v-snackbar>
</template>
<script>
import { mapGetters } from 'vuex';
export default {
computed: {
...mapGetters({
message: 'snackbar/message',
color: 'snackbar/color'
}),
show: {
get() {
return this.$store.state.snackbar.show
},
set(v) {
this.$store.commit('snackbar/SET_SHOW', v)
}
}
}
}
</script>
Using composition api plugin
<template>
<v-snackbar v-model="show" :color="color">
{{ message }}
<template v-slot:action="{ attrs }">
<v-btn text v-bind="attrs" #click="show = false">Close</v-btn>
</template>
</v-snackbar>
</template>
<script>
import { defineComponent, computed } from '#vue/composition-api';
export default defineComponent({
setup(_props, { root }) {
const show = computed({
get: () => root.$store.state.snackbar.show,
set: (v) => root.$store.commit('snackbar/SET_SHOW', v),
});
const message = computed(() => root.$store.state.snackbar.message);
const color = computed(() => root.$store.state.snackbar.color);
return {
show,
message,
color,
};
},
});
</script>
A better implementation using composables here https://gist.github.com/wobsoriano/2f3f0480f24298e150be0c13f93bac20
You are having a prop and the same in data.
remove snackbar from data() as it is available from prop.
<script>
export default {
props: {
snackbar: {
type: Boolean,
required: true,
},
color: {
type: String,
required: false,
default: "success",
},
timeout: {
type: Number,
required: false,
default: 3000,
},
text: {
type: String,
required: true,
},
}
};
</script>
This is what I did with Options API with mere props and events;
Here is the Snackbar.vue component
<template>
<div class="text-center">
<v-snackbar
transition="true"
bottom
right
v-model="show"
:color="snackbar.color"
:timeout="snackbar.timeout"
class="snackbar-shadow"
>
<div class="d-flex align-start alert-notify">
<v-icon size="24" class="text-white mr-5">{{ snackbar.icon }}</v-icon>
<p class="mb-0">
<span class="font-size-root font-weight-600">{{
snackbar.title
}}</span>
<br />
{{ snackbar.message }}
</p>
</div>
<template v-slot:action="{ attrs }">
<v-btn
icon
elevation="0"
max-width="136"
:ripple="false"
height="43"
class="font-weight-600 text-capitalize py-3 px-6 rounded-sm"
color="rgba(255,255,255, .85)"
text
v-bind="attrs"
#click="show = false"
>
<v-icon size="13">fas fa-times</v-icon>
</v-btn>
</template>
</v-snackbar>
</div>
</template>
<script>
export default {
name: "snackbar",
props: {
snackbar: Object,
},
computed: {
show: {
get() {
return this.snackbar.visible;
},
set(value) {
this.$emit("closeSnackbar", value);
},
},
},
};
</script>
Here is the App.vue component
<template>
<!-- Snackbar -->
<snackbar :snackbar="snackbar" #closeSnackbar="SnackbarClose"></snackbar>
</template>
<script>
export default {
name: "app",
data() {
return {
snackbar: {
visible: false,
timeout: 2000,
color: "#11cdef",
title: "Hello",
message: null,
icon: "fas fa-bell",
},
};
},
created: { this.SnackbarShow(); }
methods: {
SnackbarShow() {
this.snackbar.visible = true;
this.snackbar.message = "Hola!👋 I'm a snackbar";
},
SnackbarClose() {
this.snackbar.visible = false;
},
},
};
</script>

Updating Vuex following Drag and Drop using vuedraggable

I have created a Vue application that uses Vuetify, Vuex and vuedraggable. The component functions as expected with drag and drop of v-card containing the associated information but I have not been able to find a way to identify the destination so that I can update the Vuex Store
<template>
<v-container>
<v-row justify='center'>
<v-col v-for="stage in stages" :key="stage.value">
<span class="card-text-bold">{{ stage.heading }}</span>
<draggable :list="buckets[stage.name]" group="openTasks" :move="handleStatus">
<v-card color="commentCard" class="list-group mt-3" v-for="task in buckets[stage.name]" :key="task._id">
<v-card-text>
<span class="card-text-bold">{{ task.title}}</span>
</v-card-text>
<v-card-text>
<span class="card-text"> {{ task.description}} </span>
</v-card-text>
<v-card-text>
{{ task.assignee}}<v-icon right #click="goToTask(task._id)">edit</v-icon>
</v-card-text>
</v-card>
</draggable>
</v-col>
</v-row>
</v-container>
</template>
<script>
import { mapGetters } from 'vuex';
import draggable from 'vuedraggable';
import moment from 'moment';
export default {
name: 'Tasks',
components: {
draggable
},
data() {
return {
stages: [
{ heading: 'Created', name: 'created' },
{ heading: 'Assigned', name: 'assigned' },
{ heading: 'In Progress', name: 'in progress' },
{ heading: 'On Hold', name: 'on hold' },
{ heading: 'Complete', name: 'complete' },
{ heading: 'Closed', name: 'closed' }
]
};
},
created() {
this.handleGetTasks();
},
computed: {
...mapGetters(['user', 'loading', 'tasks', 'buckets'])
},
methods: {
getTimeFromNow(time) {
return moment(new Date(time)).fromNow();
},
goToTask(taskId) {
this.$router.push(`/task/${taskId}`);
},
handleGetTasks() {
const userRole = localStorage.getItem('role');
const fullname = localStorage.getItem('fullname');
switch (userRole) {
case 'Manager': {
this.$store.dispatch('getAllTasks');
break;
}
case 'Requester': {
this.$store.dispatch('getRequestTasks', {
fullname: fullname
});
break;
}
case 'Assignee': {
this.$store.dispatch('getAssignTasks', {
fullname: fullname
});
break;
}
}
},
stateTasks(target) {
console.log('state', target);
if (this.buckets[target] > 0) return this.buckets[target];
else return [];
},
handleStatus(evt) {
const movedId = evt.draggedContext.element._id;
var targetStage;
console.log('source', movedId, 'target', Object.keys(evt.relatedContext.list));
}
}
};
</script>
The source list for data used here is the 'buckets' object which contains an array of tasks for each of the task stages. Drag and Drop moves the task cards from the source to the target but I have been able to find a way to identify the target list.
Please provide advice about how I can determine which of the lists in the 'buckets' object should be updated following the move.
Thanks
Des
If you take a look at the documentation, you'll see that vuedraggable has a #end event which will trigger after moving an element. When logging that event you'll notice quite a lot of useful information - such as oldIndex, newIndex and more..
<draggable :list="list" #end="onEnd">
Source: https://github.com/SortableJS/Vue.Draggable#events
I was able to solve the problem by parsing the HTML element of the move event.
The header for each of the stage columns can then be used as the identifier of the target list.
Here is the code
<template>
<v-container>
<v-row justify='center'>
<v-col v-for="(stage, index) in stages" :key='index'>
<draggable :list="buckets[stage]" group="openTasks" :move='handleMove'>
<span slot="header" :tag="stage" class="card-text-bold">{{ stage }}</span>
<v-card color="commentCard" class="list-group mt-3" v-for="task in buckets[stage]" :key="task._id">
<v-card-text>
<span class="card-text-bold">{{ task.title}}</span>
</v-card-text>
<v-card-text>
<span class="card-text"> {{ task.description}} </span>
</v-card-text>
<v-card-text>
{{ task.assignee}}<v-icon right #click="goToTask(task._id)">edit</v-icon>
</v-card-text>
</v-card>
</draggable>
</v-col>
</v-row>
</v-container>
</template>
<script>
import { mapGetters } from 'vuex';
import draggable from 'vuedraggable';
import moment from 'moment';
export default {
name: 'Tasks',
components: {
draggable
},
data() {
return {
stages: ['created', 'assigned', 'in progress', 'on hold', 'complete', 'archive']
};
},
created() {
this.handleGetTasks();
},
computed: {
...mapGetters(['user', 'loading', 'tasks', 'buckets'])
},
methods: {
getTimeFromNow(time) {
return moment(new Date(time)).fromNow();
},
goToTask(taskId) {
this.$router.push(`/task/${taskId}`);
},
handleGetTasks() {
const userRole = localStorage.getItem('role');
const fullname = localStorage.getItem('fullname');
switch (userRole) {
case 'Manager': {
this.$store.dispatch('getAllTasks');
break;
}
case 'Requester': {
this.$store.dispatch('getRequestTasks', {
fullname: fullname
});
break;
}
case 'Assignee': {
this.$store.dispatch('getAssignTasks', {
fullname: fullname
});
break;
}
}
},
handleMove(evt) {
const movedId = evt.draggedContext.element._id;
var stageTag = evt.relatedContext.component.rootContainer.firstElementChild.innerText;
if (this.stages.includes(stageTag)) {
this.$store.dispatch('changeStatus', {
taskId: movedId,
status: stageTag
});
}
}
}
};
Decoding the component HTML is done with this
evt.relatedContext.component.rootContainer.firstElementChild.innerText;