0

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>
Heretic Monkey
  • 11,687
  • 7
  • 53
  • 122
Yhomi
  • 97
  • 1
  • 9
  • Does this answer your question? [Event binding on dynamically created elements?](https://stackoverflow.com/questions/203198/event-binding-on-dynamically-created-elements) (if you do not use jQuery, [there is an answer which does not use jQuery](https://stackoverflow.com/a/27373951/215552)) – Heretic Monkey Nov 05 '20 at 13:25

2 Answers2

0

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.

eloyra
  • 519
  • 2
  • 6
0

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
    }
Noman
  • 4,088
  • 1
  • 21
  • 36
  • 1
    yes, thank you, it worked, the new todo where not taking the class of 'dragging', had to put it in a function and it worked.. Thanks a lot – Yhomi Nov 05 '20 at 13:35
  • Glad it worked. everything was working except event on new items. – Noman Nov 05 '20 at 13:38