javascriptdrag-and-drop

How to use Javascript's drag and drop Api to sort rows and colums displayed as grid


I have the following grid:

.grid {
  display: grid;
  grid-template-columns: 100px 100px 100px 100px 100px;
  grid-template-rows: 50px 50px 50px 50px;
  grid-gap: 10px;
  &>* {
    padding: 10px;
    border: 1px solid black;
  }
  &>div:first-child {
    border: none;
  }
  & .column-header {
    background-color: lightgray;
  }
  & .row-header {
    background-color: lightgray;
  }
}
<div class="grid">
  <div></div>
  <div class="column-header">Col 1</div>
  <div class="column-header">Col 2</div>
  <div class="column-header">Col 3</div>
  <div class="column-header">Col 4</div>

  <div class="row-header">Row 1</div>
  <div>Cell 1.1</div>
  <div>Cell 2.1</div>
  <div>Cell 3.1</div>
  <div>Cell 4.1</div>

  <div class="row-header">Row 2</div>
  <div>Cell 1.2</div>
  <div>Cell 2.2</div>
  <div>Cell 3.2</div>
  <div>Cell 4.2</div>

  <div class="row-header">Row 3</div>
  <div>Cell 1.3</div>
  <div>Cell 2.3</div>
  <div>Cell 3.3</div>
  <div>Cell 4.3</div>

  <div class="row-header">Row 4</div>
  <div>Cell 1.4</div>
  <div>Cell 2.4</div>
  <div>Cell 3.4</div>
  <div>Cell 4.4</div>
</div>

How to use Javascript drag and drop api to:

  1. drag any of the "Column headers" cells and rearrange the columns horizontally
  2. drag any of the "Row headers" cells and rearrange the rows vertically

Solution

  • I have put together this example:

    Here is a working example (of course, you need to adjust hardcoded values or make it dynamic for your application):

    let dragSrcEl = null;
    
    function handleDragStart(e) {
      this.style.opacity = '0.4';
      dragSrcEl = this;
      e.dataTransfer.effectAllowed = 'move';
      e.dataTransfer.setData('text/html', this.innerHTML);
    }
    
    function handleDragEnd() {
      this.style.opacity = '1';
      items.forEach(function(item) {
        item.classList.remove('over');
      });
    }
    
    function handleDragOver(e) {
      if (e.preventDefault) {
        e.preventDefault();
      }
      return false;
    }
    
    function handleDragEnter() {
      this.classList.add('over');
    }
    
    function handleDragLeave() {
      this.classList.remove('over');
    }
    
    function handleDrop(e) {
      if (e.stopPropagation) {
        e.stopPropagation();
      }
      if (dragSrcEl !== this) {
        if (dragSrcEl.classList.contains('column-header') && this.classList.contains('column-header')) {
          swapColumns(dragSrcEl, this);
        } else if (dragSrcEl.classList.contains('row-header') && this.classList.contains('row-header')) {
          swapRows(dragSrcEl, this);
        }
      }
      return false;
    }
    
    function swapColumns(src, dest) {
      const srcIndex = Array.from(src.parentNode.children).indexOf(src);
      const destIndex = Array.from(dest.parentNode.children).indexOf(dest);
    
      const grid = document.querySelector('.grid');
      for (let i = 0; i < grid.children.length; i++) {
        if (i % 5 === srcIndex) {
          const temp = grid.children[i].innerHTML;
          grid.children[i].innerHTML = grid.children[i + (destIndex - srcIndex)].innerHTML;
          grid.children[i + (destIndex - srcIndex)].innerHTML = temp;
        }
      }
    }
    
    function swapRows(src, dest) {
      const srcIndex = Array.from(src.parentNode.children).indexOf(src) / 5;
      const destIndex = Array.from(dest.parentNode.children).indexOf(dest) / 5;
    
      const grid = document.querySelector('.grid');
      for (let i = 0; i < 5; i++) {
        const temp = grid.children[srcIndex * 5 + i].innerHTML;
        grid.children[srcIndex * 5 + i].innerHTML = grid.children[destIndex * 5 + i].innerHTML;
        grid.children[destIndex * 5 + i].innerHTML = temp;
      }
    }
    
    let items = document.querySelectorAll('.grid .column-header, .grid .row-header');
    items.forEach(function(item) {
      item.addEventListener('dragstart', handleDragStart);
      item.addEventListener('dragover', handleDragOver);
      item.addEventListener('dragenter', handleDragEnter);
      item.addEventListener('dragleave', handleDragLeave);
      item.addEventListener('dragend', handleDragEnd);
      item.addEventListener('drop', handleDrop);
    });
    .grid {
      display: grid;
      grid-template-columns: 100px 100px 100px 100px 100px;
      grid-template-rows: 50px 50px 50px 50px;
      grid-gap: 10px;
    }
    
    .grid>* {
      padding: 10px;
      border: 1px solid black;
    }
    
    .grid>div:first-child {
      border: none;
    }
    
    .column-header,
    .row-header {
      background-color: lightgray;
      cursor: move;
    }
    <div class="grid">
      <div></div>
      <div class="column-header" draggable="true">Col 1</div>
      <div class="column-header" draggable="true">Col 2</div>
      <div class="column-header" draggable="true">Col 3</div>
      <div class="column-header" draggable="true">Col 4</div>
    
      <div class="row-header" draggable="true">Row 1</div>
      <div>Cell 1.1</div>
      <div>Cell 2.1</div>
      <div>Cell 3.1</div>
      <div>Cell 4.1</div>
    
      <div class="row-header" draggable="true">Row 2</div>
      <div>Cell 1.2</div>
      <div>Cell 2.2</div>
      <div>Cell 3.2</div>
      <div>Cell 4.2</div>
    
      <div class="row-header" draggable="true">Row 3</div>
      <div>Cell 1.3</div>
      <div>Cell 2.3</div>
      <div>Cell 3.3</div>
      <div>Cell 4.3</div>
    
      <div class="row-header" draggable="true">Row 4</div>
      <div>Cell 1.4</div>
      <div>Cell 2.4</div>
      <div>Cell 3.4</div>
      <div>Cell 4.4</div>
    </div>