2

I am working on a webapp with drag 'n drop functionality. Up to now I used basic HTML5 Dragging but now I switched to using the library interact.js.

I don't know if my problem is specific to this library or of more general kind: Events when dragging and dropping usually fire multiple times (if I have watched it correctly, it also seems to always be exactly 4 times, but no guarantee on that).

I am also using Vue.js and this is my code:

<template>
    <v-card
      elevation="0"
      :id="id"
      class="board device-dropzone"
    >
        <slot class="row"/>
        <div
         Drop Card here
        </div>
    </v-card>
</template>

In the slot, an image and div with text get added. Also this is the script:

<script>
import interact from 'interactjs';

export default {
  name: 'Devices',
  props: ['id', 'acceptsDrop'],
  data() {
    return {
      extendedHover: false,
      hoverEnter: false,
      timer: null,
      totalTime: 2,
    };
  },
  methods: {
    resetHover() {
      alert('reset');
    },
    drop(e) {
      let wantedId = e.relatedTarget.id.split('-')[0];
      console.log(wantedId);
      console.warn(e.target);
      e.target.classList.remove('hover-drag-over');
      this.extendedHover = false;
      console.log('-------------dropped');
      console.warn('dropped onto device');
      this.$emit('dropped-component', cardId);
      e.target.classList.remove('hover-drag-over'); */
    },
    dragenter() {
      console.log('------------dragenter');
      this.hoverEnter = true;
        setTimeout(() => {
          this.extendedHover = true;
          console.log('extended hover detected');
        }, 2000);
      } */
      this.timerID = setTimeout(this.countdown, 3000);
    },
    dragover(e) {
      if (this.acceptsDrop) {
        e.target.classList.add('hover-drag-over');
      }
    },
    dragleave(e) {
      if (this.acceptsDrop) {
        clearInterval(this.timer);
        this.timer = null;
        this.totalTime = 2;
        e.target.classList.remove('hover-drag-over');
        this.extendedHover = false;
        this.hoverEnter = false;
        console.log('................');
        console.warn(this.extendedHover);
        // this.$emit('cancel-hover');
      }
    },
    countdown() {
     console.log('!!!!!!!!!!!!!!!!!!!!');
     if (this.hoverEnter === true) {
        this.extendedHover = true;
        console.log('-------------------');
        console.warn(this);
        console.warn(this.extendedHover);
        this.$emit('long-hover');
      } 
    },
  },
  mounted() {
    // enable draggables to be dropped into this
    const dropzone = this;
    interact('.device-dropzone').dropzone({
      overlap: 0.9,
      ondragenter: dropzone.dragenter(),
      ondrop: function (event) {
        dropzone.drop(event);
      },
    })
  },
};
</script>

The draggable component is this one:

<template>
  <v-card
    class="primary draggable-card"
    :id = "id"
    :draggable = "false"
    @dragover.stop
    ref="interactElement"
  >
    <slot/>
  </v-card>

With the script:

<script>
import interact from 'interactjs';

export default {
  props: ['id', 'draggable'],
  data() {
    return {
      isInteractAnimating: true,
      position: { x: 0, y: 0 },
    };
  },
  methods: {
    /* dragStart: (e) => {
      e.stopPropagation(); // so dragStart of ParentBoard does not get triggered as well
      // eslint-disable-next-line
      const target = e.target;
      e.dataTransfer.setData('card_id', target.id);
      e.dataTransfer.setData('type', 'widget');
      // for some delay
      setTimeout(() => {
        console.log('started dragging');
      }, 0);
    }, */
    dragEndListener: (event) => {
      console.warn('+++++++++++++++++++++++++++');
      // console.warn(event.currentTarget.id);
      if (document.getElementById(event.currentTarget.id)) {
        event.currentTarget.parentNode.removeChild(event.currentTarget);
      }
    },
    dragMoveListener: (event) => {
      /* eslint-disable */
      var target = event.target;
      // keep the dragged position in the data-x/data-y attributes
      const xCurrent = parseFloat(target.getAttribute('data-x')) || 0;
      const yCurrent = parseFloat(target.getAttribute('data-y')) || 0;
      const valX = xCurrent + event.dx;
      const valY = yCurrent + event.dy;
      // translate the element
      event.target.style.transform =
        `translate(${valX}px, ${valY}px)`

      // update the postion attributes
      target.setAttribute('data-x', x);
      target.setAttribute('data-y', y);
    }
    /* eslint-enable */
  },
  mounted() {
    const element = this.$refs.interactElement;
    console.log(element);
    // interact(element).draggable({
    const component = this;
  interact('.draggable-card')
    .draggable({ 
      manualStart: true,
      onmove: component.dragMoveListener,
      onend:component.dragEndListener,
    })
    .on('move', function (event) {
      var interaction = event.interaction;
      // if the pointer was moved while being held down
      // and an interaction hasn't started yet
      if (interaction.pointerIsDown && !interaction.interacting()) {
        var original = event.currentTarget;
        // create a clone of the currentTarget element
        const clone = event.currentTarget.cloneNode(true);
        clone.id = clone.id + "-clone"; 
        clone.classname += " dragged-clone";
        // insert the clone to the page
        document.body.appendChild(clone);

        clone.style.opacity = 0.5;

        // start a drag interaction targeting the clone
        interaction.start({ name: 'drag' },
          event.interactable,
          clone);
      } 
    })
    .on('end', function (event) {
      console.error('end drag');
    });
  },
  /* eslint-enable */
};
</script>

In general the dragging and dropping works. But I don't get why e.g. the drop-event would trigger four times when only dropping a single card. Can anybody help me with this?

Brian Tompsett - 汤莱恩
  • 5,753
  • 72
  • 57
  • 129
NotARobot
  • 455
  • 2
  • 10
  • 27
  • I am debugging a similar issue with the interactjs library. `ondrop` event fires twice, then four times, then eight. Did you find the issue in the end? – bigsee Jul 09 '20 at 15:10

1 Answers1

16

I was facing a similar issue using the same framework and library. Since there was some logic based on the event firing, it was breaking my app when it fired multiple times.

I suspected that the issue was related to bubbling of events.

The solution in my case was therefore to add event.stopImmediatePropagation() inside my drop(event) handler.

As noted in the referenced article, one should take care when stopping event bubbling that it doesn't have unforeseen consequences elsewhere.

bigsee
  • 927
  • 8
  • 14