dragula icon indicating copy to clipboard operation
dragula copied to clipboard

Option to not move or clone

Open hnry opened this issue 8 years ago • 31 comments

Have an option to not move or clone the original element when it is dropped.

Useful for when we want to do our own rendering of the dropped element or let a third party lib (react).

hnry avatar Aug 30 '15 20:08 hnry

@bevacqua can you elaborate what @hnry is asking for?

beatfreaker avatar Sep 01 '15 18:09 beatfreaker

@beatfreaker right now there is no ability to drop an element with no side effect once the drop is completed.

The side effect being that the source element is either moved or copied to target.

There are times you want to have nothing happen. Everything runs normally, drop fires, all events fire, but no moving or copying of the source to target.

Right now this is how it works when I glanced over the source, drag starts, if target is a valid container, a shadow (cloned) element is created. If dropped and it's ok, use the shadow element, check to see if it's copy only, if not, remove the original element.

I may have time to make a pull for this.

hnry avatar Sep 01 '15 18:09 hnry

For context, we're talking about this, right? The way I see it, the only reasonable way to go about this would be to

  • Only allow to "hook/replace" the drop() call
  • Call a drops(item, target, source) method in the options that defaults to returning true
  • If drops returns true, then normal drop(item, target) execution
  • Else?
    • Nothing happens (we assume user handled) -- or,
    • We call cleanup() to avoid weird state in case user does weird things

I'm inclined towards calling cleanup. Besides that, PRs welcome. But also, I'd like to hear opinions wrt cleanup/nothing

bevacqua avatar Sep 13 '15 20:09 bevacqua

Hm. I guess this is the problem I'm running into. I'll just detail my use case here - I'm using react-dragula.

  1. Add data-* attributes to list elements so that they can be used in "drop" callback via sibling argument.
  2. When "drop" is fired I calculate a new 'position' value for the dragged item (based on siblings).
  3. New position is pushed to server, updating flux stores.
  4. Flux store change triggers a redraw event which cascades through Reactjs, causing list to be redrawn.
  5. Dragula manipulates correctly ordered DOM, moving elements into the wrong order.

Currently I'm not sure how to use Dragula with react because this seems to be mutating the DOM after react redraws. Has anybody else used flux and dragula together successfully?

rhys-vdw avatar Sep 21 '15 07:09 rhys-vdw

@rhys-vdw So if I understand your problem correctly, then you basically don't want dragula to touch your DOM, since you will render the drop effect yourself (via react).

What I did is monkey patch dragula to take out the shadow element, it's a 1 liner. It's ugly because it loses the 'shadow' positioning effect on the target container.

So this issue is for someone to submit a patch to have dragula have the shadow element to show positioning but then remove it after it's dropped or cancelled, while following @bevacqua advice earlier in this issue. Which should be easy to do too. I'll submit a pull request if no one wants to give it a go (but might be a week or 2).

hnry avatar Sep 21 '15 07:09 hnry

Hi @hnry, tomorrow I'm going to have a look at React-dnd instead. It seems like a more appropriate choice for React. If that doesn't suit I'll come back with the PR here since I've only really got to disable this one thing to get it working.

rhys-vdw avatar Sep 21 '15 08:09 rhys-vdw

@hnry, @bevacqua I can't work out how to get this to work.

What I did is monkey patch dragula to take out the shadow element, it's a 1 liner. It's ugly because it loses the 'shadow' positioning effect on the target container.

I was hoping to keep the shadow element, but restore the DOM just before react re-renders. Pretty hacky, but this is my approach:

Basically I'm trying to restore the original state before triggering a redraw. So I'm setting options.copy to true, and then trying to call target.removeChild on the "drop" event. However target is always given as null when copy is true. Is this intentional?

{ pull } = Lodash
{ repositionJob } = Project.Actions.Jobs
{ dispatch } = Project.Store

drake = reactDragula(copy: true)
drake.on 'drop', (element, target, source, nextSibling) ->

  # Get the element before the drop point.
  previousSibling = if nextSibling? \
    then element.previousSibling
    else target.lastElementChild

  jobId = +element.dataset.jobId
  projectId = +target.dataset.projectId
  previousJobId = +previousSibling?.dataset.jobId or null
  nextJobId = +nextSibling?.dataset.jobId or null

  target.removeChild(element) # throws because `target` is `null`

  dispatch repositionJob(jobId, projectId, previousJobId, nextJobId)

rhys-vdw avatar Sep 22 '15 02:09 rhys-vdw

+1 would love to have the option to use that, I want to use meteor blaze to switch between places the order so if we have a list of 10 elements make it possible to change 2 with 8 so 2 becomes 8 8 becomes 2. A little bit different than it works right now. Same use like react but hopefully without hacks :)

markudevelop avatar Sep 26 '15 18:09 markudevelop

I'm not sure about what the exact behavior should be wrt: https://github.com/bevacqua/dragula/issues/188#issuecomment-139914022, other than that I'm completely sold on this one.

bevacqua avatar Sep 26 '15 18:09 bevacqua

+1 I also want to drop an item on another item, without causing a side-effect (want to implement it myself).

derwaldgeist avatar Oct 04 '15 18:10 derwaldgeist

@derwaldgeist seems like the right way to put it yeah, Handle the logic our self just drop it on other item :) This is what I also need, But dragula might be the tool for this purpose and possibly can do some small jQuery plugin for this I found tiny 1kb libray for draging

markudevelop avatar Oct 05 '15 10:10 markudevelop

@voidale: I found a lot of libraries out there, but most were missing the following capabilities:

  • Copy the source object as a shadow instead of moving the source directly
  • Doing this in a way that allows to drag the shadow to any place on screen, i.e. not limited to the boundaries of the source object
  • Working on any desktop and mobile browser

Dragula is quite good at this, but I am missing some generic way of intercepting some of its "opionated" stuff (e.g. what exactly to do on drop). A hook / event-driven interface would be great here.

I tried to implement such behaviour on top of interact.js, but did not manage to get the positions right, so the shadow was jumping around...

derwaldgeist avatar Oct 05 '15 10:10 derwaldgeist

@derwaldgeist you're 100% right, I also need the exact same thing hopefully @bevacqua can get this hook for us so we could customize it.

markudevelop avatar Oct 05 '15 10:10 markudevelop

With revertOnSpill enabled it works to restore the element to the original position without altering the DOM, though probably is not a complete solution to this issue.

var drake = dragula(containers, {
  revertOnSpill: true,
});

drake.on('drop', function(element, container) {
  drake.cancel();

  // Change the model here to refresh the view
});

(Source: https://github.com/jdanyow/aurelia-solitaire/blob/master/src/drag-and-drop.js#L41-L42)

ernestoalejo avatar Oct 08 '15 22:10 ernestoalejo

:+1:

I could really use this too!

I'm trying to integrate Dragula with Ember and they are fighting over the DOM. I thought my app was working great until I read this:

http://stackoverflow.com/questions/32587495/resolving-ember-and-dragulas-view-of-the-dom

I found my app had the exact same bug! The kind programmer there even has an Ember Twiddle that reproduces the issue:

http://ember-twiddle.com/c086d2853a926c310a23 https://github.com/RyanHirsch/dragula-ember-example

akappen avatar Oct 13 '15 17:10 akappen

@ernestoalejo Thanks for the on-drop-cancel tip! Looks like a valid workaround to prevent the DOM from being altered.

derwaldgeist avatar Oct 14 '15 13:10 derwaldgeist

In fact I discovered you can let revertOnSpill to false if you cancel the drag with a parameter:

var drake = dragula(containers, {});

drake.on('drop', function(element, container) {
  drake.cancel(true);

  // Change the model here to refresh the view
});

Also note that if the library adds comment nodes to track the DOM generated by the repeater (like Aurelia does) you'll probably want to change isContainer to return different elements depending if the user is dragging or not; like this: https://github.com/jdanyow/aurelia-solitaire/blob/master/src/drag-and-drop.js#L20

Having two different containers allows you to keep the exact order of child elements. Dragula was moving the element after the second comment when restoring it which makes the same damage as not canceling the drop.

<div>
  <!-- lib comment 1 -->
  <div class="drop-target"> <!-- return this element if dragging -->
    <div class="drag-source"> <!-- return this element if not dragging -->
       content 1
    </div>
  </div>
  <!-- lib comment 2 -->
</div>

ernestoalejo avatar Oct 14 '15 16:10 ernestoalejo

+1 for this. I've bulit a Dragula ractive.js decorator. However, ideally, I'd like to handle the side effects - specifically, I want to be able to drop in an element, and replace the dropped element with something new - then set it in ractive.

BigWillie avatar Feb 02 '16 01:02 BigWillie

+1 for having an option to prevent the default drop actions.

It would also be nice if we can disable showing the target shadow. In some cases, when we override the drop actions, the resulting position of the element being dropped may not correspond to the one indicated by the shadow.

aesfer avatar Feb 14 '16 08:02 aesfer

Here is how I solved it in React:

componentDidMount() {
        let containers = [this.refs.dragContainer.getDOMNode()]

        var drake = dragula(containers, {

        })

        drake.on("drop", function(el, target, source, sibling) {
            this._onDrop(el, target, source, sibling)
            drake.cancel(true)
        }.bind(this))

        drake.on("cancel", function(el) {
            this._onCancel(el)
        }.bind(this))
}

_onDrop(el, target, source, sibling) {
        let sorting = []
        for(let i = 0; i < target.children.length; i++) {
            let child = target.children[i]
            let id = parseInt(child.children[0].innerText)
            sorting.push(id)
        }

        let orders = this.state.orders

        let i = 1
        sorting.forEach(id => {
            let order = orders.find(function(o) { return o.id == id })
            order.sequence = i++
        })

        let ordersSorted = orders.sort((o1, o2) => o1.sequence - o2.sequence)

        this.ordersCopy = ordersSorted
}

_onCancel(el) {
        this.setState({orders: this.ordersCopy})
}

I pickup the drop event to sort my array programmatically, and then cancel the drop event. Once cancelled I set the state, which causes React to re-render it in the correct order

n1mda avatar Apr 07 '16 08:04 n1mda

@n1mda I have used this approach and while it does work, there is a momentary flicker between the time the cancel happens and the time react re-renders the the section according to the new sort.

Did you find a work-around for that? :)

rmahoney-bl avatar Jun 14 '16 14:06 rmahoney-bl

@rmahoney-bl This was the solution I ended up using in production, but I have not noticed any lag. The dragula consists of about 50 simple divs with text.

n1mda avatar Jun 14 '16 14:06 n1mda

@n1mda I just realized the issue -- I'm not re-sorting until after performing an XHR call. Thanks for super-fast response!

rmahoney-bl avatar Jun 14 '16 14:06 rmahoney-bl

@n1mda I also ran into this issue, and your approach was very helpful in creating a workaround. Thanks!

richcsmith avatar Sep 13 '16 01:09 richcsmith

+1

jkevingutierrez avatar Oct 02 '16 18:10 jkevingutierrez

A bit of a bizarre solution that seems to be working for us is to go remove the display: none rule on the .gu-hide class (or remove the rule and declaration completely). The drop event is still fired but no new DOM element is created. The original item we were dragging is left intact.

Putting this here in the hopes that it either helps someone (or more likely someone can tell us why this is a dumb idea and what we are breaking elsewhere)!

stellasoft-george avatar Nov 18 '16 15:11 stellasoft-george

I tried using Dragula in a Meteor app, following the recommendation in this issue to cancel the drop in the event handler and let the rest of the stack take it from there (update underlying documents' ordering and let the reactivity work it's magic to update DOM).

It works, except in an edge case of moving the item up by one notch, essentially switching places with the previous sibling. In that case the new ordering is updated properly, but for some strange reason, the order of actual DOM elements doesn't reflect that change.

The issue is better described here along with a reproduction, if anyone is interested: https://github.com/meteor/blaze/issues/231

arggh avatar Jan 18 '17 20:01 arggh

+1

Devenor avatar Jan 23 '17 19:01 Devenor

Here's a combination of tricks from this thread (thank you all for your effort!) that worked in my project. I am rendering the DOM with snabbdom and storing application state is in redux.

Posting here in case it's of help to someone.

const drake = dragula({ containers })
  .on('drop', (el, target, source, sibling) => {
    const itemId = el.dataset.id
    const targetColumnId = parseInt(target.dataset.id)
    const ghostElement = document.querySelector('.gu-transit')
    const targetItemIndex = Array
      .from(ghostElement.parentNode.children)
      .indexOf(ghostElement)
    drake.cancel(true)
    store.dispatch(moveItem(itemId, targetColumnId, targetItemIndex))
  })

codeclown avatar Jul 17 '17 18:07 codeclown

There really should be a simple way to automatically cancel a drop event. So, get the drop event, do something with it but not have dragula append the dragged element to the dropped element.

As with React above, in Vue you have to modify your data on drop and Vue will update the DOM. You don't want Dragula doing it or you get extra elements.

cawoodm avatar Mar 17 '19 19:03 cawoodm