Removing an element from a node without removing the associated event? - javascript

I have been working on an html/css/javascript 'etch-a-sketch' style project.
In a nutshell I have a grid of div elements with a mouseenter event:
const fillInGrid = document.querySelectorAll(".gridSquares");
fillInGrid.forEach((div) => {
div.addEventListener('mouseenter', (e) => {
div.style.backgroundColor = 'black';
});
});
In the project I have a reset button that removes the child elements from the grid and replaces them with new divs, two prompts where a number of rows and columns specified by the user which then generates a new grid:
const resetButton = document.querySelector("#reset");
resetButton.addEventListener('click', (e) => {
const resetEvent = document.getElementById('container');
while (resetEvent.lastElementChild) {
resetEvent.removeChild(resetEvent.lastElementChild);
};
newGrid();
}
);
However, after clicking reset and choosing dimensions for a new grid, the grid is generated but the grid loses responsiveness to the mouseenter event because I'm assuming the event is being removed along with the divs, is there a way to re-add the event or a method alternative that can remove the divs without the associated event?
A link to a codepen demonstrating the issue: https://codepen.io/MaBuCode/pen/eYpjwOV

Instead of adding multiple event listeners on the child elements, you can add a single event listener at the containing element. This way, your code will become more performant and you will also get to catch any event that gets triggered on the dynamically (newly created) elements.
You will need to replace the mouseenter event with the mouseover event, that supports bubbling.
Here's the code to add and replace the mouseenter event:
// // One event listener to rule them all:
document.getElementById("container").addEventListener('mouseover', (e)=>{
if ( e.target.classList.contains('gridSquares')){
e.target.style.backgroundColor = 'black';
}
});
You can now get rid of the individual div event listener:
fillInGrid.forEach((div) => {
div.addEventListener('mouseenter', (e) => {
div.style.backgroundColor = 'black';
});
});
Codepen Demo
Tip: I have also refactored the ’gridCreator' function to reduce the number of appendChild operations, and instead replaced it with a simple string concatenation to make the code more performant:
function gridCreator(gridSize) {
let content = "";
for (let i = 0; i < gridSize; i++) {
content += "<div class='gridSquares'></div>";
}
document.getElementById("container").innerHTML = content;
}
By using the approach above, you can also omit the code that removes the .container child elements in the resetButton code.

Related

Some of Icon click listeners work and some not in a strange way in JavaScript

I am working on a to-do list application. I create a li item and put 2 icons and a p tag in it. One of the icons is edit and it works quite well, I replace an input with the p tag and it is fine but the problem is that my check icons on the left side work half way. If I add the li items one by one, the check icons work very well but when I add 5 or 10 items and then try to check the icons, a few of them works and the others do not. I have tried replacing i tags with span tags and no result. It is like every second li tag blocks the former one. I need help, I would appreciate any.
I'll add below the only the icons which don't work.
const DONE = document.getElementsByClassName('far fa-circle');
const LINE = document.getElementsByClassName('list-points');
const EDIT = document.getElementsByClassName('far fa-edit');
const CONTAINER = document.getElementById("actual-container");
const BUTTON = document.getElementById("list-adder");
BUTTON.addEventListener('click', nameList);
function nameList() {
const item1 = document.createElement("i");
item1.className = "far fa-circle";
const paraph1 = document.createElement("p");
paraph1.className = "list-points";
paraph1.innerText = "Fresh again!";
const item2 = document.createElement("i");
item2.className = "far fa-edit";
const myList = document.createElement("li");
myList.appendChild(item1);
myList.appendChild(paraph1);
myList.appendChild(item2);
CONTAINER.appendChild(myList);
for (let i = 0; i < DONE.length; i++) {
DONE[i].addEventListener('click', function() {
DONE[i].classList.toggle('fa-times-circle');
})
}
}
<head>
<title>Debug</title>
<script src="https://kit.fontawesome.com/ae444f90db.js" crossorigin="anonymous"></script>
</head>
<body>
<div>
<ul id="actual-container"></ul>
</div>
<button id="list-adder">ME</button>
</body>
The error is within the assignment of your click-handlers.
You do
for (let i = 0; i < DONE.length; i++) {
DONE[i].addEventListener('click', function() {
DONE[i].classList.toggle('fa-times-circle');
});
}
This will add a toggling event handler to all elements that you keep in DONE and has this behaviour.
Click button: create elements A, assign click handler to A
(A has a single handler) works
Click button: create elements B, assign click handler to A and B
(A has two handlers) does not work
(B has a single handler) works
Click button: create elements C, assign click handler to A and B and C
(A has three handlers) works
(B has two handlers) does not work
(C has a single handler) works
Because you are using toggle in your click handler, the handlers are "canceling" each other because toggling something twice will leave you in the initial state.
I guess you are not aware of the fact that getElementByClassName returns a live list, so that the elements of your variable DONE are changed when you add new elements to the DOM. I was not aware of this either, so thank you for your question :)
See here for a better explanation: https://developer.mozilla.org/en-US/docs/Web/API/Document/getElementsByClassName
In your code it should be enough to add the handler just to the one element you create (item1 is the icon element in your code):
item1.addEventListener('click', function() {
item1.classList.toggle('fa-times-circle');
});

How come my onMouseOver effect is not working?

As the title states, my onMouseOver event listener is not working. I'm able to create a 'hover' pseudo class in CSS and have it work that way but I want to create an event listener for this to work.
Code:
let container = document.getElementById('container');
//function to create 16x16 grid
function gridSquares () {
for (x = 0; x < 256; x++) {
let squares = document.createElement('div');
squares.className = 'grid-square';
container.appendChild(squares);
//mouse-over event listener for each grid
squares.addEventListener('onmouseover', function () {
const gridSquare = document.getElementsByClassName('grid-square');
gridSquare.setAttribute('style', 'background: yellow');
});
}
}
gridSquares();
I've created a div in my HTML with an ID of 'container' as well.
Any ideas?
Pretty sure is not
squares.addEventListener('onmouseover', function() {...
but is:
squares.addEventListener('mouseover', function() {...
You should remove 'on' inside the event listener

DOM: Delete newly created 'article' element with newly created delete button within onclick event?

I have one section element that contains one article element. Also, I have one input button with 'onclick' event. Whenever this event fired, a new article element appended to the section element with unique id.
The newArticle element contains a label, text box and a delete button. All these three elements get created within the on-click event.
document.getElementById("addRow").onclick = function () {
var newCustomerlbl = document.createElement("label");
newCustomerlbl.innerHTML = "Cutomer Name: ";
var newCustomertxt = document.createElement("input");
newCustomertxt.setAttribute("type", "text");
var delBtn = document.createElement("input");
delBtn.setAttribute("type", "button");
delBtn.setAttribute("value", "Delete");
delBtn.setAttribute("id", "btnDelete");
var newArticle = document.createElement("article");
newArticle.appendChild(newCustomerlbl);
newArticle.appendChild(newCustomertxt);
newArticle.appendChild(delBtn);
var customerSection = document.getElementById("customerRecords");
var customerArticles = customerSection.getElementsByTagName("article");
for (var i = 0; i < customerArticles.length; i++) {
var lastDigit = i + 1;
var newArticleValue = "article" + lastDigit;
newArticle.setAttribute("id", newArticleValue);
}
customerSection.appendChild(newArticle);
}
Now what I want is whenever user click upon the newly created appended delete button, only that particular article get deleted without effecting the rest of articles.
Here is the my jsFiddle code.
If you don't want to use jQuery you can add event listeners to your buttons:
delBtn.addEventListener('click', function () {
this.parentElement.remove();
}, false);
https://jsfiddle.net/3nq1v5e1/
You need to bind an event listener on the newly created delete button. Your example code about using $(this) suggest that you are using JQuery, but then again in the rest of the code you are not using any JQuery?
If you are using JQuery, things get real simple, just add something like
$(document).on('click','.btnDelete', function(){
$(this).closest('article').remove();
});
(and remember to give the deletebutton a CLASS rather than ID, as there will be multiple delete buttons).
If you are NOT using JQuery, you need to add the event listener EVERY TIME a new delete button is created
newArticle.appendChild(delBtn);
delBtn.onclick = function(.....
etc.

Attaching Click Event to DIV Using jQuery Library

I am aware I can use the click function to attach an event to the DIV element but for some reason it is not working for me. Here is how I am creating the DIV element.
function createColorSwatchDiv(color) {
var colorSwatchDiv = $("<div>");
var image = $("<img>");
image.attr("src",color.imageURL);
var label = $("<label>");
label.text(color.title);
colorSwatchDiv.append(image);
return colorSwatchDiv;
}
Then I try to attach the click event like the following:
// run a loop and build the grid layout
for(index = 0; index < colors.length; index++) {
var colorSwatchDiv = createColorSwatchDiv(colors[index]);
// attach the event
colorSwatchDiv.click(function(){
alert('hello world');
});
colorsSection.append(colorSwatchDiv);
}
// add to the dom
$("#color .imageChartOption").after(colorsSection);
But it does not work and no click event is been attached.
following is the code
var $newdiv1 = $("<div id='object1' onClick=Test()>Hello</div>");
$("body").append($newdiv1);
function Test()
{
alert("Clicked");
}
OR
$newdiv1.on('click',function(){alert("hello");});
since you have created the div in a jQuery wrapper you don't need to wrap it again here $(colorSwatchDiv).click(.... Also, are you sure that the colorSwatchDiv variable is referencing the dom element and not the in memory element? Can you apply a class or anything to the elm in the dom?

Attaching eventListener to dynamic elements in Javascript

I'm making a todo list and I have li and button tags added dynamically when adding a new list item. The button is an x which is supposed to remove the list item. I have tried several things but can't figure out how to make an eventListener for each individual x button and remove the corresponding list item when it is clicked.
The renderTodos function is where all of the dynamically added content is created. I have a data-index set to each button in which I was trying to use to access each button to attach an eventListener on each dynamic button, but I wasn't sure how to implement that. From what I have read there should be a way to do this using the currentTarget or target of the event but I don't understand how that works.
var input = document.querySelector('input[name=todoItem]'),
btnAdd = document.querySelector('button[name=add]'),
btnClear = document.querySelector('button[name=clear]'),
list = document.querySelector('.todo'),
storeList = [];
function renderTodos(){
var el = document.createElement('li'),
x = document.createElement('button');
listLength = storeList.length;
//Set text for remove button
x.innerHTML = 'x';
for(var i = 0; i < listLength; i++){
el.innerHTML = storeList[i];
list.appendChild(el);
x.setAttribute('data-index', i);
el.appendChild(x);
}
// check for correct data-index property on x button
}
function addTodos(){
storeList.push(input.value);
// Check that input is getting pushed to list array
console.log(storeList);
renderTodos();
}
function clearList(){
// make list empty
list.innerHTML = '';
storeList.splice(0, storeList.length);
//render empty list
renderTodos();
//Check that list array is empty
console.log(storeList);
}
btnAdd.addEventListener('click', addTodos);
btnClear.addEventListener('click', clearList);
Everything else on the list works so far I just can't figure out how to implement this eventListener.
One simple example can be
//a click hadler is added to #mylist which is already present in the dom
document.querySelector('#mylist').addEventListener('click', function(e) {
//assuming that the the `x` is in a span and it is the only span in the `li` we check for that, we can improve this check more to make sure we have actually clicked on the delete button
if (e.target.tagName == 'SPAN') {
//if so then since we know the structure we can delete the parent node of the target which is the span element
e.target.parentNode.parentNode.removeChild(e.target.parentNode);
}
}, false);
//kindly forgive the use of jQuery here
for (var i = 0; i < 10; i++) {
$('<li />', {
text: i
}).append('<span class="x">X</span>').appendTo('#mylist');
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<ul id="mylist"></ul>
This is a very basic implementation of event delegation, where the actual event is bound to an ancestor element but then we use the actual event target to determine whether to act on it. We can improve the if condition to test for an class for any other attribute!!!
You can add a listener to each button using something like:
x.innerHTML = '';
x.onclick = function(){
var node = this.parentNode;
node.parentNode.removeChild(node);
};
Or you can keep the renderTodos code as it is and delegate the remove to the parent UL:
// Add the listener
list.addEventListener('click', removeItem);
// The listener function
function removeItem(event) {
var node = event.target;
// Check that the click came from an X button
// better to check against a class name though
if (node.tagName &&
node.tagName.toLowerCase() == 'button' &&
node.innerHTML == 'x') {
node = node.parentNode;
node.parentNode.removeChild(node);
}
}
basically what you want to do is add an event on the parent container and wait for the event to bubble up and identify if the event originating is from your x mark and if it is then trigger the callback function.. This is the concept I think most of the libraries use..
Or use a library like jQuery, why solve a problem that has already been solved by others.

Categories

Resources