<template>
  <div :key="dragKey">
    <slot />
  </div>
</template>

<script>
/*
* Note by hht 2020-03-03
* This component is used to make el-table draggable.
* Because vue-draggable can not work fine with el-table.
* So I use sortablejs to make el-table draggable instead.
* The problem is :key refreshes the whole table is not a good idea,
* But I can not find a better way to resolve sortable & el-table's cache problem.
* Other scene we can use `vue-draggable` component in this project.
* */

/*
* Design by hht 2020-03-03
* 1 Use key on the root element to force table re-render.
* 2 Use dragOptions to override default drag options. So all sortablejs options can be used.
* 3 Use onEnd event to update table data and emit drag-change event.
* 4 Use sortable.destroy() to destroy sortable instance after each drag.
* 5 Use raw data of el-table as drag list, so drag-change modify and emit the same data as el-table.
* */
import Sortable from 'sortablejs'

export default {
  name: 'TableDraggable',
  props: {
    dragOptions: {
      type: Object,
      default: () => ({})
    }
  },
  data() {
    return {
      dragVersion: 0,
      sortable: null,
      defaultSortOptions: {
        animation: 150,
        ghostClass: 'table-draggable-ghost',
        onStart: (evt) => {
          // close all expanded rows in el-table
          const tableRef = this.findElTable(this)
          tableRef.store.states.expandRows = []
        },
        onEnd: (evt) => {
          const tableRef = this.findElTable(this)
          // get reference of table data
          const list = tableRef.data
          // update list if drag index changed, and emit drag-change event
          if (evt.oldIndex !== evt.newIndex) {
            // exchange list item from old index to new index
            const item = list.splice(evt.oldIndex, 1)[0]
            list.splice(evt.newIndex, 0, item)
            // update version to force table re-render
            this.dragVersion++
            this.destroySortable()
            // emit drag-change event
            this.$emit('drag-change', list, evt)
            setTimeout(() => {
              this.initSortable()
            }, 0)
          }
        }
      }
    }
  },
  computed: {
    dragKey() {
      // NOTE: drag key is very important, it will force the table to re-render
      // $forceUpdate() is not enough, so does modify table data or call other methods
      return 'table-draggable-' + this.dragVersion
    },
    sortOptions() {
      return {
        ...this.defaultSortOptions,
        ...this.dragOptions
      }
    }
  },
  mounted() {
    this.initSortable()
  },
  methods: {
    initSortable() {
      this.destroySortable()
      const tableBody = this.$el.querySelector('tbody')
      this.sortable = Sortable.create(tableBody, this.sortOptions)
    },
    destroySortable() {
      this.sortable?.destroy()
      this.sortable = null
    },
    findElTable(node) {
      if (node.$options.name === 'ElTable') {
        return node
      }
      if (node.$children && node.$children.length > 0) {
        for (let i = 0; i < node.$children.length; i++) {
          const child = node.$children[i]
          const table = this.findElTable(child)
          if (table) {
            return table
          }
        }
      }
    },
    refresh() {
      this.destroySortable()
      this.initSortable()
      this.dragVersion++
    }
  }
}
</script>

<style lang="scss">
@import "@/assets/styles/variables.scss";

.table-draggable-ghost {
  opacity: 0.8 !important;
  background-color: mix($--color-primary, #fff, 20%) !important;
}
</style>
