Ошибка манипуляции DOM Javascript с помощью removeChild и addEventListener

Я в основном строю довольно простой список. Напишите что-нибудь в поле ввода вверху, нажмите кнопку, и оно появится в ul под ним.

Я получаю две ошибки в своей консоли, я не совсем уверен, что я сделал неправильно.

Во-первых, в строке 12 слушатель этого события:

deleteBtn.addEventListener('click', removeItem);

Ошибка говорит: "Не удается прочитать свойство 'addEventListener' со значением NULL", и я считаю, что это потому, что deleteBtn не находится на странице при загрузке, он добавляется в DOM вместе с li, когда вы добавляете элементы в список.

Во-вторых, в строке 40:

selectedItem.removeChild(checkMark);

Ошибка говорит: "Не удалось выполнить" removeChild "на" Узле ": удаляемый узел не является потомком этого узла".

Вот весь мой код:

<!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();
}

3 ответа

Решение

Вместо добавления события в deleteBtn до его создания добавьте встроенное событие, используя onclick при создании кнопки и вызове функции removeItem и передать контекст, используя this

Для второй проблемы listGroupItems это массив. Так нет removeChild метод в массиве.

Чтобы решить вторую проблему, получите индекс значка из childNodes collection.Then использовать этот индекс, чтобы удалить этот конкретный дочерний элемент

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>

Переменная deleteBtn не определена, так как у вас нет по крайней мере одного элемента с идентификатором deleteBtn при загрузке страницы.

Для удаления элемента используйте removeChild() метод, подпись это:

parentOfTarget.removeChild(target)

В следующем демо, шаблонные литералы и insertAdjacentHTML() был использован для консолидации создания и вставки элементов в DOM. Кстати, вы не можете присвоить идентификатор элементу, который создан несколько раз. идентификаторы уникальны, так #deleteBtn был удален. Идентификатор не нужен для кнопок, если вы все равно делегируете события.

Есть куча классов, включающих и выключающих, поэтому я помещаю их в массивы, а затем переключаю каждый класс на каждой итерации forEach()

Подробности прокомментированы в демоверсии

демонстрация

var xBtn = document.getElementById('xButton');
var xOut = document.getElementById('xOutput');
var xInp = document.getElementById('xInput');

xBtn.addEventListener('click', addItem);
xOut.addEventListener('click', iconClick);

function addItem() {
  if (xInp.value === '') {
    console.log('Field is empty!');
  } else {
    /* This is a Template Literal which is a string
    || with powerful syntax and methods
    */
    let li = `<li class='list-group-item'>
    <i class="fa fa-circle-o float-left"></i>
    &nbsp;${xInp.value}&nbsp;
    <i class="fa fa-times-circle-o float-right"></i>
    </li>`;

    // Use insertAdjacentHTML() instead of innerHTML
    xOut.insertAdjacentHTML('beforeend', li);
  }
  xInp.value = '';
}

function iconClick(e) {

  // Reference e.target
  var tgt = e.target;
  // Reference e.target's parent
  var item = tgt.parentElement;

  /* if e.target has class .float-left
  || forEach() will...
  */
  if (tgt.classList.contains('float-left')) {

    // toggle e.target's FA icon classes
    ['fa-check-circle', 'fa-circle-o'].forEach(function(c, idx) {
      tgt.classList.toggle(c);
    });

    // toggle e.target's parent's state classes
    ['bg-success', 'text-white'].forEach(function(i, idx) {
      item.classList.toggle(i);
    });

    /* ...but if it has class .float-right
    || remove the parent of e.target by referencing
    || the parent of the parent of e.target (grandma)
    */
  } else if (tgt.classList.contains('float-right')) {
    xOut.removeChild(item);

    // ...otherwise just end function
  } else {
    return false;
  }
}
<!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">
  <link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet">

  <style>
    #xButton {
      cursor: pointer;
    }
    
    .input-group {
      margin: 15px 0;
    }
    
    .fa {
      font-size: 24px;
      cursor: pointer;
    }
  </style>
</head>

<body>

  <div class="container">

    <div class="input-group">
      <input type="text" class="rounded form-control" id="xInput" />
      <button id="xButton" class="btn btn-primary input-group-addon">ADD</button>
    </div>

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

    </ul>

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




  <script src="https://code.jquery.com/jquery-3.2.1.slim.min.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.3/umd/popper.min.js"></script>
  <script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta.2/js/bootstrap.min.js"></script>

</body>

</html>

Другие вопросы по тегам