HTML5 Draggable List - multiple setData?

2.2k Views Asked by At

So, I have a list of bands that I want to organise into a running order, like so:

<li data-band-id='1' draggable='true' class='band-name'>Metallica</li>
<li data-band-id='2' draggable='true' class='band-name'>Slayer</li>
<li data-band-id='3' draggable='true' class='band-name'>Paradise Lost</li>
<li data-band-id='4' draggable='true' class='band-name'>Gojira</li>

I would like to be able to drag these list items around to change each bands placement within the overall list. So far, I have the following JavaScript to do this:

var dragSatMS = null;
function handleDragStart(e) {
    //this.style.color = 'green'; 
    dragSatMS = this;
    e.dataTransfer.effectAllowed = 'move';
    e.dataTransfer.setData('text', this.innerHTML);
}
function handleDragOver(e) {
    if (e.preventDefault) {
        e.preventDefault(); 
    }
    e.dataTransfer.dropEffect = 'move';
    return false;
}
function handleDragEnter(e) {
    this.classList.add('over');
}
function handleDragLeave(e) {
  this.classList.remove('over');
  //this.style.color = '#333';
}
function handleDrop(e) {
  if (e.stopPropagation) {
    e.stopPropagation();
  }
  if (dragSatMS != this) {
    dragSatMS.innerHTML = this.innerHTML;
    this.innerHTML = e.dataTransfer.getData('text');
  }
  return false;
}
function handleDragEnd(e) {
  [].forEach.call(mssatCols, function (mSatCol) {
    mSatCol.classList.remove('over');
     //mSatCol.style.color = '#333';
  });
}
var mssatCols = document.querySelectorAll('#ms-saturday .band-name');
[].forEach.call(mssatCols, function(msSatCol) {
  msSatCol.addEventListener('dragstart', handleDragStart, false);
  msSatCol.addEventListener('dragenter', handleDragEnter, false);
  msSatCol.addEventListener('dragover', handleDragOver, false);
  msSatCol.addEventListener('dragleave', handleDragLeave, false);
  msSatCol.addEventListener('drop', handleDrop, false);
  msSatCol.addEventListener('dragend', handleDragEnd, false);
});

This works perfectly, I can drag and drop lists to make them change places and the name of the band swaps appropriately. However, the value of the 'data-band-id' attribute stays as it was. I know this is exactly what the code I have does and thats my issue. I'd like to amend the code so that both the name of the band being dragged and dropped and the value of the 'data-band-id' attribute are swapped.

I've Googled a lot but found nothing that can show me how to setData on multiple values, any help much appreciated.

2

There are 2 best solutions below

2
On BEST ANSWER

You can query the attributes property of both items and access the value of data-band-id. Once you have both two values, you can call setAttribute("name", "value") to update the data-band-id. Your updated handleDrop method would then be:

function handleDrop(e) {
  if (e.stopPropagation) {
    e.stopPropagation();
  }
  if (dragSatMS != this) {
    dragSatMS.innerHTML = this.innerHTML;
    this.innerHTML = e.dataTransfer.getData('text');
    //Get the data-band-id of both items.
    itemToReplaceAttr = this.attributes["data-band-id"].value;
    draggedItemAttr = dragSatMS.attributes["data-band-id"].value;
    //Call "setAttribute" to update the attributes.
    this.setAttribute("data-band-id", draggedItemAttr);
    dragSatMS.setAttribute("data-band-id", itemToReplaceAttr);
  }
  return false;
}

Here's a demo for good measure: Fiddle

1
On

Yass' answer is one way of solving the problem.

But I will now introduce another method to you that all tutorials I have seen on drag-and-drop carefully avoid - I believe the usage of drag-and-drop API provides an ideal scenario where event delegation should shine - however, till date (and as far as I know) none of the popular tutorials have explored that approach.

If you're in a hurry to see my suggestion working, see this fiddle.

So, you may delegate the handling of the drag events to the parent of the items, ul. And instead of swapping the innerHtml of the dragged element with that of the element dropping will occur on, swap the outerHtml.

var draggedItem = null;
var ul = document.getElementById('ms-saturday');

// delegate event handling to the parent of the list items
ul.addEventListener('dragstart', buffer(handleDragStart), false);
ul.addEventListener('dragover', buffer(handleDragOver), false);
ul.addEventListener('drop', buffer(handleDrop), false);

function handleDragStart(e) {
  draggedItem = e.target;
  e.dataTransfer.effectAllowed = 'move';

  // dataTransfer is not really required, but I think IE may need it.
  // Also, not that if you must use dataTransfer API,
  // IE strongly requires that the mime type must be 'text'.
  e.dataTransfer.setData('text', this.innerHTML);
}

function handleDragOver(e) {
  if (e.preventDefault) {
    e.preventDefault();
  }

  e.dataTransfer.dropEffect = 'move';

  return false;
}

function handleDrop(e) {
  var tmp;

  if (e.stopPropagation) {
    e.stopPropagation();
  }

  if (draggedItem !== e.target) {
    // swapp outerHtml here
    tmp = draggedItem.outerHTML;
    draggedItem.outerHTML = e.target.outerHTML;
    e.target.outerHTML = tmp;;
  }

  return false;
}

// this is used to create the handlers so that there won't be
// a need to repeat 'if (e.target === ul) {...' for as many times
// as there are handlers needed.
function buffer(fn) {

  return function(e) {

    // ignore drag-and-drop on the parent element itself
    if (e.target === ul) {
      return;
    }

    fn.call(this, e);
  };
}

I leave you to refine this approach to your taste.