I'm working on a JavaScript project where a user can click a button to create a text element. However, I also want a feature where I can click a different button and the element that was created most recently will be removed, so In other words, I want to be able to click a button to create an element and click a different button to undo that action.
The problem I was having was that I created the element, then I would remove the element using:
element.parentNode.removeChild(element); , but it would clear all of the elements that were created under the same variable.
var elem = document.createElement("div");
elem.innerText = "Text";
document.body.appendChild(elem);
This code allows an element to be created with a button click. All elemente that would be created are under the "elem" variable. so when I remove the element "elem", all element are cleared.
Is there a simple way to remove on element at a time that were all created procedurally?
Thanks for any help
When you create the elements, give the a class. When you want to remove an element, just get the last element by the className and remove it.
The below snippet demonstrates it -
for(let i = 0; i<5; i++){
var elem = document.createElement("div");
elem.innerText = "Text " + i;
elem.className = "added";
document.body.appendChild(elem);
}
setTimeout(function(){
var allDivs = document.getElementsByClassName("added");
var lastDiv = allDivs.length-1;
document.body.removeChild(allDivs[lastDiv]);
}, 3000);
I would probably use querySelectors to grab the last element:
// optional
// this is not needed it's just a random string added as
// content so we can see that the last one is removed
function uid() {
return Math.random().toString(36).slice(2);
}
document.querySelector('#add')
.addEventListener('click', (e) => {
const elem = document.createElement('div');
elem.textContent = `Text #${uid()}`;
document.querySelector('#container').appendChild(elem);
// optional - if there are elements to remove,
// enable the undo button
document.querySelector('#undo').removeAttribute('disabled');
});
document.querySelector('#undo')
.addEventListener('click', (e) => {
// grab the last child and remove
document.querySelector('#container > div:last-child').remove();
// optional - if there are no more divs we disable the undo button
if (document.querySelectorAll('#container > div').length === 0) {
document.querySelector('#undo').setAttribute('disabled', '');
}
});
<button id="add">Add</button>
<button id="undo" disabled>Undo</button>
<div id="container"></div>
Related
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);
I have a function that creates an html element with an unique ID.
And after that I want that when I click this element I could call a new function.
Quick example:
1) I click a button "Create element";
2) An element is created with id of "New_Element";
3) I click the "New_Element";
4) I get a function that was already preset to this element.
My current code for creating an element.
var pageRows = document.getElementsByClassName('pageRows');
var pageRowID = "section";
var el = document.createElement('section');
el.setAttribute('id', pageRowID + pageRows.length);
var row = document.getElementById('allNewRows');
row.parentNode.appendChild(el);
el.innerText = "New " + pageRows.length + " ROW!";
Now that the Element of id "pageRowId0" is created I want to have a function that works when I click this element.
Best wishes.
Thanks for helping.
You can do element.onclick= function(){}
var pageRows = document.getElementsByClassName('pageRows');
var pageRowID = "section";
var el = document.createElement('section');
el.setAttribute('id', pageRowID + pageRows.length);
el.onclick = function(){
/*write your fn here*/
};
var row = document.getElementById('allNewRows');
row.parentNode.appendChild(el);
el.innerText = "New " + pageRows.length + " ROW!";
You can use event delegation:
var row = document.getElementById('allNewRows');
row.parentNode.onclick = function(e) {
if (e.target.nodeName.toLowerCase() == 'select') {
//click on target select element
}
};
The snippet below has two parts. The first piece of code allows you to add a bunch of elements with different texts to the document.
The second parts shows the text of the element you clicked.
You will notice that the click event handler is just assigned to the parent element in which the new elements are added. No explicit click event handlers are bound to the new element.
I like to use addEventListener, because I think it's better to add a listener for a specific goal than to override any other event listeners by bluntly setting 'onclick', but that's a matter of opinion.
// Only run this code when the DOM is loaded, so we can be sure the proper elements exist.
window.addEventListener('DOMContentLoaded', function(){
// The code to add an element when the add button was clicked.
document.getElementById('add').addEventListener('click', function() {
var element = document.createElement('div');
element.innerText = document.getElementById('text').value;
element.className = 'clickableElement';
document.getElementById('elements').appendChild(element);
});
// Click event handler for the 'elements' div and everything in it.
document.getElementById('elements').addEventListener('click', function(event) {
var target = event.target; // The element that was clicked
// Check if the clicked element is indeed the right one.
if (target.classList.contains('clickableElement')) {
alert(target.innerText);
}
});
})
<input id="text" value="test"><button id="add">add</button>
<div id="elements"></div>
It's very difficult for me to show you my code, as it's all over the place, but what I'm trying to do is this:
I am injecting html code into the DOM in a function buy using .innerHTML, I wish to add a click event to an icon that is being injected in this step, as at this moment in time I know its id. So after I've injected it I write:
document.getElementById(product.id+"x").addEventListener("click", removeItem);
product.id is created above and this element is a 'X' button, that when clicked will be removed from the screen.
The trouble is, this code is run many times as there are many items to be displayed on the screen. And when finished, only the last even made fires when the 'X' button is pressed.
Any suggestions?
EDIT:
I am unable to use jquery in this project.
Here is my code:
function createHTML(targetID, product) {
var target = document.getElementById(targetID);
total = (parseFloat(total) + parseFloat(product.price)).toFixed(2);;
target.innerHTML += '<article class="item" id="'+product.id+'"><img class="item_img" src="../'+product.image+'" width=100 height=100><h1 class="item_name">'+product.name+'</h1><p class="item_description">'+product.desc+'</p><h1 class="item_quantity">Quantity: '+product.quantity+'</h1><h1 class="item_price">£'+product.price+'</h1><i id="'+product.id+'x" class="fa fa-times"></i></article>';
document.getElementById(product.id+"x").addEventListener("click", removeItem, true);
}
So you're adding new elements to a container by overwriting the innerHTML or appending to it using +=. This is your problem. When you overwrite the innerHTML or append to it, you are destroying and recreating all elements within it and this causes them to lose any bound event handlers (ie your click handler).
This fiddle reproduces your problem. Only the last button has a click handler.
The solution is to build DOM elements using document.createElement() and use appendChild() or similar to append them, instead of creating/appending raw HTML. This way, your previous elements event handlers will remain intact.
This Fiddle uses DOM nodes instead of raw HTML and all buttons have a click handler.
Example fix:
var container = document.getElementById("container");
var elem;
function clicky(){
alert("clicked");
}
for(var i=0; i<4; i++){
elem = document.createElement('button');
elem.id = "btn_" + i;
elem.appendChild(document.createTextNode('Click'));
elem.addEventListener("click", clicky);
container.appendChild(elem);
}
I quess you do something like that
//Place where you add elements.
var container = document.body;
you create element and add listener to that element(button):
var button = '<button id="btn1x">Button 1</button>';
container.innerHTML += button;
//product.id = 'btn1';
document.getElementById(product.id+"x").addEventListener("click", removeItem);
and then you add in the same way new elements and add for them event listeners before next element will be generated.
If my quess is right, then your problem is that you replace whole content of container so previous event listens are lost.
stringVariable += 'abc' is the same as stringVariable = stringVariable + 'abc'. Because of that you overwrite html.
You should create elements from functions, not from string as you do now.
var button = document.createElement('button');
button.id = product.id + 'x';
button.innerText = 'Button 1'; // Title of button.
//Add button to container.
container.appendChild(button);
//Add event listener to created button.
button.addEventListener('click', myFunc);
UPDATE:
There are a way to parse your string to element.
First create container where will be set inner html from string, then get from that temp container first element (or more elements, depends from your html string), then add them to container and add to these elements listeners.
DEMO: http://jsfiddle.net/3cD4G/1/
HTML:
<div id="container">
</div>
Javascript:
var container = document.getElementById("container");
function clicky(){
alert("clicked");
}
var tempContainer = document.createElement('div');
for(var i=0; i<4; i++){
//Create your element as string.
var strElem = "<button type='button' id='btn_" + i + "'>Click</button>";
//Add that string to temp container (his html will be replaced, not added).
tempContainer.innerHTML = strElem.trim();//Trim function used to prevent empty textnodes before element.
//Get element from temp container.
var button = tempContainer.children[0];
//Empty tempContainer for better security (But about which security I'm talking in JavaScript in string element generation :) )
tempContainer.innerHTML = '';
//Add your button to container.
container.appendChild(button);
//Add event listener to button:
//document.getElementById("btn_" + i).onclick = clicky;
//Better way to add event listener:
button.addEventListener('click', clicky);
}
DEMO: http://jsfiddle.net/3cD4G/1/
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();
}
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