skip to Main Content

I’m trying to create a todo list then use drag and drop to arrange it as I want. I have a dummy data (list items) which I can arrange as I want. the problem I am having is any new todo I add to the list item can not be dragged or rearrange.

here is my code below

var form = document.getElementById('addForm');
var itemList = document.getElementById('items');

form.addEventListener('submit', addTodo);
itemList.addEventListener('click', removeItem)

function addTodo(e) {
  e.preventDefault()

  // get input value
  var newTodo = document.getElementById('todo');

  // create new li element
  var li = document.createElement('li')

  //add class draggable property
  li.className = 'draggable';
  li.draggable = true


  // add textnode with input value
  li.appendChild(document.createTextNode(newTodo.value))

  // delete button
  var delBtn = document.createElement('button')
  delBtn.className = 'btn btn-danger btn-sm float-right del';
  delBtn.appendChild(document.createTextNode('X'))
  li.appendChild(delBtn)

  itemList.appendChild(li)
  newTodo.value = ""
}

function removeItem(e) {
  if (e.target.classList.contains('del')) {
    if (confirm('Are you sure?')) {
      var li = e.target.parentElement
      itemList.removeChild(li)
    }
  }
}

const draggables = document.querySelectorAll('.draggable')

draggables.forEach(draggable => {
  draggable.addEventListener('dragstart', () => {
    draggable.classList.add('dragging')
  })
  draggable.addEventListener('dragend', () => {
    draggable.classList.remove('dragging')
  })
})

itemList.addEventListener('dragover', (e) => {
  e.preventDefault()
  const draggable = document.querySelector('.dragging')
  const afterElement = getDragAfterElement(draggable, e.clientY)

  console.log(afterElement);
  if (afterElement == null) {
    itemList.appendChild(draggable)
  } else {
    itemList.insertBefore(draggable, afterElement)
  }

})

function getDragAfterElement(draggables, y) {
  const draggableElements = [...document.querySelectorAll('.draggable:not(.dragging)')]
  console.log('Dragable', draggableElements);

  return draggableElements.reduce((closest, child) => {
    const box = child.getBoundingClientRect()
    const offset = y - box.top - box.height / 2

    if (offset < 0 && offset > closest.offset) {
      return {
        offset: offset,
        element: child
      }
    } else {
      return closest
    }
  }, {
    offset: Number.NEGATIVE_INFINITY
  }).element
}
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Todo task</title>
  <!-- Font Awesome -->
  <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.8.2/css/all.css">
  <!-- Google Fonts -->
  <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap">
  <!-- Bootstrap core CSS -->
  <link href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.5.0/css/bootstrap.min.css" rel="stylesheet">
  <!-- Material Design Bootstrap -->
  <link href="https://cdnjs.cloudflare.com/ajax/libs/mdbootstrap/4.19.1/css/mdb.min.css" rel="stylesheet">
  <link rel="stylesheet" href="style.css">
</head>

<body>
  <div class="container mt-2">
    <div class="row justify-content-center">
      <div class="col-md-8">
        <h1>Todo Task</h1>
        <div class="card">
          <div class="card-header bg-grey">
            <h4>What Todo</h4>
          </div>
          <div class="card-body">
            <form action="" id="addForm">
              <div class="form-group mt-2 pl-5">
                <label for="">Title of Task</label>
                <input type="text" id="todo" placeholder="Enter todo..." class="form-control">
              </div>

              <button type="submit" id="submit" class="btn btn-primary btn-sm">Submit</button>
            </form>
          </div>

        </div>
        <div class="jumb mt-4">
          <h2>Todo Lists</h2>
          <ul class="list-group" id="items">
            <li class="draggable" draggable="true">Item 1 <button class="btn btn-danger btn-sm float-right del">X</button></li>
            <li class="draggable" draggable="true">Item 2 <button class="btn btn-danger btn-sm float-right del">X</button></li>
            <li class="draggable" draggable="true">Item 3 <button class="btn btn-danger btn-sm float-right del">X</button></li>
          </ul>
        </div>
      </div>
    </div>
  </div>

  <!-- JQuery -->
  <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
  <!-- Bootstrap tooltips -->
  <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.4/umd/popper.min.js"></script>
  <!-- Bootstrap core JavaScript -->
  <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.5.0/js/bootstrap.min.js"></script>
  <!-- MDB core JavaScript -->
  <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/mdbootstrap/4.19.1/js/mdb.min.js"></script>
  <script src="app.js"></script>
</body>

</html>

2

Answers


  1. If I understand it correctly you are attaching some events to every item on the list to allow the drag&drop functionality.

    The new elements don’t have those events attached to them, though. You need to watch for new elements and initialize the drag&drop functionality for them too.

    NOTICE: Heretic Monkey has linked to a more comprehensive answer.

    Login or Signup to reply.
  2. You need to warp this piece of into function

    const draggables = document.querySelectorAll('.draggable')
    
            draggables.forEach(draggable => {
                draggable.addEventListener('dragstart', () => {
                    draggable.classList.add('dragging')
                })
                draggable.addEventListener('dragend', () => {
                    draggable.classList.remove('dragging')
                })
            })
    

    To

    function enableDragDrop() {
            const draggables = document.querySelectorAll('.draggable')
    
            draggables.forEach(draggable => {
                draggable.addEventListener('dragstart', () => {
                    draggable.classList.add('dragging')
                })
                draggable.addEventListener('dragend', () => {
                    draggable.classList.remove('dragging')
                })
            })
        }
    

    and call the function in addTodo(e) that is called event binding

    function addTodo(e) { 
    
      ....
      enableDragDrop();
    }
    

    also, call the function on page load show it should bind the drag events by default

    ....
    var form = document.getElementById('addForm');
    var itemList = document.getElementById('items');
    
    form.addEventListener('submit', addTodo);
    itemList.addEventListener('click', removeItem)
    enableDragDrop();
    

    Complete JS.

        var form = document.getElementById('addForm');
        var itemList = document.getElementById('items');
    
        form.addEventListener('submit', addTodo);
        itemList.addEventListener('click', removeItem)
        enableDragDrop();
    
        function addTodo(e) {
            e.preventDefault()
    
            // get input value
            var newTodo = document.getElementById('todo');
    
            // create new li element
            var li = document.createElement('li')
    
            //add class draggable property
            li.className = 'draggable';
            li.draggable = true
    
    
            // add textnode with input value
            li.appendChild(document.createTextNode(newTodo.value))
    
            // delete button
            var delBtn = document.createElement('button')
            delBtn.className = 'btn btn-danger btn-sm float-right del';
            delBtn.appendChild(document.createTextNode('X'))
            li.appendChild(delBtn)
    
            itemList.appendChild(li)
            newTodo.value = "";
    
            enableDragDrop();
        }
    
        function removeItem(e) {
            if (e.target.classList.contains('del')) {
                if (confirm('Are you sure?')) {
                    var li = e.target.parentElement
                    itemList.removeChild(li)
                }
            }
        }
    
        function enableDragDrop() {
            const draggables = document.querySelectorAll('.draggable')
    
            draggables.forEach(draggable => {
                draggable.addEventListener('dragstart', () => {
                    draggable.classList.add('dragging')
                })
                draggable.addEventListener('dragend', () => {
                    draggable.classList.remove('dragging')
                })
            })
        }
    
        itemList.addEventListener('dragover', (e) => {
            e.preventDefault()
            const draggable = document.querySelector('.dragging')
            const afterElement = getDragAfterElement(draggable, e.clientY)
    
            console.log(afterElement);
            if (afterElement == null) {
                itemList.appendChild(draggable)
            } else {
                itemList.insertBefore(draggable, afterElement)
            }
    
        })
    
        function getDragAfterElement(draggables, y) {
            const draggableElements = [...document.querySelectorAll('.draggable:not(.dragging)')]
            console.log('Dragable', draggableElements);
    
            return draggableElements.reduce((closest, child) => {
                const box = child.getBoundingClientRect()
                const offset = y - box.top - box.height / 2
    
                if (offset < 0 && offset > closest.offset) {
                    return {offset: offset, element: child}
                } else {
                    return closest
                }
            }, {offset: Number.NEGATIVE_INFINITY}).element
        }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search