Search code examples
javascriptdomaddeventlistenerremovechild

Javascript DOM Manipulation error with removeChild and addEventListener


I'm basically building a pretty simple list. Write something in the input field at the top, click the button and it appears in a ul underneath.

I'm getting two errors in my console, I'm not really sure what I've done wrong.

Firstly, on line 12, this event listener:

deleteBtn.addEventListener('click', removeItem);

The error says "Cannot read property 'addEventListener' of null" and I believe it is because the deleteBtn is not on the page at load, it is added to the DOM with the li as you add items to the list.

Secondly, on line 40:

selectedItem.removeChild(checkMark);

The error says "Failed to execute 'removeChild' on 'Node': The node to be removed is not a child of this node."

Here is all of my code:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Bootstrap Crash Course</title>

  <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta.2/css/bootstrap.min.css"
        integrity="sha384-PsH8R72JQ3SOdhVi3uxftmaW6Vc51MKb0q5P2rRUpPvrszuE4W1povHYgTpBfshb"
        crossorigin="anonymous" />
  <link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet"
        integrity="sha384-wvfXpqpZZVQGK6TAh5PVlGOfQNHSoD2xbE+QkPxCAFlNEevoEH3Sl0sibVcOQVnN"
        crossorigin="anonymous">

  <style type="text/css">
    #myButton {
      cursor: pointer;
    }

    .input-group {
      margin: 15px 0;
    }

    .fa-times-circle-o {
      font-size: 24px;
      cursor: pointer;
    }

    .fa-check-circle {
      font-size: 24px;
    }

  </style>
</head>
<body>

<div class="container">

  <div class="input-group">
    <input type="text" class="rounded form-control" id="myInput" />
    <span id="myButton" class="input-group-addon">Click</span>
  </div>

  <ul class="list-group" id="myOutput">

  </ul>

</div> <!-- .containter -->




  <script src="https://code.jquery.com/jquery-3.2.1.slim.min.js"
          integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN"
          crossorigin="anonymous"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.3/umd/popper.min.js"
          integrity="sha384-vFJXuSJphROIrBnz7yo7oB41mKfc8JzQZiCq4NCceLEaO4IHwicKwpJf9c9IpFgh"
          crossorigin="anonymous"></script>
  <script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta.2/js/bootstrap.min.js"
          integrity="sha384-alpBpkh1PFOepccYVYDB4do5UnbKysX5WZXm3XxPqe5iKTfUKjNkCk9SaVuEZflJ"
          crossorigin="anonymous"></script>
  <script src="app.js"></script>
</body>
</html>

//Code from app.js

let myButton = document.getElementById('myButton');
let myOutput = document.getElementById('myOutput');
let myInput = document.getElementById('myInput');
let listGroupItems = document.querySelectorAll('.list-group-item');
let deleteBtn = document.getElementById('deleteBtn');

setUpEventListeners();

function setUpEventListeners() {
  myButton.addEventListener('click', addItem);
  myOutput.addEventListener('click', toggleItem);
  deleteBtn.addEventListener('click', removeItem);
}

function addItem() {
  if (myInput.value === '') {
    console.log('Field is empty!');
  } else {
    let li = document.createElement('li');
    let inputValue = document.createTextNode(myInput.value);

    li.innerHTML = '<i class="fa fa-times-circle-o float-right" aria-hidden="true" id="deleteBtn"></i>';
    li.className = 'list-group-item';
    myOutput.appendChild(li);
    li.appendChild(inputValue);
  }
  myInput.value = '';
}

function toggleItem(e) {
  let selectedItem = e.target;
  let checkMark = document.createElement('i');
  checkMark.classList.add('fa', 'fa-check-circle', 'float-left');

  console.log(selectedItem);

  if (selectedItem.classList.contains('bg-success') && selectedItem.classList.contains('list-group-item')) {
    selectedItem.classList.remove('bg-success');
    selectedItem.classList.remove('text-white');
    //listGroupItems.removeChild(checkMark);
  } else if (!selectedItem.classList.contains('bg-success') && selectedItem.classList.contains('list-group-item')) {
    selectedItem.classList.add('bg-success');
    selectedItem.classList.add('text-white');
    selectedItem.appendChild(checkMark);
  }
}

function removeItem() {
  e.target.parentElement.remove();
}

Solution

  • Instead of adding event to deleteBtn prior to its creation add inline event using onclick while creation of the button and call the function removeItem and pass the context using this

    For the second problem listGroupItems is a an array. So there is no removeChild method in array.

    To solve the second problem get the index of the icon from the childNodes collection.Then use that index to remove this specific child

    let myButton = document.getElementById('myButton');
    let myOutput = document.getElementById('myOutput');
    let myInput = document.getElementById('myInput');
    let listGroupItems = document.querySelectorAll('.list-group-item');
    
    
    setUpEventListeners();
    
    function setUpEventListeners() {
      myButton.addEventListener('click', addItem);
      myOutput.addEventListener('click', toggleItem);
    
    }
    
    function addItem() {
      if (myInput.value === '') {
        console.log('Field is empty!');
      } else {
        let li = document.createElement('li');
        let inputValue = document.createTextNode(myInput.value);
        // Changed here adding onclick
        li.innerHTML = '<i class="fa fa-times-circle-o float-right" aria-hidden="true" onclick="removeItem(this)" class="deleteBtn"></i>';
        li.className = 'list-group-item';
        myOutput.appendChild(li);
        li.appendChild(inputValue);
    
      }
      myInput.value = '';
    }
    
    function toggleItem(e) {
      let selectedItem = e.target;
      if (selectedItem.classList.contains('bg-success') && selectedItem.classList.contains('list-group-item')) {
        selectedItem.classList.remove('bg-success');
        selectedItem.classList.remove('text-white');
        var iconIndex = '';
        // getting the index of the icon which have the specifc class from childNodes using its class
        for (var i = 0; i < selectedItem.childNodes.length; i++) {
          if (selectedItem.childNodes[i].className === "fa fa-check-circle float-left") {
            iconIndex = i;
          }
        }
        // Using that index to remove the icon child
        selectedItem.removeChild(selectedItem.childNodes[iconIndex]);
      } else if (!selectedItem.classList.contains('bg-success') && selectedItem.classList.contains('list-group-item')) {
        let checkMark = document.createElement('i');
        checkMark.classList.add('fa', 'fa-check-circle', 'float-left');
        selectedItem.classList.add('bg-success');
        selectedItem.classList.add('text-white');
        selectedItem.appendChild(checkMark);
      }
    }
    
    function removeItem(elem) {
      elem.parentNode.remove();
    }
    #myButton {
      cursor: pointer;
    }
    
    .input-group {
      margin: 15px 0;
    }
    
    .fa-times-circle-o {
      font-size: 24px;
      cursor: pointer;
    }
    
    .fa-check-circle {
      font-size: 24px;
    }
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta.2/css/bootstrap.min.css" integrity="sha384-PsH8R72JQ3SOdhVi3uxftmaW6Vc51MKb0q5P2rRUpPvrszuE4W1povHYgTpBfshb" crossorigin="anonymous" />
    <link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet" integrity="sha384-wvfXpqpZZVQGK6TAh5PVlGOfQNHSoD2xbE+QkPxCAFlNEevoEH3Sl0sibVcOQVnN" crossorigin="anonymous" />
    <script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.3/umd/popper.min.js" integrity="sha384-vFJXuSJphROIrBnz7yo7oB41mKfc8JzQZiCq4NCceLEaO4IHwicKwpJf9c9IpFgh" crossorigin="anonymous"></script>
    <script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta.2/js/bootstrap.min.js" integrity="sha384-alpBpkh1PFOepccYVYDB4do5UnbKysX5WZXm3XxPqe5iKTfUKjNkCk9SaVuEZflJ" crossorigin="anonymous"></script>
    
    <div class="container">
    
      <div class="input-group">
        <input type="text" class="rounded form-control" id="myInput" />
        <span id="myButton" class="input-group-addon">Click</span>
      </div>
    
      <ul class="list-group" id="myOutput">
    
      </ul>
    
    </div>