Shopify / draggable

The JavaScript Drag & Drop library your grandparents warned you about.
https://shopify.github.io/draggable
MIT License
17.96k stars 1.09k forks source link

group option for sortable #235

Open philippkuehn opened 6 years ago

philippkuehn commented 6 years ago

Is there a way to drag items between two sortable instances without initializing both at the same time?

I know there is a demo for multiple containers.

const containers = document.querySelectorAll('#MultipleContainers .StackedList');
const sortable = new Sortable(containers);

But if you think in components (react, vue, …) this is likely a problem because for each sortable component there is one sortable instance. It would be nice if these instances could work together. Many other drag and drop plugins provide a way to set a group property for doing this. This could look something like this:

const sortable1 = new Sortable(document.querySelectorAll('#list1'), {
    group: 'a',
});
const sortable2 = new Sortable(document.querySelectorAll('#list2'), {
    group: 'a',
});
const sortable3 = new Sortable(document.querySelectorAll('#list3'), {
    group: 'b',
});

// you can drag items between sortable1 and sortable2
// you can't drag items between sortable1 and sortable3 or sortable2 and sortable3

Maybe something like this is already possible now?

tsov commented 6 years ago

Hey @philippkuehn, thanks for your suggestion. We definitely have plans to add a group option for Sortable, but it would work slightly different. Due to design choices we made, it's not possible to drag between draggable instances (includes Sortable and all other modules), instead we expose an API to allow adding of additional containers, e.g. sortable.addContainer()/sortable.removeContainer(). This way you can dynamically add more containers as your application may render more content.

Here an example of how you could get that behaviour working today:

const sortable = new Sortable(document.querySelectorAll('.selector-for-all-lists'))

sortable.on('sortable:sort', (sortableEvent) => {
  const {source, over} = sortableEvent;

  // You'll need to add `data-group` attributes to your elements instead
  if (source.getAttribute('data-group') !== over.getAttribute('data-group')) {
    // Canceling the `sortable:sort` event will prevent sorting
    sortableEvent.cancel();
  }
})
philippkuehn commented 6 years ago

Thanks for the clarification! Unfortunately, this makes it impossible to build portable and encapsulated web components, isn't it? 😟

I hope it's not out of context but this is what a simple Vue.js <sortable /> component looks like.

<template>
  <div ref="container">
    <slot />
  </div>
</template>

<script>
import { Sortable } from '@shopify/draggable'

export default {
  name: 'sortable',
  data() {
    return {
      sortable: null,
    }
  },
  mounted() {
    this.sortable = new Sortable(this.$refs.container, {
      draggable: '.draggable'
    })
  },
  beforeDestroy() {
    this.sortable.destroy()
  }
}
</script>

This component can be used like this.

<sortable>
  <div class="draggable">Item 1</div>
  <div class="draggable">Item 2</div>
</sortable>

<sortable>
  <div class="draggable">Item 3</div>
  <div class="draggable">Item 4</div>
</sortable>

But at the moment there is no way that these two lists can communicate with each other.

The only solution I have in mind is to store this sortable instance const sortable = new Sortable(document.querySelectorAll('.selector-for-all-lists')) somewhere globally and each <sortable /> component will add and remove its container on mounted/beforeDestroy to this instance. But again – this is not encapsulated.

This also becomes much more complicated if there are multiple groups, because then you have to save one instance globally for each group.

dchenk commented 6 years ago

I concur with @philippkuehn

A solution that may, according to what your @tsov say, change the current API would be to simply have a “group” property for lists that would specify families of interchangeable elements. Taking inspiration from the nice design in https://github.com/RubaXa/Sortable we may allow a “group” option upon the creation of any Draggable instance:

dam1r89 commented 6 years ago

Hey all, first apologies if writing this on a wrong place.

I'm having similar problem with connecting two separate components to handle sorting between separate. Like @philippkuehn said and @tsov confirmed, only way is to have a single instance of Sortable.

Aside from sort between groups I need additional option to drop sortable elements on dropzones and that means Sortable is not going to work, but Draggable and then handling all events manually.

I was surprised that Draggable has all events needed to make this work and they are easy to use, so that is great!

Then it is basically copying Sortable.js in my vue component Sortable.vue and each component attaching event listeners to that global draggable and handling events individually.

The problem I'm having right now is how to reference component instance and dom element in a nice way. Ugly solution is by attaching source component instance to a source draggable element like this:

onDragStart(event) {
    if (this.isChild(event)) {
        event.source._source = {
            component: this,
            startIndex: this.index(event.source)
        }
    }

}

Anyone trying to implement shopify draggable with Vue I would appreciate any help with this. Again, this is working but it is ugly, and I'm not sure will it cause any memory leaks. Thanks

https://github.com/dam1r89/vue-shopify-draggable-components/tree/master/src/components