0

I have nested draggable elements in my vue project and for drag event, I am using vue-draggable. So you can imagine my view something similar with this:

--> Element A
    --> Element B
        -->Element C
--> Element D
    --> Element E
-->Element F

So if the element has a child element, the element is expandable or collapsable. So you can see the view like this too:

> Element A
> Element D
> Element F

My problem is elements are expanded (like in the first position), For example if I drag Element E under Element A, Element B is being collapsed even Element B is expanded. So long story short I want to keep expandable positions how they are. So for this I have ElementsListComponent:

<template>
    <div>
        <draggable
            v-model="newElement"
            v-bind="dragOptions"
            handle=".handle"
            :move="handleMove"
            @end="handleEnd"
        >
            <NestedElement
                v-for="(element, index) in newElement"
                :key="`${element.id}_${index}`"
                :element="element"
                @open-element-sidebar="openElementSidebar"
                @edit-entity="editEntity"
                @delete-entity="successfullDelete"
                :is-expanded="isExpanded"
            />
        </draggable>
        <Modal
            v-if="showWarningModal"
            @close="showWarningModal = false"
        >
            <template>
                Confirm Modal
            </template>
            <template #footer>
                <div class="buttons">
                    <button type="button" class="btn btn-primary mr-2" @click="showWarningModal = false">
                        {{ "Confirm" }}
                    </button>
                    <button type="button" class="btn btn-secondary" @click="revertDraggedElement">Cancel</button>
                </div>
            </template>
        </Modal>
    </div>
</template>

<script>
export default {
    props: {
        elements: {
            type: Array,
            required: true,
            default: () => [],
        },
        isExpanded: {
            type: Boolean,
            required: false,
            default: false,
        },
    },
    data() {
        return {
            newElement: [],
            dragOptions: {
                group: "plan-elements",
                animation: 150,
                ghostClass: "ghost",
                forceFallback: true,
                fallbackOnBody: true,
                chosenClass: "dragging",
                pull: "clone",
            },
            isLoading: false,
            showWarningModal: false,
            draggedElement: {},
            parentElement: {},
            draggedElementInitialState: {},
        };
    },
    methods: {
        openElementSidebar(sidebarName, element) {
            this.$emit("open-element-sidebar", sidebarName, element);
        },
        successfullDelete() {
            this.$emit("delete-entity");
        },
        editEntity(id, type) {
            this.$emit("edit-entity", id, type);
        },
        handleMove(event) {
            if (event.draggedContext.element) {
                this.draggedElement = event.draggedContext.element;
            }

            // Store the initial state of the dragged element
            this.draggedElementInitialState = {
                index: event.draggedContext.index,
                container: event.draggedContext.container,
                element: event.draggedContext.element,
            };

            if (event.relatedContext.element) {
                this.parentElement = event.relatedContext.element;
            }
        },
        handleEnd() {
            this.showWarningModal = true;
        },
        revertDraggedElement() {
            // I dont know what to do here
        },
    },
    watch: {
        elements: {
            handler(newVal) {
                this.newElement = newVal;
            },
            immediate: true,
        },
    },
};
</script>

and here is my NestedElement component:

<template>
    <div>
        <Element
            :element="element"
            @toggle-accordion="toggleAccordion"
            @open-element-sidebar="openElementSidebar"
            @delete-entity="successfullDelete"
            @edit-entity="editEntity"
            :has-child="hasChildElement"
            :is-expanded="isOpen"
        />
        <draggable
            v-if="element.childElements && element.childElements.data"
            v-model="element.childElements.data"
            v-bind="dragOptions"
            class="child-elements-container"
            handle=".handle"
            :move="handleMove"
            @end="handleEnd"
        >
            <NestedElement
                v-if="isOpen || (isOpen && isAllOpen)"
                v-for="(childElement, index) in element.childElements.data"
                :key="`${childElement.id}_${index}`"
                :element="childElement"
                @open-element-sidebar="openElementSidebar"
                @delete-entity="successfullDelete"
                @edit-entity="editEntity"
                :is-open="isOpen"
                :is-expanded="isAllOpen"
            />
        </draggable>
        <Modal
            v-if="showWarningModal"
            @close="showWarningModal = false"
        >
            <template>
                Confirm Modal
            </template>
            <template #footer>
                <div class="buttons">
                    <button type="button" class="btn btn-primary mr-2" @click="showWarningModal = false">
                        {{ "Confirm" }}
                    </button>
                    <button type="button" class="btn btn-secondary" @click="revertDraggedElement">Cancel</button>
                </div>
            </template>
        </Modal>
    </div>
</template>

<script>
export default {
    name: "NestedElement",
    props: {
        element: {
            required: true,
            type: Object,
        },
        isExpanded: {
            type: Boolean,
            required: false,
            default: false,
        },
    },
    data() {
        return {
            dragOptions: {
                group: "plan-elements",
                animation: 150,
                ghostClass: "ghost",
                forceFallback: true,
                fallbackOnBody: true,
                chosenClass: "dragging",
                pull: "clone",
                revertClone: true,
            },
            isAllOpen: false,
            isOpen: false,
            showWarningModal: false,
            draggedElement: {},
            parentElement: {},
        };
    },
    computed: {
        hasChildElement() {
            if (this.element.childElements) {
                return this.element.childElements.data.length ? true : false;
            }
        },
    },
    methods: {
        openElementSidebar(sidebarName, element) {
            this.$emit("open-element-sidebar", sidebarName, element);
        },
        successfullDelete() {
            this.$emit("delete-entity");
        },
        editEntity(id, type) {
            this.$emit("edit-entity", id, type);
        },
        toggleAccordion(isOpen) {
            this.isOpen = isOpen;
        },
        handleMove(event) {
            if (event.draggedContext.element) {
                this.draggedElement = event.draggedContext.element;
            }

            if (event.relatedContext.element) {
                this.parentElement = event.relatedContext.element;
            }
        },
        handleEnd() {
            this.showWarningModal = true;
        },
    },
    watch: {
        isExpanded: {
            handler(newVal) {
                this.isAllOpen = newVal;
                this.isOpen = newVal;
            },
            immediate: true,
        },
    },
};
</script>

I hope I am clear. How can I receive this target?

4
  • You have to move the expanded state out of NestedElement's data into a store or up in the parent component. Use a unique identifier for each element. Then import this state from the store (or provide it via props, from parent) to each instance of <NestedElement />.
    – tao
    Commented Dec 14, 2023 at 2:54
  • I cannot use store so I need to do it with props but I didnt understand what you exactly mean. Commented Dec 14, 2023 at 5:36
  • "I cannot use store" doesn't make much sense. By definition, you need a store (which is a state management concept): an external entity (one source of truth) in charge of providing synchronised data consumed in more than one place. The idea here is that whenever the expanded state changes for any element, it gets stored into the store. And when the component is moved around, rather than handling its expanded state internally it reads it from the store and gets the saved value. Provide a runnable minimal reproducible example if you need more help. Use codesandbox.io or a similar node-like online editor.
    – tao
    Commented Dec 14, 2023 at 10:14
  • The store can take many forms: an actual vue store plugin (pinia (recommended), vuex (deprecated)), a reactive object, hooking up localStorage or sessionStorage to the app with getters/setters or, as mentioned earlier, the parent of the draggable lists. Whichever you use, it fulfils the role of a store. Everything considered, you should use pinia. It's tiny, intuitive, flexible and powerful. You'll never want to use anything else for state management.
    – tao
    Commented Dec 14, 2023 at 10:20

0