Elements lose javascript functionality after cloning - javascript

The following is the code. What I have done is made an item (the first one) and when i hover on its button, its background color changes to red and so on.
What I did next is cloned the element and appended it to a new div. The html (elements having same classes) is the same but the mouseover event doesn't work anymore.
My question is that why it did not work and how can I fix it? Also I tried to do the same by copying inner HTML to the new element but it is the same everytime.
const colorDiv = document.querySelector(".color-div");
const button = document.querySelector("button");
const mainContainer = document.querySelector(".main-container");
button.addEventListener("mouseover", function() {
colorDiv.style.backgroundColor = "red";
});
button.addEventListener("mouseout", function() {
colorDiv.style.backgroundColor = "seagreen";
});
const newItem = mainContainer.cloneNode(true);
document.querySelector(".new-container").appendChild(newItem);
.color-div {
height: 300px;
width: 300px;
background-color: seagreen;
margin: 10px;
padding: 10px;
transition: all .3s;
}
<!-- I will copy the div with main container class -->
<div class="main-container">
<div class="color-div">Hello</div>
<button>Change</button>
</div>
<!-- and append copied item to the following item -->
<div class="new-container"></div>

Events are not cloned, here's a quick fix:
const colorDiv = document.querySelector(".color-div");
const button = document.querySelector("button");
const mainContainer = document.querySelector(".main-container");
// If someone clicks on anywhere on the website THAT IS A BUTTON, then change the color.
document.addEventListener("mouseover", function(e) {
if (e.target.matches("button")) {
colorDiv.style.backgroundColor = "red";
}
});
document.addEventListener("mouseout", function(e) {
if (e.target.matches("button")) {
colorDiv.style.backgroundColor = "seagreen";
}
});
const newItem = mainContainer.cloneNode(true);
document.querySelector(".new-container").appendChild(newItem);
This should work for every clone.

Related

How can I remove elements from prepend() by click close button?

I have an interface where images is being showed by .prepend() method, I am trying to remove images one by one by click on close button and also remove all the images by one click on a button, but I have no idea how to do ?
Here is image list interface below, 3 images show as of now How can I attach close button and remove images individual and also on 1 click.
HTML:
<div id="imageList">
</div>
JS:
//Images are stored in this array as base64
var imagesBase64 = [];
// add images in the array ImagesBase64
function addToDownload() {
imagesBase64.push(dataURL);
appendImageToList(dataURL);
}
const imageList = document.getElementById("imageList");
function appendImageToList(image) {
var img = document.createElement("img");
img.src = image;
imageList.prepend(img);
}
I crafted a demo that does more or less what you asked (but using jQuery).
The page gets initialized with a list of pictures hardcoded in an array. Each picture gets appended in #imageList inside its own container and a little red box is added to trap the click event when you mean to remove the picture from the list. On top of that there's a button to remove all of them at once:
//a list of img urls needed to feed the list of pictures with some
const images = ['https://www.w3schools.com/css/img_5terre.jpg', 'https://www.w3schools.com/css/img_forest.jpg', 'https://www.w3schools.com/css/img_lights.jpg', 'https://www.w3schools.com/css/img_mountains.jpg'];
//inits the imageList with pictures coming from images constant
//..so when the document is ready,
$(document).ready(() => {
//for each url picture in the images constant
images.forEach((o, i) => {
//append a picture to imageList having that url
appendImageToList(o);
});
});
//appends an image to the list (where image is a picture url)
function appendImageToList(image) {
var img = document.createElement("img");
img.src = image;
//the img will be enclosed in a container
let container = $('<div>', {
class: 'imgContainer'
});
container.append(img);
//creates the close handle for this new picture and adds an event handler on its click event
let closeHandle = $('<div>', {
class: 'closeHandle'
});
//adds content x to the close handle
closeHandle.append('<i class="fa-solid fa-circle-xmark"></i>');
closeHandle.click(() => {
//when the button is clicked, remove this image from the list
removeOneImageFromList($(event.target).closest('.imgContainer'));
});
container.append(closeHandle);
//adds the container inside the imageList
$('#imageList').prepend(container);
}
//removes all images from the list
function removeAllImages() {
$('#imageList .imgContainer').remove();
}
//removes a specific image from the list
function removeOneImageFromList(imgParentElement) {
$(imgParentElement).remove();
}
/* rule to style every single img container */
#imageList .imgContainer {
position: relative;
width: fit-content;
}
/* rule to style the close handle */
#imageList .closeHandle {
position: absolute;
top: 15px;
right: 15px;
text-align: center;
cursor: pointer;
}
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.1.1/css/all.min.css" rel="stylesheet" />
<script src="https://code.jquery.com/jquery-3.6.0.slim.js"></script>
<button type="button" onclick="removeAllImages();">Remove all images</button>
<br><br>
<div id="imageList">
</div>

To-do list application: Why does my code prevent me from deleting the most recently added list element?

I'm trying to create a to-do list application. I'm using JS to dynamically create list elements on the web page when a user clicks on the submit button along with their user input.
Here's what I have so far:
const inputTXT = document.querySelector('.inputText'); //input element
const submitBUTT = document.querySelector('.submitButton'); //button element
const listITEMS = document.querySelector('.items'); //list element
function createListElement(inputString){
const newListItem = document.createElement("li");
const newEditButton = document.createElement("button");
const newDeleteButton = document.createElement("button");
const listText = document.createTextNode(inputString);
newListItem.appendChild(listText);
const editText = document.createTextNode("Edit");
newEditButton.appendChild(editText);
const deleteText = document.createTextNode("Delete");
newDeleteButton.appendChild(deleteText);
newListItem.appendChild(newEditButton);
newListItem.appendChild(newDeleteButton);
//assign class to each list element for line below:
newDeleteButton.className = "deleteCLASS";
newEditButton.className = "editCLASS";
//delete function:
var deleteButtonArray = document.querySelectorAll(".deleteCLASS");
for(var i=0; i < deleteButtonArray.length ; ++i){
deleteButtonArray[i].onclick = function(){
this.parentNode.remove();
}
}
return newListItem;
}
function addTask(){
listITEMS.classList.remove('hidden');
const ITEM = createListElement(inputTXT.value);
document.getElementsByClassName("items")[0].appendChild(ITEM);
inputTXT.value = ''; //Resets user input string
}
submitBUTT.addEventListener("click", addTask);
*{
margin: 0;
padding: 0;
}
.container{
width: 800px;
margin: 0 auto;
font-family: sans-serif;
}
.main-header{
background: #e7e7e7;
text-align: center;
margin: 15px;
padding: 20px;
}
.inputText{
margin-top: 20px;
}
h1{
font-size: 35px;
}
ul{
list-style: square;
margin-left: 275px;
}
.hidden{
display: none;
}
.itemLIST{
margin-bottom: 5px;
}
.editBUTT{
margin-left: 20px;
}
.deleteBUTT{
margin-left: 4px;
}
<!DOCTYPE html>
<head>
<title> "To-do List" </title>
<link href="style.css" rel="stylesheet"/>
<meta charset = "UTF-8"/>
</head>
<body>
<div class = "container">
<div class = "main-header">
<h1>My To-do List</h1>
<input type="text" placeholder = "Enter Item" class= "inputText" required>
<button class = "submitButton">Submit</button>
</div>
<ul class ="items hidden">
</ul>
</div>
<script src="script.js"></script>
</body>
</html>
This works for the most parts, but I cannot delete the last list element for some reason.
The only debugging I've done to figure out what is happening is print out the "deleteButtonArray" variable, which uses the keyword "querySelectorAll". From this, I found out that when the user hits the submit button, an empty NodeList printed. When the user hits the submit button a second time, only then do we get a NodeList with 1 element. When the user hits the submit button a third time, we get a NodeList with 2 elements.
It looked like the problem had something to do with querySelectorAll not properly updating in time when the user hits the submit button. I replaced it with getElementsByClassName, and still the same issue.
Now, I think the problem has something to do with the way I'm trying to implement the delete function within the createListElement function.
The way I think my code works:
Every time a user hits the submit button, a list element is created along with an array (actually a NodeList) that contains all the list elements present so far. This means that if I delete list elements, the array will update with the correct number of list elements.
And, it does update correctly, for the most parts. I just don't know why the array is empty when we first create an element. Shouldn't querySelectorAll or getElementsByClassName return a non-empty NodeList when the user first hits the submit button?
All right, so here's a solution I've tried and tested it, and it seems to be working.
I removed the delete item portion out of the createListElement function, made it its own function, and added an event listener to each delete button that's created, so it will run the now separate delete function when clicked.
No changes were made to the HTML or the CSS.
const inputTXT = document.querySelector('.inputText'); //input element
const submitBUTT = document.querySelector('.submitButton'); //button element
const listITEMS = document.querySelector('.items'); //list element
function createListElement(inputString){
const newListItem = document.createElement("li");
const newEditButton = document.createElement("button");
const newDeleteButton = document.createElement("button");
const listText = document.createTextNode(inputString);
newListItem.appendChild(listText);
const editText = document.createTextNode("Edit");
newEditButton.appendChild(editText);
const deleteText = document.createTextNode("Delete");
newDeleteButton.appendChild(deleteText);
newListItem.appendChild(newEditButton);
newListItem.appendChild(newDeleteButton);
//Here is the new addEventListener
newDeleteButton.addEventListener("click", deleteStuff);
//assign class to each list element for line below:
newDeleteButton.className = "deleteCLASS";
newEditButton.className = "editCLASS";
return newListItem;
}
//Here is the new delete function. The onclick function that was there before has been removed because, in this case, it's not needed.
function deleteStuff() {
//delete function:
var deleteButtonArray = document.querySelectorAll(".deleteCLASS");
for(var i=0; i < deleteButtonArray.length ; ++i){
this.parentNode.remove();
}
}
function addTask(){
listITEMS.classList.remove('hidden');
const ITEM = createListElement(inputTXT.value);
document.getElementsByClassName("items")[0].appendChild(ITEM);
inputTXT.value = ''; //Resets user input string
}
submitBUTT.addEventListener("click", addTask);

After first append javascript is not adding a cloned item for the second time

In my application I am adding cloned elements:
document.getElementById("add").addEventListener("click", function(){
let theblock = document.getElementById("theblock").cloneNode(true);
let newer = theblock;
newer.removeAttribute("id");
document.getElementById("restblocks").append(newer);
Bit if I do cloning outside scope it adds the element to html only once:
let theblock = document.getElementById("theblock").cloneNode(true);
document.getElementById("add").addEventListener("click", function(){
let newer = theblock;
newer.removeAttribute("id");
document.getElementById("restblocks").append(newer);
What could be the reason?
Because when you clone outside you only ever make a single clone, and a Node can only exist in one place in a document. If you want to avoid the query inside the click handler you can query outside, but clone inside.
let theblock = document.getElementById("theblock");
document.getElementById("add").addEventListener("click", function(){
let newer = theblock.cloneNode(true);
...
});
const theblock = document.getElementById('theblock');
const restblocks = document.getElementById('restblocks');
document.getElementById('add').addEventListener('click', function () {
let newer = theblock.cloneNode(true);
newer.removeAttribute('id');
newer.classList.remove('hidden');
restblocks.append(newer);
});
.block { width: 40px; height: 40px; margin: 4px; background-color: pink; }
.hidden { display: none; }
<button type="button" id="add">Add a block</button>
<div id="restblocks"></div>
<div id="theblock" class="block hidden"></div>

DOM Event.StopPropagation to child has no effect

I'm doing a tutorial about Events and I'm stuck with Event.StopPropagation() method. It seems that with my example that the bubbling effect to the children are being affected.
Definition of StopPropagation:
The stopPropagation() method prevents propagation of the same event from being called.
Propagation means bubbling up to parent elements or capturing down to child elements.
At first I thought it was a browser problem but it was not the case. I can't find a solution about this.
Code:
// Event Bubbling and Propagation
// element.addEventListener( type, func, useCapture);
let m = document.getElementById('m');
let d = document.getElementById('d');
let p = document.getElementById('p');
let s = document.getElementById('s');
let highlight = (ev)=>{
//add CSS class "gold" to the clicked element
ev.stopPropagation();
let target = ev.currentTarget;
target.className = 'gold';
reset(target);
}
let reset = (_element)=>{
setTimeout(()=>{
_element.className = '';
}, 2000);
}
d.addEventListener('click', (ev)=>{
ev.stopImmediatePropagation();
log('Hi I\'m a DIV');
});
[m,d,p,s].forEach((element)=>{
element.addEventListener('click', highlight);
})
#m,#d,#p,#s{
border: 2px solid black;
padding: 15px;
margin: 10px;
}
.gold{
background-color: gold;
}
<main id="m"> m
<div id="d"> d
<p id="p"> p
<span id="s"> s</span>
</p>
</div>
</main>
This is not a problem with events, you are overthinking this.
You example is simply applying background color to top element, and as children do not have it defined, its applying the top one below it.
And if you remove stopImmediatePropagation() color will be applied as definition is: execute the first event handler, and stop the rest of the event handlers from being executed, and the first one was simply log().
In example below, if you apply background color to child elements, you will see they will stay the same. The color it self is applied only on clicked one.
That means the JS event itself did not bubble up to parent elements or capture down. And class was added only on clicked one. Check it with dev tools or add DOM change event listener on every element.
You confused CSS styling with JS event bubbling.
Example:
// Event Bubbling and Propagation
// element.addEventListener( type, func, useCapture);
let m = document.getElementById('m');
let d = document.getElementById('d');
let p = document.getElementById('p');
let s = document.getElementById('s');
let log = console.log;
let highlight = (ev)=>{
//add CSS class "gold" to the clicked element
ev.stopPropagation();
let target = ev.currentTarget;
target.className = 'gold';
reset(target);
}
let reset = (_element)=>{
setTimeout(()=>{
_element.className = '';
}, 2000);
}
d.addEventListener('click', (ev)=>{
//ev.stopImmediatePropagation();
log('Hi I\'m a DIV');
});
[m,d,p,s].forEach((element)=>{
element.addEventListener('click', highlight);
})
#m,#d,#p,#s{
border: 2px solid black;
padding: 15px;
margin: 10px;
}
#d {
background-color: blue;
}
#s {
background-color: red;
}
.gold{
background-color: gold !important;
}
<main id="m"> m
<div id="d"> d
<p id="p"> p
<span id="s"> s</span>
</p>
</div>
</main>
EDIT:
In your example child nodes do not have background property (rgba(0, 0, 0, 0)), that means you need to set it on click, so that background color of clicked element wont apply on its children.
In code below, you can read the CSS background value of parent element of clicked one, and read its background property. That you take that and apply on all children of clicked one.
This will work in your example.
You can also make sure to apply white to elements if parent was transparent.
Here is a fiddle to play with:
but make sure you will un-comment m (parent) background color to see side effects. You would need to adjust this to suit your production needs
const style = getComputedStyle(target.parentNode);
const backgroundColor = style.backgroundColor;
console.clear();
console.log(backgroundColor);
[...target.children].forEach(el => {
if (backgroundColor==="rgba(0, 0, 0, 0)") {
el.style.backgroundColor = "white";
}else{
el.style.backgroundColor = backgroundColor;}
})
// Event Bubbling and Propagation
// element.addEventListener( type, func, useCapture);
let m = document.getElementById('m');
let d = document.getElementById('d');
let p = document.getElementById('p');
let s = document.getElementById('s');
let log = console.log;
let highlight = (ev)=>{
//add CSS class "gold" to the clicked element
ev.stopPropagation();
let target = ev.currentTarget;
target.className = 'gold';
const style = getComputedStyle(target.parentNode);
const backgroundColor = style.backgroundColor;
console.clear();
console.log(backgroundColor);
[...target.children].forEach(el => {
if (backgroundColor==="rgba(0, 0, 0, 0)") {
el.style.backgroundColor = "white";
}else{
el.style.backgroundColor = backgroundColor;}
})
reset(target);
}
let reset = (_element)=>{
setTimeout(()=>{
_element.className = '';
}, 2000);
}
[m,d,p,s].forEach((element)=>{
element.addEventListener('click', highlight);
})
#m,#d,#p,#s{
border: 2px solid black;
padding: 15px;
margin: 10px;
}
#m {
background-color: blue;
}
.gold{
background-color: gold !important;
}
<main id="m"> m
<div id="d"> d
<p id="p"> p
<span id="s"> s</span>
</p>
</div>
</main>

How to select a new added element and edit it?

I have an <a> element:
<a id='addNewElementk' onclick='//Some Js Code' class='continueButton'>Click To Add</a>
When this anchor is clicked , A new element added:
New Added Element
And the first anchor which was clicked , Is removed.
I want to select that new element.
I tried:
window.onload = function(){
var newElem = document.getElementsByClassName('continueButton')[1];
alert(newElem.innerHTML);
}
I'm using ('continueButton')[1] , As there is another input with the same class before that anchor.
But for sure I get Click To Add from the first one , As that's was found when the page is loaded.
So how can I select that new element?
You're attempting to select the element before it exists in the DOM.
You instead need to run that code within the click event handler of the first <a>, like this:
window.onload = function() {
document.querySelector('#addNewElementk').addEventListener('click', function(e) {
e.preventDefault();
var a = document.createElement('a');
a.textContent = 'New Added Element';
a.href = '#';
a.classList.add('continueButton');
a.addEventListener('click', function(e) {
console.log(a.innerHTML);
});
this.parentNode.insertBefore(a, this);
this.remove();
});
}
<a id='addNewElementk' href="#" class='continueButton'>Click To Add</a>
Note the use of addEventListener() over the outdated on* event attributes which should be avoided.
You are attempting to select on an element that doesn't exist in the DOM. Dynamically added elements can be accessed in a couple of ways, above someone has an answer that adds an event listener to the created element which is a solid solution. The other most common way would be to use event delegation (if you are familiar with jQuery that would be $(parentElement).on('action', 'elementWeWantToWatch', function)) in Vanilla js the pattern is effectively the same, find or make a container element for your dynamic html, then add a listener to that container. Inside the listener you will want to ensure the target matches whatever your dynamic selection would be and execute when you find a match.
In this Example
The event listener is initiated on page load to watch the container element. The listener watches for clicks on elements with the continueButton class and when it finds one it removes the clicked element and adds a new element (the counter is to demonstrate that new content is being displayed :D)
(function() {
let i = 1;
const makeButton = () => {
const a = document.createElement('a');
a.classList.add('continueButton');
a.href = '#';
a.textContent = `Button ${i}`
i++;
return a;
}
const init = () => {
const container = document.querySelector('.test');
container.addEventListener('click', e => {
if (e.target.classList.contains('continueButton')) {
let button = makeButton();
container.appendChild(button);
container.removeChild(e.target);
return;
}
});
};
if (document.readyState == 'loading') {
window.addEventListener('DOMContentLoaded', init);
} else {
init();
}
})()
.test {
width: 100%;
display: block;
text-align: center;
}
.continueButton {
display: block;
color: white;
background-color: green;
border-radius 2px;
padding: 15px 30px;
line-height: 2;
margin: 50px auto;
width: 200px;
text-decoration: none
}
<section class="test">
<a id='addNewElementk' class='continueButton'>Click To Add</a>
</section>

Categories

Resources