SortableList

Component for enabling sorting of items accompanied by the flip animation based on Drag'n'Drop API.

Props

items

  • type: SortabeListItem[]
  • default: () => []

Sortable items

itemKey

  • type: string
  • default: 'id'

Name of id field of an item

threshold

  • type: number
  • default: null

Threshold in pixels

startDelay

  • type: number
  • default: null

Delay before drag start

pending

  • type: boolean

Loading state

Events

Event namePropertiesDescription
update:itemsUpdate of items
sortSorting of items
selectItem selection

Slots

NameDescriptionBindings
default




Brief description

The logic of the component depends upon the CSS-based flip animation provided by Vue. As there is no hook to listen to the end of that animation, trick with @transitionend was necessary. In fact, sorting works as follows:

  1. User grabs SortabeListTrigger component. Then the closest .sortable-list__item is searched and once it's found, its clone is created with fixed position and the corresponding list item becomes invisible.
  2. Next, global listeners for pointerup, pointermove, pointercancel are set up. As the mouse moves across the screen, so does the cloned item. While moving, component checks if the cloned item hovers over another list item. If true, it triggers reordering of the list locally by updating duplicated list of items.
  3. While animation is played, next reordering is disabled. Once it's finished, reordering gets enabled. Animation end is detected by @transitionend listeners attached to each affected item. Vue doesn't provide any hooks for that.
  4. Once user has released mouse button, clone of the active list item is destroyed, all global listeners are removed too and event sort is sent with the following argument type:
  interface SortEventArgument<Item extends Record<string, any>> {
    updatedIds: (number|string)[];
    updatedItems: Item[]
    oldItems: Item[];
    oldIds: (number|string)[];

    start: () => void;
    rollback: () => void;
  }

start function should be called before the request to save new order is sent. That function updates the list of items by emitting event update:items with updatedItems. In case of request failure, rollback needs to be called. It restores the initial value of items by emitting event update:items with oldItems.

Events

update:items

  • type Array<Record<string, any>>

Items update

sort

  • type: SortEventArgument

Sort saving event

Scoped slots

default

  • required: true
  • prop: SortableListTrigger - component that triggers sort.
  • prop: triggerListeners - listeners for sort triggering.
  • prop: item - currently iterated list item.
  • prop: activeItem - picked list item if it exists.
  • prop: activeKey - id of the picked list item if it exists.
  • prop: pending - pending flag.

Example

<template>
  <SortableList
    :items.sync="items"
    @sort="onSort"
  >
      <template #default="{ item, SortableListTrigger, triggerListeners }">
        <Component
          :is="SortableListTrigger"
          v-on="triggerListeners"
        >
          <div>
            {{ item.name }}
          </div>
        </Component>
      </template>
  </SortableList>
</template>

<script>
import flatry from 'flatry';

import SortableList from '@/components/sortable-list';

// @vue/component
export default {
  components: {
    SortableList
  },

  data() {
    return {
      items: [
        {
          id: 1,
          name: 'Item 1'
        },
        {
          id: 2,
          name: 'Item 2'
        },
        {
          id: 3,
          name: 'Item 3'
        },
      ]
    }
  },

  methods: {
    async onSort({
      updatedIds,
      start,
      rollback
    }) {
      start();

      const [err] = await flatry(this.$axios.$patch('v1/templates/ord', {
        templateIds: updatedIds
      }));

      if (err) {
        rollback();

        this.$toat.erro(err.serverError || 'Failed sort update !');
      }
    }
  }
}
</script>