vue.jsvuedraggablev-data-table

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


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?


Solution

  • 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