onDragend should be emitted from the target instead of source
Hello, if you have multiple nested groups, i think that onDragend should be emitted by the component where you're letting go the element.
Imagine that i have a Scholar Groups and Courses, and i can move courses between groups.
I have CoursesList.vue component and GroupsList.vue component, how can i send group_id params for example when i move courses between groups in my api calls? I've tried using onsort and onTransfer, but i dont want to make apiCalls every movement, if not until the user drops the element.
GroupList.vue
<script lang="ts" setup>
import { dragAndDrop } from "@formkit/drag-and-drop/vue";
const sanctumClient = useSanctumClient();
const groups = defineModel({
type: Array as PropType<Array<Group>>,
required: true,
});
const groupsRef = ref<any>();
const toast = useToast();
const state = ref({
position: 0,
group_id: 0,
});
dragAndDrop({
parent: groupsRef,
values: groups,
group: "groups",
onDragend: (event) => {
const group = groups.value[event.draggedNode.data.index];
const nextGroup = groups.value[event.draggedNode.data.index + 1];
const previousGroup = groups.value[event.draggedNode.data.index - 1];
state.value.group_id = group.id;
state.value.position = group.position;
if (previousGroup && nextGroup) {
state.value.position =
((previousGroup.position as number) + (nextGroup.position as number)) /
2;
} else if (previousGroup) {
state.value.position =
(previousGroup.position as number) +
(previousGroup.position as number) / 2;
} else if (nextGroup) {
state.value.position = (nextGroup.position as number) / 2;
}
moveGroup();
},
});
const onCourseCreated = (groupId: number) => {
const groupsRef = document.getElementById("group-" + groupId);
groupsRef!.scrollTop = groupsRef!.scrollHeight;
};
const { submit: moveGroup } = useSubmit(
() =>
sanctumClient("/groups/" + state.value.group_id + "/move", {
method: "PUT",
body: {
position: state.value.position,
},
}),
{
onSuccess: () => {
groups.value!.forEach((group) => {
if (group.id === state.value.group_id) {
group.position = state.value.position;
}
});
},
onError: () => {
refreshNuxtData("groups");
toast.add({
title: "Error",
description: "Error moving group",
icon: "i-ph-x",
color: "red",
});
},
},
);
</script>
<template>
<div class="inline-flex space-x-4 max-h-full">
<div
ref="groupsRef"
v-auto-animate
class="inline-flex space-x-4 max-h-full"
>
<div
v-for="group in groups"
:id="'group-' + group.id"
:key="group.id"
class="w-72 bg-gray-200 dark:bg-gray-800 rounded-lg max-h-full flex flex-col"
:group-id="group.id"
>
<div class="px-4 py-2 flex justify-between gap-2">
<GroupsUpdateGroupName :group="group" />
<GroupsDeleteGroup :group="group" />
</div>
<CoursesList v-model="group.courses" :group="group" />
<div class="px-4 py-2">
<CoursesCreateCourseButton
:group="group"
@course-created="onCourseCreated"
/>
</div>
</div>
</div>
<GroupsCreateGroupButton />
</div>
</template>
<style></style>
CourseList.vue
<script lang="ts" setup>
import { dragAndDrop } from "@formkit/drag-and-drop/vue";
import { useDebounceFn } from "@vueuse/shared";
const props = defineProps({
group: {
type: Object as PropType<Group>,
required: true,
},
});
const courses = defineModel({
type: Array as PropType<Array<Course>>,
required: true,
});
const coursesRef = ref<any>();
const state = ref({
position: 0,
group_id: 0,
course_id: 0,
});
dragAndDrop({
parent: coursesRef,
values: courses,
group: "courses",
onDragend: (event) => {
console.log("entrando en dragend desde " + props.group.id);
return;
const course = courses.value[event.draggedNode.data.index];
state.value.course_id = course.id;
const nextCourse = courses.value[event.draggedNode.data.index + 1];
const previousCourse = courses.value[event.draggedNode.data.index - 1];
state.value.group_id = course.id;
state.value.position = course.position;
if (previousCourse && nextCourse) {
state.value.position =
((previousCourse.position as number) +
(nextCourse.position as number)) /
2;
} else if (previousCourse) {
state.value.position =
(previousCourse.position as number) +
(previousCourse.position as number) / 2;
} else if (nextCourse) {
state.value.position = (nextCourse.position as number) / 2;
}
moveCourse();
},
onSort: (event) => {
console.log("entrando en sort desde " + props.group.id);
},
onTransfer: (event) => {
console.log("entrando en transfer desde " + props.group.id);
console.log(event);
},
});
const onMove = useDebounceFn(() => {
const course = courses.value[event.draggedNode.data.index];
}, 1000);
const sanctumClient = useSanctumClient();
const toast = useToast();
const { submit: moveCourse } = useSubmit(
() =>
sanctumClient("/courses/" + state.value.course_id + "/move", {
method: "PUT",
body: {
position: state.value.position,
},
}),
{
onSuccess: () => {
// groups.value!.forEach((group) => {
// if (group.id === state.value.group_id) {
// group.position = state.value.position;
// }
// });
},
onError: () => {
refreshNuxtData("groups");
toast.add({
title: "Error",
description: "Error moving group",
icon: "i-ph-x",
color: "red",
});
},
},
);
</script>
<template>
<ul
ref="coursesRef"
v-auto-animate
class="flex flex-col flex-1 overflow-y-auto overflow-x-hidden px-4 space-y-2"
>
{{
group.id
}}
<li
v-for="course in courses"
:key="course.id"
class="bg-white shadow dark:bg-gray-700 rounded-lg p-4 hover:shadow-lg transition-all flex"
>
<CoursesUpdateCourseName :course="course" />
<CoursesDeleteCourseButton :course="course" :group="group" />
</li>
</ul>
</template>
<style></style>
Great question. I think handleEnd should be invoked for both the parent in which it was dropped as well as where it began from (the source parent). I'm working on a big release and I'd like to add this in there. Do you think that would satisfy your use case?
Btw, I'm curious, we have an experimental insert plugin on the docs. Would that implementation serve you better for nested groups?
Hey there :v: I need exactly this behavior; also in a dynamic nested list context. Any suggestions for a workaround?