How can I solve this Null property on Javascript? - javascript

I'm trying to put a delete button on each li using JavaScript and to make an event handler that runs when a button is clicked that removes the li. However when I try to add the handler, I get:
Cannot read property 'addEventListener' of null
I think this is because I am referencing a class that not exist before run the function createbtn. So How can I solve this?
The Code:
I set the variables, put querySelector to buttons because I testing how to do it:
var button = document.getElementById("enter");
var input = document.getElementById("userinput");
var ul = document.querySelector("ul");
var list = document.querySelectorAll ("li");
var buttons = document.querySelector (".btn-danger");
var li = document.createElement("li")
How I create the button:
function createbtn() {
for (var i = 0; i < list.length; i++) {
var btn = document.createElement("button");
btn.appendChild(document.createTextNode("Delete"));
btn.classList.add("btn", "btn-danger","btn-sm");
list[i].appendChild(btn);
}
}
The function I try to run:
function liDel(){
li.parentNode.removeChild(li);
}
buttons.addEventListener("click", liDel);
This is my fiddle to see all the code.

The reason why you are getting the null error is because;
You have assigned the variable buttons to a node which doesn't exist yet. (Note that the button is created after the page has been loaded, which means .btn-danger hasn't yet been created at that time).
According to MDN the querySelector method does the the ff:
The Document method querySelector() returns the first Element within the document that matches the specified selector, or group of selectors. If no matches are found, null is returned.
Based on the code you have in the fiddle, here is a guide to achieve the desired results.
First of all, get rid of the global li variable on line 6.
The reason is that if you create a new li from the input, it will render on the same line because it's still referencing the same element node (I'm sure you've realized that)
then in your createListElement function, do the ff
function createListElement() {
var li = document.createElement('li');
li.appendChild(document.createTextNode(input.value));
var btn = document.createElement("button");
btn.appendChild(document.createTextNode("Delete"));
btn.classList.add("btn", "btn-danger","btn-sm");
btn.addEventListener('click', function(e){
if(!e) e = window.event;
try{
ul.removeChild(this.parentNode)
}catch(err){
alert(err.message)
}
})
li.appendChild(btn)
ul.appendChild(li);
input.value = "";
}
Then when you create the buttons, you have to attach the event listener function to it. So you do the ff in your createbtn function:
// To create a button
function createbtn() {
for (var i = 0; i < list.length; i++) {
var btn = document.createElement("button");
btn.appendChild(document.createTextNode("Delete"));
btn.classList.add("btn", "btn-danger","btn-sm");
btn.addEventListener('click', function(e){
if(!e) e = window.event;
try{
ul.removeChild(this.parentNode)
}catch(err){
alert(err.message)
}
})
list[i].appendChild(btn);
}
}
anyways, there are more efficient ways to do this. But this is a quick workable model based on the code in your fiddle

Rather than querying and adding the event to the buttons object
try the chaining inside the document load.
window.onload = function () {
document.querySelector('.btn-danger').addEventListener('click', liDel);
};
The above code should work!

Thanks a lot everybody, I got a solution after reading all your answers:
First I got rid the following:
var buttons = document.querySelector (".btn-danger");
var li = document.createElement("li")
Then create this function for remove the "li"
Using "this" you avoid the error for don't have a reference, because with that you don't care in what kind of element this is, you only now something is there and grab it for anything you need.
function liDel(){
ul.removeChild(this.parentNode);
}
and put this in createBtn for delete the existing "li" in the html:
btn.addEventListener('click', liDel);
then put this on createElement for do the same of the above, but for the new "li" creates with the DOM:
btn.addEventListener('click', liDel);
li.appendChild(btn);
And with that the problems was solved.
Thanks again and you can see how the page works on the fiddle

Related

how to remove selected list element, instead of removing only top li tag

I'm trying to remove specific li elements, based off of which one has the x button clicked. Currently I'm having an error
"bZMQWNZvyQeA:42 Uncaught TypeError: Failed to execute 'removeChild' on 'Node': parameter 1 is not of type 'Node'."
I am aware that this could mean that the paramater is null, but this dosn't make any sense to me. Chrome dev tools show that the onClick attribute is correctly exectuing removeItem, and passing in the idName as a parameter. How is this not working?
var note = 0;
function saveInfo() {
var idName = "note" + note;
//assign text from input box to var text, and store in local storage
var input = document.getElementById('input').value;
var text = localStorage.setItem(note, input);
var list = document.createElement("li");
var node = document.createTextNode(input);
var removeBtn = document.createElement("button");
list.setAttribute("id", idName);
removeBtn.setAttribute("onClick", `removeItem(${idName})`);
removeBtn.innerHTML = "X";
list.appendChild(node);
list.appendChild(removeBtn);
document.getElementById("output").appendChild(list);
note += 1;
}
function removeItem(name) {
var parent = document.getElementById("output");
var child = document.getElementById(name);
parent.removeChild(child);
}
In my comment, I suggested that you listen to click event bubbling from the removeBtn. In this case, all you need is to remove the onclick attribute assignment logic from your code, and instead give your removeButton an identifiable property, such as a class. Lets give it a class of delete-button:
var removeBtn = document.createElement("button");
removeBtn.classList.add('delete-button');
removeBtn.type = 'button';
removeBtn.innerHTML = 'X';
Then, you can listen to the click event at the level of #output, which is guaranteed to be present at runtime. When the event is fired, you simply check if the event target has the identifiable property, e.g. the remove-button class in our case:
output.addEventListener('click', function(e) {
// GUARD: Do nothing if click event does not originate from delete button
if (!e.target.matches('.remove-button')) {
return;
}
// Delete parent node
e.target.closest('li').remove();
});
If the click event did not originate from the remove button, we simply return and don't do anything else. Otherwise, we know that the button has been clicked, and we can then use Element.closest(), i.e. .closest('li') to retrieve the closest <li> parent node and delete it.
If you absolutely have to support IE11 (which in turn, does not support Element.closest()), you can also use Node.parentNode to access and delete the <li> element, assuming that your remove button is a direct child of the <li> element:
// Delete parent node
e.target.parentNode.remove();
See proof-of-concept below:
var rows = 10;
var output = document.getElementById('output');
for (var i = 0; i < rows; i++) {
var list = document.createElement('li');
var node = document.createTextNode('Testing. Row #' + i);
var removeBtn = document.createElement("button");
removeBtn.classList.add('remove-button');
removeBtn.type = 'button';
removeBtn.innerHTML = 'X';
list.appendChild(node);
list.appendChild(removeBtn);
output.appendChild(list);
}
output.addEventListener('click', function(e) {
// GUARD: Do nothing if click event does not originate from delete button
if (!e.target.matches('.remove-button')) {
return;
}
e.target.closest('li').remove();
});
<ul id="output"></ul>
The issue is that you have missing quotes around the id that you pass to removeItem:
removeBtn.setAttribute("onClick", `removeItem(${idName})`);
This should be:
removeBtn.setAttribute("onClick", `removeItem('${idName}')`);
Better pattern
It is better practice to bind the click handler without relying on string evaluation of code, and without needing to create dynamic id attribute values:
removeBtn.addEventListener("click", () => removeItem(list));
And then the function removeItem should expect the node itself, not the id:
function removeItem(child) {
child.parentNode.removeChild(child);
}
You can remove the following code:
var idName = "note" + note;
list.setAttribute("id", idName);

Explaining parentNode

This isn't a help me solve something kind of question rather a explain what this does type question.
I understand what parentNode does but I can't wrap my head around the context of how it works with my code. The reason I was able to write the code was through a YouTube tutorial.
I was learning how to create a todo list app where you were able to add stuff and remove it. I made the remove button but the code I don't understand is how the remove function works in the code.
By my understanding, I'm thinking that it deletes the child which is the LI from the parent which is the UL?
If someone could explain the removeItem() function and what the code does I would very much appreciate it.
var input = document.getElementById('input'),
button = document.getElementById('add')
function removeItem() {
var item = this.parentNode
var parent = item.parentNode
parent.removeChild(item)
}
button.addEventListener('click', function(e) {
var p = document.querySelector('p')
if (input.value.trim() === '') {
p.style.display = 'block'
return false
}
p.style.display = ''
var userInput = document.createTextNode(input.value)
var li = document.createElement('li')
var ul = document.getElementById('todo')
var remove = document.createElement('button')
remove.innerHTML = 'Remove'
remove.addEventListener('click', removeItem);
ul.insertBefore(li, ul.childNodes[0])
li.appendChild(userInput)
li.appendChild(remove)
})
<input type="text" id="input"/>
<button id="add">Add</button>
<p>plz add</p>
<ul id="todo"></ul>
You are correct. The best way to remove a node in Javascript is from its parent, using the removeChild() function.
You could use the remove() function like item.remove(), but this will not work with IE because in IE this function does another thing, it removes an option from a drop-down list (select).
So, to achieve cross-browser behavior, it is used the removeChild approach.

".addEventListener is not a function" why does this error occur?

I’m getting an ".addEventListener is not a function" error. I am stuck on this:
var comment = document.getElementsByClassName("button");
function showComment() {
var place = document.getElementById('textfield');
var commentBox = document.createElement('textarea');
place.appendChild(commentBox);
}
comment.addEventListener('click', showComment, false);
<input type="button" class="button" value="1">
<input type="button" class="button" value="2">
<div id="textfield">
</div>
The problem with your code is that the your script is executed prior to the html element being available. Because of the that var comment is an empty array.
So you should move your script after the html element is available.
Also, getElementsByClassName returns html collection, so if you need to add event Listener to an element, you will need to do something like following
comment[0].addEventListener('click' , showComment , false ) ;
If you want to add event listener to all the elements, then you will need to loop through them
for (var i = 0 ; i < comment.length; i++) {
comment[i].addEventListener('click' , showComment , false ) ;
}
document.getElementsByClassName returns an array of elements. so may be you want to target a specific index of them: var comment = document.getElementsByClassName('button')[0]; should get you what you want.
Update #1:
var comments = document.getElementsByClassName('button');
var numComments = comments.length;
function showComment() {
var place = document.getElementById('textfield');
var commentBox = document.createElement('textarea');
place.appendChild(commentBox);
}
for (var i = 0; i < numComments; i++) {
comments[i].addEventListener('click', showComment, false);
}
Update #2: (with removeEventListener incorporated as well)
var comments = document.getElementsByClassName('button');
var numComments = comments.length;
function showComment(e) {
var place = document.getElementById('textfield');
var commentBox = document.createElement('textarea');
place.appendChild(commentBox);
for (var i = 0; i < numComments; i++) {
comments[i].removeEventListener('click', showComment, false);
}
}
for (var i = 0; i < numComments; i++) {
comments[i].addEventListener('click', showComment, false);
}
var comment = document.getElementsByClassName("button");
function showComment() {
var place = document.getElementById('textfield');
var commentBox = document.createElement('textarea');
place.appendChild(commentBox);
}
for (var i in comment) {
comment[i].onclick = function() {
showComment();
};
}
<input type="button" class="button" value="1">
<input type="button" class="button" value="2">
<div id="textfield"></div>
The first line of your code returns an array and assigns it to the var comment, when what you want is an element assigned to the var comment...
var comment = document.getElementsByClassName("button");
So you are trying to use the method addEventListener() on the array when you need to use the method addEventListener() on the actual element within the array. You need to return an element not an array by accessing the element within the array so the var comment itself is assigned an element not an array.
Change...
var comment = document.getElementsByClassName("button");
to...
var comment = document.getElementsByClassName("button")[0];
Another important thing you need to note with ".addEventListener is not a function" error is that the error might be coming a result of assigning it a wrong object eg consider
let myImages = ['images/pic1.jpg','images/pic2.jpg','images/pic3.jpg','images/pic4.jpg','images/pic5.jpg'];
let i = 0;
while(i < myImages.length){
const newImage = document.createElement('img');
newImage.setAttribute('src',myImages[i]);
thumbBar.appendChild(newImage);
//Code just below will bring the said error
myImages[i].addEventListener('click',fullImage);
//Code just below execute properly
newImage.addEventListener('click',fullImage);
i++;
}
In the code Above I am basically assigning images to a div element in my html dynamically using javascript. I've done this by writing the images in an array and looping them through a while loop and adding all of them to the div element.
I've then added a click event listener for all images.
The code "myImages[i].addEventListener('click',fullImage);" will give you an error of "addEventListener is not a function" because I am chaining an addEventListener to an array object which does not have the addEventListener() function.
However for the code "newImage.addEventListener('click',fullImage);" it executes properly because the newImage object has access the function addEventListener() while the array object does not.
For more clarification follow the link: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Not_a_function
The main reason of this error is this line
document.getElementsByClassName("button")
Cause the getElementsByClassName returns an array-like object of all child elements or a collection of elements.
There are two possible solutions AFAIK -
Treat the variable containing document.getElementsByClassName("button") as an array and be specific when using an event listener.
Example -
comment[0].addEventListener('click' , showComment , false )
Use id for selecting that specific element.
Example-
document.getElementById('button')
Try this one:
var comment = document.querySelector("button");
function showComment() {
var place = document.querySelector('#textfield');
var commentBox = document.createElement('textarea');
place.appendChild(commentBox);
}
comment.addEventListener('click', showComment, false);
Use querySelector instead of className
<script src="main.js" defer></script>
which makes execute your code after the document fully loaded hence the javascript has complete reference

How to remove an specific <li> with pure javascript

I'm trying to figure out, how am i able to delete a specific <li> with pure javascript ?
My purpose is: each <li> does have a "remove" button and if we click on that button, it will remove that <li>.
function remove(r){
**REMOVE**
}
function add(){
var ul = document.getElementById("ul");
var li = document.createElement("li");
if(document.getElementById("nameS").value && document.getElementById("mailS").value){
var nameS = document.createElement("i");
nameS.innerHTML = document.getElementById("nameS").value;
nameS.innerHTML += ": ";
var mailS = document.createElement("font");
mailS.setAttribute("color","#000080");
mailS.innerHTML = document.getElementById("mailS").value;
mailS.innerHTML += " - ";
if(document.getElementById("webpageS").value){
mailS.innerHTML += ""+document.getElementById("webpageS").value+"";
}
var element = document.createElement("input");
element.setAttribute("type","button");
element.setAttribute("value","Remover");
//element.setAttribute("onclick",remove());
element.addEventListener('click',function(){
li.remove();
},false);
li.appendChild(nameS);
li.appendChild(mailS);
li.appendChild(element);
ul.appendChild(li);
}
}
If you add your remove function using addEventListener as suggested by #Eevee, the first argument passed to remove will be an Event object. (For a click event, it will be a MouseEvent object.)
Every Event object has a property, target, which tells you where the element came from. So, you can simply go up the tree of elements to get the li, and remove that:
function removeParent(evt) {
evt.target.parentNode.remove();
}
element.addEventListener('click', removeParent, false);
Some other comments on your code:
Please don't ever use font elements; likewise i elements. You should use span if you want to apply styles; if you want to emphasise text, use em.
What i would do would be to add a unique id to each of the li's that you can later reference to remove it:
var curId = 0;
function add(){
....
var li = document.createElement("li");
li.setAttribute(id,curId)
...
curId++;
}
Then in your button pass that id
element.setAttribute("onclick",remove(curId));
Then for your remove function it is simply:
function remove(ele){
ducument.getElementById(ele).remove();
}

Delete current parent node in recursion

I created multiple divs which class="extra". Then I add delete buttons to each div in order to remove each div respectively. So my code is:
var exr = document.getElementsByClassName("extra");
for(var i = 0 ;i<exr.length;i++){
var delbt = document.createElement("button");
delbt.className="floatbutton_3 font_b"
delbt.innerHTML="delete";
exr[i].appendChild(delbt);
delbt.onclick= function(i){ return function(){ exr[i].parentNode.removeChild(exr[i]) } }(i);
}
The problem is, the button can't removethe button it should remove. It seems that after last delete, the index is changed. How to avoid this from happening?
Thanks!
Use this within the onclick function to reference the button that was clicked — so by replacing excr[i] with this.parentNode, your code should execute as intended.
Do this...
var exr = document.getElementsByClassName("extra");
for(var i = 0 ;i<inserter.length;i++){
var delbt = document.createElement("button");
delbt.className="floatbutton_3 font_b"
delbt.innerHTML="delete";
exr[i].appendChild(delbt);
delbt.index = i;
delbt.onclick= function(i){ var me = this; return function(){ exr[me.index].parentNode.removeChild(exr[i]) } }(i);
}
Since getElementsByClassName() returns a live set, you could make a shallow copy first:
var exr = [].slice.call(document.getElementsByClassName("extra"), 0);
You could use currentTarget on the click event.
This will only remove the parent div of the button clicked.
var exr = document.getElementsByClassName("extra");
for(var i = 0 ;i<exr.length;i++){
var delbt = document.createElement("button");
delbt.className="floatbutton_3 font_b"
delbt.innerHTML="delete";
exr[i].appendChild(delbt);
delbt.onclick= function( event ){
event.currentTarget.parentElement.remove();
}
}
The code looks like it should work, but only as long as you don't add or remove elements. And that's what you are doing.
There is a simpler and more reliable solution: You can use this inside the event handler to refer to the current element.
delbt.onclick = function() {
var div = this.parentNode; // you want to remove the div, not the button
div.parentNode.removeChild(div);
};
I recommend to read the excellent articles about event handling on quirksmode.org, especially about traditional event handling (since that's what you are using).
getElementsByClassName() returns an HTMLCollection. This is a live list that changes when the underlying structure changes. So even if you don't activly delete the elements from the list, they still will be removed from it automatically when you remove the corresponding HTML Element.
By passing i to the anonymous function, you kind of fixed the index. So whenever you click on the third delete button it will try to delete the third element in the list. But if you have already deleted the first and the second element, the list will be modified too. So your third button will be represented by the first item in your list but it will try to find the third one.
To avoid this problem you can pass the complete element to the anonymous function and not just the index:
for(var i = 0 ;i<exr.length;i++){
var delbt = document.createElement("button");
delbt.className="floatbutton_3 font_b"
delbt.innerHTML="delete";
exr[i].appendChild(delbt);
delbt.onclick = function(el){ return function(){ el.parentNode.removeChild(el) } }(exr[i]);
}
I'll add a jQuery solution:
$('.extra').append(function() {
return $('<button />', {'class':'floatbutton_3 font_b', html:'delete'});
});
$('.floatbutton_3').on('click', function() {
$(this).closest('div').remove();
});
FIDDLE

Categories