Shopify/draggable

How to use returnToOriginalDropzone?

AnteroRamos opened this issue · 16 comments

Hello,
I'm trying to create a Drag and Drop form builder with .net MVC and I need to drag the field I want to the form and have it back. But I'm not understanding how to use the "method" returnToOriginalDropzone... Can anyone explain please? Documentation doesn't seem to help much on this one.

Thank you in advance.

[returnToOriginalDropzone] is used to put the dragging element back to the original dropzone by condition. This is a private method and can't be called externally. It seems that you need to manually record the original dropzone and move back when needed.

Thank you for your answer! I've been trying to find it but haven't had any luck. Is there any method on the library that does that? I mean, every time I add a field, I need it to add the field but the "button" that I drag to create the field has to be in initial place again, because I might need to use it again.

It seems that there is no method on the library can do that currently (we are planning to add one). Hope those answers can help you

#380 (comment)
#222 (comment)

That's exactly what I was looking for! Thank you!

Just one question, why does this not work?

var droppable = new Draggable.droppable(document.querySelectorAll(".container"), { draggable: '.item', dropzone: '.dropzone' });

Always says that the constructor does not exist, but it's how it is documented. Am I missing something?

Edit: Switched to:

var droppable = new Droppable.default(document.querySelectorAll(".container"), { draggable: '.item', dropzone: '.dropzone' }); and it's working. Shouldn't work the other way tho?

Sure. It looks like a typo (droppable and Droppable), or please check whether lib/draggable.bundle.js or lib/droppable.js is being used and the version.

See: https://github.com/Shopify/draggable/tree/master/src/Droppable#usage

I had multiple scripts for imports and it was screwing it up. Thank you! Now I'm just trying to get the item to the exact same position that it was :D

By the way, any special reason that it won't let me drag more than 1 item to the dropzone? I haven't set a limiter anywhere. I wanted to drag as many as I needed.

If you can answer to this one, please forget the following one. Droppable is messing up a "addEventListener" I added to get the HTML of the item I'm dragging... Can't find anything on the documentation that is able to tell me that :/

If the answer for the question above is no, the API has any method to get the last dropped item? For example, when I would drop the word "Text field" I would be able to create literally a text field on where it dropped.

Thank you! You can close the issue when answering to this questions :)

We use the occupied class to detect whether an element can drop in the dropzone, you can remove this class to drop multiple elements. Like:

<style>
  * {
    user-select: none;
  }
  .item {
    width: 50px;
    height: 50px;
    background: aqua;
  }
  .item-box {
    width: 50px;
    height: 50px;
    overflow: hidden;
  }
  .box {
    display: flex;
    min-width: 100px;
    width: max-content;
    height: 100px;
  }
  .dropzone {
    border: 1px solid lime;
  }
</style>

<div class="container">
  <div class="item-box dropzone">
    <div class="item">drag me</div>
  </div>
  <div class="box dropzone">drop zone</div>
  <div class="box dropzone">drop zone</div>
  <div class="box dropzone">drop zone</div>
</div>

<script src="https://cdn.jsdelivr.net/npm/@shopify/draggable@1.0.0-beta.12/lib/draggable.bundle.js"></script>
<script>
  var droppable = new Draggable.Droppable(
    document.querySelectorAll(".container"),
    {
      draggable: ".item",
      dropzone: ".dropzone",
    }
  );
  var itemBox = document.querySelector(".item-box");
  var item = itemBox.querySelector(".item").cloneNode(true);
  droppable.on("drag:stopped", function (event) {
    if (!itemBox.querySelector(".item")) {
      itemBox.appendChild(item.cloneNode(true));
    } 
    
    // remove the occupied class to allow drop more items in it
    event.originalSource.parentNode.classList.remove(
      "draggable-dropzone--occupied"
    );
  });
</script>

BTW, we have created two elements (source and mirror) for drag and drop. They will be destroyed when drag stopped. This may be the reason for messing up addEventListener.

Thank you for the explanation! The mirror worked perfectly! But I'm having trouble with getting the item to his exact position after dragged. It keeps being added as the last element of the list. And it makes sense that it does, just can't figure out how to make it not do that in a nice way.

Can't seem to find much on the source element you mentioned on the documentation, would that be able to help me with this problem?

It seems that sortable is more suitable for this situation. For example:

<style>
  * {
    user-select: none;
  }
  .container {
    display: flex;
    flex-wrap: wrap;
    min-height: 50px;
  }
  .container--drop {
    border: 1px solid violet;
  }
  .item {
    margin-right: 10px;
    width: 50px;
    height: 50px;
    background: aqua;
  }
  .fill {
    width: 100%;
  }
</style>

<div class="container"></div>
<div class="container container--drop">
  drop to here
  <div class="fill"></div>
</div>

<script src="https://cdn.jsdelivr.net/npm/@shopify/draggable@1.0.0-beta.12/lib/draggable.bundle.js"></script>
<script>
  const items = ` 
    <div class="item">1 drag me</div>
    <div class="item">2 drag me</div>
    <div class="item">3 drag me</div>
  `;
  var sortable = new Draggable.Sortable(
    document.querySelectorAll(".container"),
    {
      draggable: ".item",
    }
  );

  var candidateContainer = document.querySelector(".container");
  candidateContainer.innerHTML = items;
  sortable.on("drag:stopped", function (event) {
    candidateContainer.innerHTML = items;
  });
</script>

Hm, I thinks that's exactly what I dont want. I want to have the menu with the fields static, which means that I only want to drag the mirror to the other container. I tried to remove the dropzone from the draggable container. Tried to add the technique you used there to make the container HTML equal to the inicial HTML to the "droppable.on('drag:start')", but that wouldn't let me drag the items.

This is the code, for better understanding, it's a very basic thing:
https://jsfiddle.net/ozabk1re/2/

For context, the point is to move the fields on the left container to the right one in order to create a form. That's why I want the left container static.

If you do not want to open the link, this is my JS code:

let mirror;
let leftPanel;

let everything = document.querySelector('.list-of-items').innerHTML;

let droppable = new Draggable.Droppable(document.querySelectorAll(".left-panel"), {
    draggable: '.field-item',
    dropzone: '.dropzone',
    'source:dragging': ['draggable-source--is--dragging']
});

droppable.on('mirror:created', function (evt) {
    mirror = evt.data.mirror.id;
    leftPanel = document.querySelector("#lista-de-items");
});

droppable.on('drag:stopped', function (event) {
    
    leftPanel.innerHTML = everything;

    if (event.originalSource.parentNode.classList !== null) {
        event.originalSource.parentNode.classList.remove(
            "draggable-dropzone--occupied"
        );
    }
});

Basically, when I drag out one of the field from the initial container and send it back, it goes back to the last position while I'm still dragging it around.

Did you mean this effect https://jsfiddle.net/a7edq8bt/4/

The droppable plug-in does a lot of operations that cannot be changed externally. Sometimes it is more convenient to use the original draggable. For a general requirement, we are welcome an improvement or a new plug-in.

That's exactly what I was looking for! Thank you a lot!

--Just one question, where in the documentation is the "drag:over" and the "drag:out" events? I'm having some trouble reading the documentation. --

Forget what I said, I was looking at your website, just saw that the documentation on the github is way more complete.

I tried to use the class "container:placed", but either it didn't do anything or I didn't use it correctly. I added it on the construtor as
let droppable = new Draggable.Droppable(document.querySelectorAll(".left-panel"), { draggable: '.field-item', dropzone: '.dropzone', classes: 'container:placed': ['draggable-container--placed] });

And on droppable.on(drag:stop) called a queryselector to get the '.draggable-container--placed', but it came as null.

Basically I wanted to get the latest element the element I dragged into the right container to replace it as a text field or something. But as I'm trying to do it on a 'drag:stopped' or 'drag:stop' event, I'm not being able to do that. Tried to look at the draggable events, but none seemed appropriate for what I'm doing. Could you clarify if I'm wrong or not and if I should switch to another?

Thank you a lot! You're being a huge help.

The event.originalSource element is the element dragged into the right container finally. And the event.source element is the element dragged into the right container temporarily.

About originalSource, source and mirror please see #245 (comment)

This is how draggable works internally:

  1. emit DragStopEvent
    const dragStopEvent = new DragStopEvent({
  2. replace the source element by the originalSource element
    this.source.parentNode.insertBefore(this.originalSource, this.source);
    this.source.parentNode.removeChild(this.source);
  3. add the placed class (update L584 to L585)
    this.sourceContainer.classList.add(...this.getClassNamesFor('container:placed'));
  4. emit DragStoppedEvent
    const dragStoppedEvent = new DragStoppedEvent({
  5. remove the placed class after timeout
    this.lastPlacedContainer.classList.remove(...this.getClassNamesFor('container:placed'));

Ohh, those classes are not that kind of classes. Got you! Ahaha

The problem is to get where the element got dropped. I need to know if when the element was dropped the user was overing over the right container or not (or something similar). The classes you talked about earlier only seem to add momentarily to the original element, not to the dropped one. Right now what happens is: I click on a element and it instantly adds to the form, I want to obligate the user to drop it in the other container.

This is the code I have for a better understanding of the issue:
https://jsfiddle.net/gczf7kqe/1/

I already tried a bunch of stuff but didn't manage to get anything to work. Even tried to add 2 events on the same as "droppable.on('event, event')' ahah

We can save the over container on drag:over:container like https://jsfiddle.net/hv2tc4jz/3/

Thank you a lot for your help! Keep up the good work :)