1

I created tree structure using DataTable from prime vue (https://tailwind.primevue.org/datatable/)

<template>
  <div class="card">
    <DataTable :value="visibleRows" dataKey="data.id" class="elements-list">
      <Column>
        <template #body="slotProps">
          <div
            :style="{
              paddingLeft: slotProps.data.level * 20 + 'px',
              display: 'flex',
              alignItems: 'center',
            }"
            class="expandable-row"
          >
            <span
              v-if="hasChildren(slotProps.data)"
              class="expander-icon"
              @click="toggleRow(slotProps.data)"
            >
              <i
                :class="[
                  'pi',
                  expandedRows[slotProps.data.data.id]
                    ? 'pi-chevron-down'
                    : 'pi-chevron-right',
                ]"
              ></i>
            </span>
            <span v-else style="width: 1em"></span>
            <Element
              :element-id="slotProps.data.data.id"
            />
          </div>
        </template>
      </Column>
    </DataTable>
  </div>
</template>

<script setup lang="ts">
import { ref, computed, watch, defineProps } from "vue";
import Element from "./Element.vue";
import DataTable from "primevue/datatable";
import Column from "primevue/column";

const props = defineProps({
  elements: {
    type: Array as () => any[],
    required: true,
    default: () => [],
  },
  expanded: {
    type: Boolean,
    required: false,
    default: false,
  },
});

const expandedRows = ref({});
const hasChildren = (rowData) => {
  return rowData.data.childElements && rowData.data.childElements.length > 0;
};
const toggleRow = (rowData) => {
  const id = rowData.data.id;
  if (expandedRows.value[id]) {
    delete expandedRows.value[id];
  } else {
    expandedRows.value[id] = true;
  }
};
const flattenTree = (elements, level = 0) => {
  let result = [];
  elements.forEach((element) => {
    result.push({
      data: element,
      level: level,
    });
    const isExpanded = expandedRows.value[element.id];
    if (isExpanded && element.childElements && element.childElements.length > 0) {
      const children = flattenTree(element.childElements, level + 1);
      result = result.concat(children);
    }
  });
  return result;
};
const visibleRows = computed(() => {
  return flattenTree(props.elements);
});
watch(
  () => props.expanded,
  () => {
    if (props.expanded) {
      expandAllNodes(props.elements);
    } else {
      expandedRows.value = {};
    }
  }
);
const expandAllNodes = (nodes) => {
  nodes.forEach((node) => {
    expandedRows.value[node.id] = true;
    if (node.childElements && node.childElements.length > 0) {
      expandAllNodes(node.childElements);
    }
  });
};
if (props.expanded) {
  expandAllNodes(props.elements);
}
</script>

<style lang="scss">
.elements-list {
  .p-datatable-tbody > tr > td {
    border: none;
    padding: 0;
  }
  .expandable-row {
    padding: 16px;
    border: 1px solid #dbdbdb;
    border-radius: 8px;
    margin-bottom: 0.5rem;
    width: 100%;
  }
  .expander-icon {
    cursor: pointer;
    margin-right: 0.5rem;
  }
}
</style>

It is not the result of my code but imagine I have a structure like this and I want to add drag and drop option into this component.

enter image description here

So each Element should be wrapped with draggable so that they can be drag and drop anywhere. I tried vuedraggable (https://github.com/SortableJS/Vue.Draggable?ref=madewithvuejs.com) for it but it didnt work at all.

Could you please help me?

4
  • Do you want to implement drag-and-drop functionality to reorder rows within the table? Commented Oct 3, 2024 at 7:03
  • you can check this . Commented Oct 3, 2024 at 7:15
  • @Nilesh yes. So in the picture that I shared,'Applications' row is an element. Same with 'Vue' and 'editor.app' and 'settings.app'. So I want to reorder them with drag and drop functionality. Commented Oct 3, 2024 at 8:05
  • Using TreeTable, you can achieve this. In the body slot, you need to write custom logic for drag-and-drop. If need help let me know. Commented Oct 3, 2024 at 11:44

1 Answer 1

1

Here I tried with custom logic in the Primevue TreeTable slot As there is no props for reOrder.

Here is the stackBlitz link fo the same

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Vue TreeTable with Drag-and-Drop</title>
    <link rel="stylesheet" href="https://unpkg.com/primevue/resources/themes/saga-blue/theme.css">
    <link rel="stylesheet" href="https://unpkg.com/primevue/resources/primevue.min.css">
    <link rel="stylesheet" href="https://unpkg.com/primeicons/primeicons.css">
    <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
    <script src="https://unpkg.com/primevue/umd/primevue.min.js"></script>
    <script src="https://unpkg.com/primevue/umd/treetable/treetable.min.js"></script>
    <script src="https://unpkg.com/primevue/umd/column/column.min.js"></script>
</head>
<body>
    <div id="app"></div>
    <script>
        const { ref, onMounted } = Vue;
        const TreeTable = PrimeVue.TreeTable;
        const Column = PrimeVue.Column;

        const App = {
            components: {
                TreeTable,
                Column
            },
            setup() {
                const nodes = ref([
                    {
                        key: '0',
                        data: { name: 'Applications', size: '100kb', type: 'Folder' },
                        children: [
                            {
                                key: '0-0',
                                data: { name: 'Vue', size: '25kb', type: 'Folder' },
                                children: [
                                    { key: '0-0-0', data: { name: 'vue.app', size: '10kb', type: 'Application' } },
                                    { key: '0-0-1', data: { name: 'native.app', size: '10kb', type: 'Application' } },
                                    { key: '0-0-2', data: { name: 'mobile.app', size: '5kb', type: 'Application' } }
                                ]
                            },
                            { key: '0-1', data: { name: 'editor.app', size: '25kb', type: 'Application' } },
                            { key: '0-2', data: { name: 'settings.app', size: '50kb', type: 'Application' } }
                        ]
                    },
                    {
                        key: '1',
                        data: { name: 'Cloud', size: '20kb', type: 'Folder' },
                        children: [
                            { key: '1-0', data: { name: 'backup-1.zip', size: '10kb', type: 'Zip' } },
                            { key: '1-1', data: { name: 'backup-2.zip', size: '10kb', type: 'Zip' } }
                        ]
                    },
                    {
                        key: '2',
                        data: { name: 'Desktop', size: '150kb', type: 'Folder' },
                        children: [
                            { key: '2-0', data: { name: 'note-meeting.txt', size: '50kb', type: 'Text' } },
                            { key: '2-1', data: { name: 'note-todo.txt', size: '100kb', type: 'Text' } }
                        ]
                    }
                ]);

                const columns = ref([
                    { field: 'name', header: 'Name', expander: true },
                    { field: 'size', header: 'Size' },
                    { field: 'type', header: 'Type' }
                ]);

                const draggedNode = ref(null);

                const onDragStart = (event, node) => {
                    draggedNode.value = node;
                    event.dataTransfer.effectAllowed = 'move';
                };

                const onDragOver = (event) => {
                    event.preventDefault();
                    event.dataTransfer.dropEffect = 'move';
                };

                const onDrop = (event, dropNode) => {
                    event.preventDefault();
                    if (draggedNode.value && draggedNode.value !== dropNode) {
                        // Find and remove the dragged node
                        const removeNode = (nodes, node) => {
                            for (let i = 0; i < nodes.length; i++) {
                                if (nodes[i] === node) {
                                    return nodes.splice(i, 1)[0];
                                }
                                if (nodes[i].children) {
                                    const result = removeNode(nodes[i].children, node);
                                    if (result) return result;
                                }
                            }
                        };
                        const dragged = removeNode(nodes.value, draggedNode.value);

                        // Find the parent of the drop node
                        const findParent = (nodes, node) => {
                            for (let i = 0; i < nodes.length; i++) {
                                if (nodes[i].children && nodes[i].children.includes(node)) {
                                    return nodes[i];
                                }
                                if (nodes[i].children) {
                                    const result = findParent(nodes[i].children, node);
                                    if (result) return result;
                                }
                            }
                            return null;
                        };

                        const parentNode = findParent(nodes.value, dropNode);

                        // Insert the dragged node at the drop location within the same level
                        if (parentNode) {
                            const dropIndex = parentNode.children.indexOf(dropNode) + 1;
                            parentNode.children.splice(dropIndex, 0, dragged);
                        } else {
                            const dropIndex = nodes.value.indexOf(dropNode) + 1;
                            nodes.value.splice(dropIndex, 0, dragged);
                        }
                    }
                    draggedNode.value = null;
                };

                return {
                    nodes,
                    columns,
                    onDragStart,
                    onDragOver,
                    onDrop
                };
            },
            template: `
                <div class="card">
                    <TreeTable :value="nodes" tableStyle="min-width: 50rem">
                        <Column v-for="col of columns" :key="col.field" :field="col.field" :header="col.header" :expander="col.expander">
                            <template #body="slotProps">
                                <div
                                    draggable="true"
                                    @dragstart="(event) => onDragStart(event, slotProps.node)"
                                    @dragover="onDragOver"
                                    @drop="(event) => onDrop(event, slotProps.node)"
                                >
                                    {{ slotProps.node.data[col.field] }}
                                </div>
                            </template>
                        </Column>
                    </TreeTable>
                </div>
            `
        };

        Vue.createApp(App).mount('#app');
    </script>
</body>
</html>

Sign up to request clarification or add additional context in comments.

4 Comments

Thanks for your answer @Nilesh but I see that I cannot drag and drop to upper level with your solution either.
You want the reordering to remain at the same level, correct? We won't make it nested.
I want the element can be drag and drop in any level. For example if the element is in the level 3 it can be dragged to level 1 or level 6, so anywhere should be possible
I edited the answer please check & confirm it.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.