SortableJS / Vue.Draggable

Vue drag-and-drop component based on Sortable.js
https://sortablejs.github.io/Vue.Draggable/
MIT License
20.11k stars 2.9k forks source link

How to apply a dynamic ghost class? #857

Closed NvdB31 closed 4 years ago

NvdB31 commented 4 years ago

I'd like to know if it's possible to dynamically change the ghost class after Draggable was initialised. I need a different ghost class to be applied depending on the origin of dragged element.

Thanks.

David-Desmaisons commented 4 years ago

Unfortunately It is not possible.

flowt-au commented 3 years ago

Actually, I have done the following which seems to work. Maybe this will help. I am using VueDraggable on a Quasar.js tree to drag tree nodes around.

My use-case was to indent the ghost when the move event e.willInsertAfter was true when dragging over a folder node. The idea is to indicate that a drop there would drop the node as the first child of the folder rather than as a sibling of the folder node.

So, instead of this: Greenshot 2021-08-08 14_32_27

I get this when the mouse is dragged to the position after the folder node but before the first child. The green is just to make the example clearer. Greenshot 2021-08-08 14_30_40

Given this CSS:

.tree-ghost {
  opacity: 0.5;
  background: #55aed8;
}

.tree-ghost-indented {
  margin-left: 27px;
  background: green;
}

And draggable:

<draggable
  v-model="dragData"
  :move="checkMove"
  @end="onDropped($event, prop)"
  group="mytree"
  ghost-class="tree-ghost"
>

My checkMove function

checkMove (e) {
  const canDrop = true

  // Always remove the indent class
  e.dragged.classList.remove('tree-ghost-indented')

  // There are some elements in the tree that I am not interested in. Skip those.
  // First see if we are over an element that is a tree node with its nodeid in the attributes.
  // I put a 'data-nodeid' attribute on each node so I can get the nodeid from the dom element.
  // That is just for my use-case so I can decide below whether to change the ghost class or not.

  if (e.to.firstChild && e.to.firstChild.dataset.nodeid) {
    // The trick is you must wait a tick before interrogating the dom for the element
    setTimeout(() => {
      // It works best to go direct to the dom element with minimal helper functions.
      const id = e.to.firstChild.dataset.nodeid

      // However I do need this custom function to get the node from the Vue data model.
      // See the note below this code block.
      const d = this.tree.getNodeByKey(id) 

      // This is the test. If it is not a leaf node and the event indicates a drop AFTER,
      // indent the ghost so it lines up with the children of that folder / header node.
      // This works even if the folder is closed and/or has no children, which is what I want.
      if (!d.leaf && e.willInsertAfter) {
        e.dragged.classList.add('tree-ghost-indented')
      }
    }, 1)
  }

  return canDrop
}

Notes:

  1. Of course the normal Sortable thing when not using a tree is to have the list in an array so the elements get dragged / sorted along with the UI. As I said I am using VueDraggable over a tree (nested arrays) and while there are examples of nesting Sortable that was going to be messy in my case. So, my draggable v-model="dragData" is set to dragData: [] to keep sortable happy but it never has any data. Instead, I manage the adding and removing nodes myself in the actual nested tree data this.treeData via the specified onDropped function. That is pretty trivial and much better in my situation than nested VueDraggable components.
  2. this.tree.getNodeByKey(id) in the code above simply looks up the Vue model data based on the nodeid set on the data attribute of the node dom element. ie the unique index of the node in the tree.
  3. In my onDropped function I also need to do this to remove the lingering extra class on what is then the "from" div:
    e.from.firstChild.classList.remove('tree-ghost-indented')

    Anyway, I hope that is useful. Murray

YukhymchukVolodymyr commented 2 years ago

removed