Can Vue.Draggable be used with Vuetify v-data-table and allow utilisation of table v-slot:item.<name>?
Asked Answered
P

2

5

Vuetify v-data-table supports several types of slots: v-slot:body, v-slot:item and v-slot:item.<name>.

We have been using v-slot:item.<name> extensively, as these provides a flexible way to style and process content in individual columns AND allow the table headers to be programmatically changed.

I'd like to add draggability to my v-data-table rows and have got this working using Vue.Draggable.

However the draggable component requires use of the v-data-table v-slot:body i.e. taking control of the full body of the table and thereby losing the flexibility of v-slot:item.<name>.

Is there a way these two components can be used together and provide v-slot:item.<name> support?

Polynesian answered 7/2, 2021 at 17:3 Comment(0)
P
11

I have created a DataTableRowHandler component which allows v-slot:item.<name> support.

This is placed inside the draggable component, inserts the table <tr> element and feeds off the same "headers" array to insert <td> elements and v-slot:item.<name> entries. If no v-slot:item.<name> is defined then the cell value is output, in the same way that v-data-table works.

Here is the example component usage:

<v-data-table
  ref="myTable"
  v-model="selected"
  :headers="headers"
  :items="desserts"
  item-key="name"
  class="elevation-1"
>
  <template v-slot:body="props">
    <draggable
      :list="props.items"
      tag="tbody"
      :disabled="!allowDrag"
      :move="onMoveCallback"
      :clone="onCloneCallback"
      @end="onDropCallback"
    >
      <data-table-row-handler
        v-for="(item, index) in props.items"
        :key="index"
        :item="item"
        :headers="headers"
        :item-class="getClass(item)"
      >
        <template v-slot:item.lock="{ item }">
          <v-icon @click="item.locked = item.locked ? false : true">{{
            item.locked ? "mdi-pin-outline" : "mdi-pin-off-outline"
          }}</v-icon>
        </template>

        <template v-slot:item.carbs="{ item }">
          {{ item.carbs }}
          <v-icon>{{
            item.carbs > 80
              ? "mdi-speedometer"
              : item.carbs > 45
              ? "mdi-speedometer-medium"
              : "mdi-speedometer-slow"
          }}</v-icon>
        </template>
      </data-table-row-handler>
    </draggable>
  </template>
</v-data-table>

Here is the DataTableRowHandler component code

<template>
  <tr :class="getClass">
    <td v-for="(header, index) in headers" :key="index">
      <slot :item="item" :name="columnName(header)">
        <div :style="getAlignment(header)">
          {{ getNonSlotValue(item, header) }}
        </div>
      </slot>
    </td>
  </tr>
</template>

<script>
export default {
  name: "DataTableRowHandler",
  components: {},
  props: {
    itemClass: {
      type: String,
      default: "",
    },
    item: {
      type: Object,
      default: () => {
        return {};
      },
    },
    headers: {
      type: Array,
      default: () => {
        return [];
      },
    },
  },
  data() {
    return {};
  },
  computed: {
    getClass() {
      return this.itemClass;
    }
  },
  methods: {
    columnName(header) {
      return `item.${header.value}`;
    },
    getAlignment(header) {
      const align = header.align ? header.align : "right";
      return `text-align: ${align}`;
    },
    getNonSlotValue(item, header) {
      const val = item[header.value];

      if (val) {
        return val;
      }

      return "";
    },
  },
};
</script>

An example of it's use is in this codesandbox link

Polynesian answered 7/2, 2021 at 17:3 Comment(3)
What should I do to make the loading animation work?Pelfrey
In Vuetify 3 this is no longer working properly because the body slot is placed within the tbody. Adding the below example causes two tbodys to render which breaks the styling.Elodiaelodie
For vuetify 3 you simply have to use the tbody slot instead of body slot and add the hide-default-body prop: github.com/vuetifyjs/vuetify/pull/19844Wulfenite
S
1

I checked the source code of VDataTable and found that the content in tbody is generated by genItems().

The following example is implemented with functional components and is fully compatible with v-slot:item*:

<v-data-table ref="table" ...>
    <template #body="props">
        <draggable
            v-if="$refs.table"
            tag="tbody"
            :list="props.items"
        >
            <v-nodes :vnodes="$refs.table.genItems(props.items, props)" />
        </draggable>
    </template>

    <template #item.name="{ item }">
    ...
    </template>
</v-data-table>

Here is the definition of the VNodes component:

components: {
    VNodes: {
      functional: true,
      render: (h, ctx) => ctx.props.vnodes,
    }
}
Stria answered 16/12, 2022 at 15:59 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.