drag-and-drop icon indicating copy to clipboard operation
drag-and-drop copied to clipboard

onDragend should be emitted from the target instead of source

Open carlosvaldesweb opened this issue 1 year ago • 3 comments

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>

carlosvaldesweb avatar Nov 26 '24 17:11 carlosvaldesweb

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?

sashamilenkovic avatar Nov 26 '24 20:11 sashamilenkovic

Btw, I'm curious, we have an experimental insert plugin on the docs. Would that implementation serve you better for nested groups?

sashamilenkovic avatar Nov 26 '24 20:11 sashamilenkovic

Hey there :v: I need exactly this behavior; also in a dynamic nested list context. Any suggestions for a workaround?

YouMe2 avatar May 27 '25 13:05 YouMe2