Search code examples
javascriptlistsortingdrag-and-drop

Drag and Drop Issue in JavaScript Sortable List


Goodday all, This is my first question here:

I'm currently working on a web application that involves drag-and-drop sorting of items within separate lists. I have two distinct lists: a left list and a right list. The goal is to enable users to reorder items within each list using drag-and-drop functionality.

I've successfully implemented drag-and-drop sorting within each individual list. However, I'm encountering an unexpected behavior when dragging an item from the left list to the right list. Specifically, when I drag an item from the left list and drop it onto an item in the right list, the positions of the items in the right list appear to be swapped.

I'm seeking guidance on how to prevent the items in the right list from being affected when an item from the left list is dragged and dropped onto it. Ideally, I'd like to maintain the ability to sort items independently within each list while ensuring that dragging an item from one list doesn't impact the sorting of the other list.

Moreover it would be appreciated when you could advice if my code can be even cleaner and more readable.

Any comments welcome

Thanks in advance

I tried removing event listeners and adding them again, however it did not help.

const grid = document.querySelector('.grid');
const leftDiv = document.querySelector('.left-side')
const rightDiv = document.querySelector('.right-side')

const pairs = [
    {pol: 'jeden', eng: 'one'},
     {pol: 'dwa', eng: 'two'},
     {pol: 'trzy', eng: 'three'},
     {pol: 'cztery', eng: 'four'},
     {pol: 'piec', eng: 'five'},
    ]

    

    

const leftList = [];
const rightList = [];
let leftElement;


createList();

function createList() {
    const leftSide = [...pairs]
        .map(a => ({ value: a.pol, sort: Math.random() }))
        .sort((a, b) => a.sort - b.sort)
        .map(a => a.value)
        .forEach((item, index) => {

            leftElement = document.createElement('div');
            leftElement.setAttribute('data-index', index);
            leftElement.setAttribute('data-left', true);
            

            leftElement.innerHTML = `
                                    <div class="draggable left-draggable" draggable="true"  data-left="true">
                                        <p class="item-name">${item}</p>
                                    </div>
                                     `;
            leftDiv.appendChild(leftElement);

            leftList.push(leftElement)

        });

        const rightSide = [...pairs]
        .map(a => ({ value: a.eng, sort: Math.random() }))
        .sort((a, b) => a.sort - b.sort)
        .map(a => a.value)
        .forEach((item, index) => {

           const rightElement = document.createElement('div');
            rightElement.setAttribute('data-index', index);
            rightElement.setAttribute('data-right', true);
            

            rightElement.innerHTML = `
                                    <div class="draggable right-draggable" draggable="true" data-right="true">
                                        <p class="item-name">${item}</p>
                                    </div>
                                        `;

            rightDiv.appendChild(rightElement)

            rightList.push(rightElement)

        })

        addEventListener();
}




function addEventListener() {
    document.querySelectorAll('.draggable').forEach((item, index) =>{
        item.addEventListener('dragstart', dragStart);
    });

    document.querySelectorAll('[data-index]').forEach(item => {
        item.addEventListener('dragenter', dragEnter);
        item.addEventListener('dragleave', dragLeave);
        item.addEventListener('dragover', dragOver);
        item.addEventListener('drop', dragDrop);
        
    })
    
}

let dragStartIndex;

function dragStart(e) {
    

    if(e.target.getAttribute('data-left')){
    
        dragStartIndex = +this.closest('[data-index]').getAttribute('data-index');
    
        
    } else if(e.target.getAttribute('data-right')){
        
        dragStartIndex = +this.closest('[data-index]').getAttribute('data-index');
    
    }
     
}

function dragEnter() {
    
}
function dragLeave() {
   
}
function dragOver(e) {
    e.preventDefault();
   
}
function dragDrop(e) {   
    
    

    if(e.target.closest('[data-right]')){
       
        const dragEndIndex = +this.closest('[data-index]').getAttribute('data-index');
        console.log(dragEndIndex);
        swapItems(dragStartIndex, dragEndIndex, 'right');
        

    } else if(e.target.closest('[data-left]')){
        
        const dragEndIndex = +this.closest('[data-index]').getAttribute('data-index');
        console.log(dragEndIndex);
        swapItems(dragStartIndex, dragEndIndex, 'left');
    }

   
}


function swapItems(fromIndex, toIndex, data) {
    
   

    if(data === 'right' ){
        
        const itemOne = rightList[fromIndex].querySelector('.right-draggable');
        const itemTwo = rightList[toIndex].querySelector('.right-draggable');
            if(itemOne.getAttribute('data-right') && itemTwo.getAttribute('data-right')){
                rightList[fromIndex].appendChild(itemTwo)
                rightList[toIndex].appendChild(itemOne)
            } 
        
    } else if(data === 'left') {
    
        
        const itemOne = leftList[fromIndex].querySelector('.left-draggable');
        const itemTwo = leftList[toIndex].querySelector('.left-draggable');
            if(itemOne.getAttribute('data-left') && itemTwo.getAttribute('data-left')){
                leftList[fromIndex].appendChild(itemTwo)
                leftList[toIndex].appendChild(itemOne)
            }
        
    }
}











   
*{
    box-sizing: border-box;
    margin: 0;
    padding: 0;
}

.grid {
    display: flex;
    margin: 50px auto;
    width: 500px;
    justify-content: center;
    /* gap: 30px; */
}

div[data-index] {
    padding: 25px;
    border: solid 2px #333;
    
}

.item-name {
    font-size: 25px;
    font-family: 'Lucida Sans', 'Lucida Sans Regular', 'Lucida Grande', 'Lucida Sans Unicode', Geneva, Verdana, sans-serif;
    text-transform: uppercase;
    padding: 10px 20px;
    border: 2px solid #ccc;
    border-radius: 16px;
    text-align: center;
    cursor: pointer;
    
}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <link rel="stylesheet" href="style.css">
</head>
<body>
    <div class="grid">
        <div class="left-side"></div>
        <div class="right-side"></div>
    </div>

    <script src="main.js"></script>
</body>
</html>


Solution

  • Then you need when dragging starts to take note which side is this. Now all you need to do is check when dragging ends if the target indeed the intended one.

    const grid = document.querySelector('.grid');
    const leftDiv = document.querySelector('.left-side')
    const rightDiv = document.querySelector('.right-side')
    
    const pairs = [{
        pol: 'jeden',
        eng: 'one'
      },
      {
        pol: 'dwa',
        eng: 'two'
      },
      {
        pol: 'trzy',
        eng: 'three'
      },
      {
        pol: 'cztery',
        eng: 'four'
      },
      {
        pol: 'piec',
        eng: 'five'
      },
    ]
    
    const leftList = [];
    const rightList = [];
    let leftElement;
    
    createList();
    
    function createList() {
      const leftSide = [...pairs]
        .map(a => ({
          value: a.pol,
          sort: Math.random()
        }))
        .sort((a, b) => a.sort - b.sort)
        .map(a => a.value)
        .forEach((item, index) => {
    
          leftElement = document.createElement('div');
          leftElement.setAttribute('data-index', index);
          leftElement.setAttribute('data-left', true);
    
          leftElement.innerHTML = `<div class="draggable left-draggable" draggable="true"  data-left="true"><p class="item-name">${item}</p></div>`;
          leftDiv.appendChild(leftElement);
    
          leftList.push(leftElement)
    
        });
    
      const rightSide = [...pairs]
        .map(a => ({
          value: a.eng,
          sort: Math.random()
        }))
        .sort((a, b) => a.sort - b.sort)
        .map(a => a.value)
        .forEach((item, index) => {
    
          const rightElement = document.createElement('div');
          rightElement.setAttribute('data-index', index);
          rightElement.setAttribute('data-right', true);
    
          rightElement.innerHTML = `<div class="draggable right-draggable" draggable="true" data-right="true"><p class="item-name">${item}</p></div>`;
    
          rightDiv.appendChild(rightElement)
    
          rightList.push(rightElement)
    
        })
    
      addEventListener();
    }
    
    
    function addEventListener() {
      document.querySelectorAll('.draggable').forEach((item, index) => {
        item.addEventListener('dragstart', dragStart);
      });
    
      document.querySelectorAll('[data-index]').forEach(item => {
        item.addEventListener('dragenter', dragEnter);
        item.addEventListener('dragleave', dragLeave);
        item.addEventListener('dragover', dragOver);
        item.addEventListener('drop', dragDrop);
      })
    
    }
    
    let dragStartIndex;
    
    // I've add a variable that is set when drag start and checked when drag end
    let dragOrigin;
    
    function dragStart(e) {
    
      if (e.target.getAttribute('data-left')) {
    
        dragStartIndex = +this.closest('[data-index]').getAttribute('data-index');
        dragOrigin = "left"
    
      } else if (e.target.getAttribute('data-right')) {
    
        dragOrigin = "right"
        dragStartIndex = +this.closest('[data-index]').getAttribute('data-index');
    
      }
    
    }
    
    function dragEnter() {}
    
    function dragLeave() {}
    
    function dragOver(e) {
      e.preventDefault();
    
    }
    
    function dragDrop(e) {
    
      if (e.target.closest('[data-right]')) {
    
        if (dragOrigin != "right") {
          console.log("denied")
          return
        }
        const dragEndIndex = +this.closest('[data-index]').getAttribute('data-index');
        console.log(dragEndIndex);
        swapItems(dragStartIndex, dragEndIndex, 'right');
    
    
      } else if (e.target.closest('[data-left]')) {
    
        if (dragOrigin != "left") {
          console.log("denied")
          return
        }
    
        const dragEndIndex = +this.closest('[data-index]').getAttribute('data-index');
        console.log(dragEndIndex);
        swapItems(dragStartIndex, dragEndIndex, 'left');
      }
    
    }
    
    function swapItems(fromIndex, toIndex, data) {
    
      if (data === 'right') {
    
        const itemOne = rightList[fromIndex].querySelector('.right-draggable');
        const itemTwo = rightList[toIndex].querySelector('.right-draggable');
        if (itemOne.getAttribute('data-right') && itemTwo.getAttribute('data-right')) {
          rightList[fromIndex].appendChild(itemTwo)
          rightList[toIndex].appendChild(itemOne)
        }
    
      } else if (data === 'left') {
    
        const itemOne = leftList[fromIndex].querySelector('.left-draggable');
        const itemTwo = leftList[toIndex].querySelector('.left-draggable');
        if (itemOne.getAttribute('data-left') && itemTwo.getAttribute('data-left')) {
          leftList[fromIndex].appendChild(itemTwo)
          leftList[toIndex].appendChild(itemOne)
        }
    
      }
    }
    * {
      box-sizing: border-box;
      margin: 0;
      padding: 0;
    }
    
    .grid {
      display: flex;
      margin: 20px auto;
      width: 300px;
      justify-content: center;
      /* gap: 30px; */
    }
    
    div[data-index] {
      padding: 10px;
      border: solid 2px #333;
    }
    
    .item-name {
      font-size: 15px;
      font-family: 'Lucida Sans', 'Lucida Sans Regular', 'Lucida Grande', 'Lucida Sans Unicode', Geneva, Verdana, sans-serif;
      text-transform: uppercase;
      padding: 5px 10px;
      border: 2px solid #ccc;
      border-radius: 16px;
      text-align: center;
      cursor: pointer;
    }
    <div class="grid">
      <div class="left-side"></div>
      <div class="right-side"></div>
    </div>