Removing elements from a document in Javascript [duplicate] - javascript

This question already has answers here:
JavaScript DOM remove element
(4 answers)
Closed 8 years ago.
Working on building this to-do list app to learn JS better.
I am able to insert text into box and have it add a new div element to the page with the relevant text residing within the div. The code that adds the div to the page automatically applies the class .newItem then adds an incrementing id i0, i1, i2, etc each time it's clicked. Everything works without an issue. Now, I have been fiddling around with the code to be able to click a single div element and have it remove itself from the page, but can't seem to get it to work.
var iDN = 0;
//Function that adds a new div with a custom incrementing id number
document.getElementById('add_button').onclick = function () {
var taskName = document.getElementById('add_task').value; // Store the value in the textbox into a variable
document.querySelector('.shit_to_do').innerHTML += '<div class = "newItem" id = "i' + iDN + '"' + 'onclick = removeEl()' + '>' + taskName + '</div>';
iDN += 1;
};
document.getElementById('testing').onclick = function () {
var parentNode = document.querySelector('.shit_to_do');
parentNode.removeChild(parentNode.children[0]);
}
function removeEl() {
for (i = 0; i < iDN; i++) {
if (document.getElementById('i' + i).onclick) {
document.getElementById('i' + i).display.style = 'none';
}
alert(i);
}
}
The for loop was really some random option I was trying to figure out how things were working onclick for each div, but didn't get to far with that one.
tl;dr:
I want to add click events to the new divs added onto the page in a single, universal function.

The value of document.getElementById('i' + i).onclick will be null if you've not set a handler to this attribute/property, otherwise it will be a function. null is always falsy, a function is always truthy.
To remove your element, you'll either have to look at this or e.target where e is the click event, and then call the DOM method node.removeChild(child).
The "quick and dirty" solution is to pass this into removeEl and remove it that way,
// ...
document.querySelector('.shit_to_do').innerHTML += '<div class="newItem" id="i' + iDN + '" onclick="removeEl(this)">' + taskName + '</div>';
// ...
function removeEl(elm) {
elm.parentNode.removeChild(elm);
}
I also removed the strange spacing between attribute names and values in your HTML
A perhaps "cleaner" solution is to create your nodes and attach listeners all by using DOM methods
function createDiv(index, taskname) {
var d = document.createElement('div');
d.setAttribute('id', 'i' + index);
d.textContent = taskname;
return d;
}
function removeElm() {
this.parentNode.removeChild(this);
}
var iDN = 0;
document.getElementById('add_button').addEventListener('click', function () {
var taskName = document.getElementById('add_task').value,
list = querySelector('.shit_to_do'),
div = createDiv(iDN, taskName);
div.addEventListener('click', removeElm);
list.appendChild(div);
iDN += 1;
});
This way means the browser does not re-parse any HTML as it not use element.innerHTML, which is a dangerous property may destroy references etc (along with usually being a bit slower)
Helpful links
node.addEventListener
document.createElement
node.appendChild

Related

Why does my button click listener not work in chrome extension?

I'm trying to create a chrome extension, my problem is that when I try to place an event listener to each button in a class, only the first button has one, and the rest don't have an event listener.
function copyButtonInitialise(){
var copyButtons = document.getElementsByClassName("copyPassword");
console.log("length = ", copyButtons.length);
for (var i = 0; i < copyButtons.length; i++){
console.log(copyButtons[i] + " element number " + i + "= button");
copyButtons[i].addEventListener("click", copyButtonClick);
}
}
This function is what should be called if any button with the class "copyPasword" is clicked.(just want to make sure it gets clicked, but it doesn't)
function copyButtonClick(){
console.log("Hello There");
}
This is the function that loads passwords, it's called before adding event listeners to buttons.
async function loadPasswords(){
document.getElementById("passwordTable").innerHTML = "";
console.log("This is the loadpasswords function");
chrome.storage.sync.get(null, function(items) {
var allKeys = Object.keys(items);
var passwordTable = document.getElementById("passwordTable");
var header = passwordTable.createTHead();
var passwordRow = header.insertRow(0);
for(var i = 0; i < allKeys.length; i++){
let passwordKey = allKeys[i];
chrome.storage.sync.get([allKeys[i]], function(value){
var passwordName = Object.keys(value);
passwordName = passwordName[0];
var table = document.getElementById("passwordTable");
var header = table.createTHead();
var passwordRow = header.insertRow(0);
var cellTwo = passwordRow.insertCell(0);
var cell = passwordRow.insertCell(1);
cellTwo.innerHTML = "<p1 id=passwordNameCol>" + passwordName + "</p1>";
cell.innerHTML = "<button class=copyPassword> Copy " + '"'+ passwordName + '"'+ "</button>";
});
}
});
}
The passwords clearly load in.
When I click the buttons, nothing gets sent to the console, expecting a "hello there" (as shown above)
Try these things:
the outer chrome.storage.sync.get(null returns all stored couples key+value.
Why you use chrome.storage.sync.get a second time inside the "for" statement? It is not necessary.
Don't use insert methods of table but try with createElement and appendChild.
Create first an THEAD (or TBODY) element and then put every rows on it.
When you'll finish you'll have to append only that THEAD\TBODY as child of your table.
Try to create the button with createElement (as i suggest for any other table elements) and after its creation put the event listener on it (inside the "for").
if you think to reuse the same table for other differente rows remenber to destroy the THEAD first otherwise the just created events listeners will remain orphans.
Destroy the THEAD with something like element.remove() and not with innerHTML = "".

change events stop working when a new change event is added to a different element in JS

I am programmatically generating some HTML, and trying to add a listener for a change event the elements.
This works fine for the first object, but as soon as I add the second object the first one stops firing the event.
In the code example below you'll see the updateLabel function only fires for the last object created. I need it to fire for all of the objects.
I have tried with .onchange, and with an event listener, but get the same results.
Any help much appreciated.
<html>
<body>
<div id="main">
<!--this is where all the generated HTML goes -->
</div>
</body>
<script>
mainDomElement = document.querySelector("#main");
for (var count = 0; count < 4; count++)
{
var labelId = 'Label' + count;
newHTML = '<input class="accordionLabel" type="text" id="' + labelId + '" value="' + labelId + '"/>'
currentHTML = mainDomElement.innerHTML
mainDomElement.innerHTML = newHTML + currentHTML
labelDomObj = document.querySelector('#' + labelId);
//labelDomObj.addEventListener("change", updateLabel);
labelDomObj.onchange = function(event){updateLabel(event)}
}
function updateLabel(event)
{
alert(event.target.value);
}
</script>
</html>
It may be best to take a different approach when creating and adding DOM elements. Try this.
for (var count = 0; count < 4; count++)
{
var labelId = 'Label' + count,
newHTML = document.createElement('input');
newHTML.type = 'text';
newHTML.value = labelId;
newHTML.id = labelId;
newHTML.addEventListener('onchange', updateLabel, false);
mainDomElement.appendChild(newHTML);
}
your code explanation
// this takes the main DOM element and stores a copy of it in the variable.
currentHTML = mainDomElement.innerHTML
/*
This is taking the innerHTML property of the main DOM element. It is then
trying to concatenate the newly created DOM to that stored in the mainDomElement
variable. I don’t think this is what you want.
*/
mainDomElement.innerHTML = newHTML + currentHTML
// this is trying to select an element from the DOM that does not exist yet.
labelDomObj = document.querySelector('#' + labelId);
// This is trying to add an event listener to an element that does not exist.
labelDomObj.onchange = function(event){updateLabel(event)}
You are also missing sim icons from your variable declarations.
This looks like an issue with closures, where initializing var count = 0 within the for loop ultimately results in only the last value getting bound to the event handler.
I believe moving the initialization outside of the loop will fix your issue. Also, ES6 introduced the let keyword that scopes the variable in the way you are expecting:
for (let count = 0; count < 4; count++) { }
See this excellent introduction to javascript closures for more information.

JS: Avoiding modifications of ".innerHTML"

I have some <div> in which I all the time add new objects.
These objects are assigned with listeners.
The problem is that when I add these new objects using .innerHTML, the previous listeners get lost.
Is it possible to create a JS string which represents an HTML object, and to append it as a child without .innerHTML += ... ?
I'll give an example:
var line_num = 0;
function addTextLine(line) {
var lineId = "line_" + line_num;
var lineHtml = "<p id = '" + lineId + "'>" + line + "</p>";
document.getElementById("some_div_id").innerHTML += lineHtml;
document.getElementById(line_id).addEventListener("click", function() {
alert("hello");
});
line_num += 1;
}
The modification of innerHTML of some_dive_id, removes the event listeners of the old <p> objects.
So - is it possible to convert the <p> HTML string into an object, and thus to append it to the some_div_id without modifying its .innerHTML ?
Your problem is that innerHtml erases then recreates the current DOM node; that's why you lose you event listeners.
you can insert your html with insertAdjacentHtml
document.getElementById("some_div_id").insertAdjacentHTML('afterbegin', lineHtml );
the afterbegin parameter assure the inserted html will be a child of your current node.
Look for more infos here: Element.insertAdjacentHTML()
Create the element and append it
var p = document.createElement("p");
p.innerHTML = line;
p.id = line_id; // or p.setAttribute("id", line_id);
p.addEventListener("click", function(){ });
document.getElementById("foo").appendChild(p);
Another option can be to create an element, and set the innerHTML and read the element from there. (First option is better)
var div = document.createElement("div");
div.innerHTML = lineHtml;
//now you can either select the children and append it or append the div.
use element.appendChild().
Your code is not working because everytime you use innerHtml += 'Something' you are removing anything inside that particular element and inserting old content with added string.
Instead you can create element with function and append it to parent element.
Rewriting your code should be:
var line_num = 0;
function addTextLine(line) {
var line = document.createElement('p');
line.id = "line_" + line_num;
line.textContent = line;
line.addEventListener("click", function() {
alert("hello");
});
document.getElementById("some_div_id").appendChild(line);
line_num += 1;
}

Sending a Static Value as a Parameter in an Event Listener

Using Javascript (and jQuery), I have an array of several objects. For each object in that array, I'm adding a small, clickable image to a div. Clicking each small image will change the path of a large image, thus showing a new picture.
Here's what I've got:
for (s = 0; s < splash.length; s += 1)
{
// add a new dot to this row
$(".splash_dots").append ("<div class = 'dot' id = 'dot" + s + "'><img src = 'images/dot2.png'></div>");
var id = 'dot' + s;
// add a click handler
document.getElementById (id).addEventListener ("click", function () {change_splash (s)});
}
//////////////////////////////////////////////////
function change_splash (s)
{
// load a new image for the large photo
$(".splash").attr ("src", splash[s].screenshot_link);
}
This almost works. The problem is that when change_splash() is called, I'm expecting the value of s to be what it was when the loop added the handler. For example, clicking the first dot will call change_splash(0). However, it's actually sending what s is when the click happens (after the loops ends, which is always array.length).
I have a temporary solution, but I don't like it::
if (s === 0) document.getElementById (id).addEventListener ("click", function () {change_splash (0)});
if (s === 1) document.getElementById (id).addEventListener ("click", function () {change_splash (1)});
This just seems really inefficient.
I'm basically just trying to create a sliding splash advertisement, like what you'd see on almost any retail homepage, so I know it's possible. Any ideas are appreciated.
Try with this
for (var s in splash){
// create the points, stores the id in the data-id attribute
$(".splash_dots").append ("<div class ='dot' data-id='" + s + "'><img src='images/dot2.png'></div>");
}
// click listener
$("body").on("click",".dot",function(){
// retrieve the id from the data-id attribute
var s = $(this).attr("data-id");
$(".splash").attr ("src", splash[s].screenshot_link);
});
See it in action in JSFiddle demo
you'll need a closure:
(function(id,s){
document.getElementById(id)
.addEventListener("click", function(){change_splash (s)});}(id,s));

Create only content of accordion in for loop

I'm trying to create the content part of an accordion in a for loop, with the title printed beforehand. This is what I tried:
$("#right").append(
"Team Leaders"
+ "<h4>Goals</h4>"
+ "<div><p>"
);
for (var i = 0; i < 5; i++) {
$("#right").append(
topGoals[i].name
+ ": "
+ topGoals[i].goals
+ "<br>"
);
}
$("#right").append("</p></div>");
$("#right").accordion();
However, that didn't work. The "Goals" part did appear as the accordion header, but the content inside of it was empty, and the content generated from the for loop appeared underneath the accordion.
I've created accordions in for loops before and had no trouble, but I've never tried to make the title outside of the loop. Am i doing something wrong here?
Edit:
The topGoals array in screen.js:
var topGoals = getTopFiveGoals(myTeam);
The function is in functions.js:
function getTopFiveGoals(team) {
var tempTeam = team.players.slice();
tempTeam.sort(function(a, b) {
return b.goals-a.goals;
});
tempTeam.splice(5, 15);
return tempTeam;
}
You're modifying the DOM tree many times, instead create a dom element in memory and append all child elements. At the end append it to the live DOM tree. In your code, the tags are not closed properly so it won't be generated properly.
jQuery will automatically close the tags such as <div><p> which are not closed properly.
$("#right").append(
"Team Leaders"
+ "<h4>Goals</h4>"
);
var $div = $("<div></div>");
for (var i = 0; i < 5; i++) {
$div.append("<p>"+
topGoals[i].name
+ ": "
+ topGoals[i].goals
+ "</p><br>"
);
}
$("#right").append($div);
$("#right").accordion();

Categories

Resources