How to handle events in dynamically created elements in js - javascript

I want to add the click event to a created Element dynamically that when user click on button create some elements (elements that show in code below) and when user click on element named remov must run function named deltion BUT that doesnt work.How can I implemented that ?
I use Vue js
methods: {
showinfo: function() {
db.collection("Studnets")
.get()
.then(function(querySnapshot) {
querySnapshot.forEach(function(doc) {
const student = document.createElement("tr");
const email = document.createElement("td");
email.innerText = doc.data().email;
const name = document.createElement("td");
name.innerText = doc.data().name;
const phone = document.createElement("td");
phone.innerText = doc.data().phone;
const stage = document.createElement("td");
stage.innerText = doc.data().stage;
const remov = document.createElement("button");
const pic = document.createElement("img");
pic.setAttribute("src","/dist/delete.svg?afdf9975229cdc15458e851b478bb6cb");
remov.classList.add("del");
//the problem
remov.addEventListener("click", this.deltion());
student.appendChild(email);
student.appendChild(name);
student.appendChild(phone);
student.appendChild(stage);
student.appendChild(remov);
remov.appendChild(pic);
document.getElementById("studentList").appendChild(student);
},
deltion: function(e) {
const rawStudent = e.target;
const raw = rawStudent.parentElement;
console.log(raw);
raw.remove();
}

There are three issues with your code.
First Issue: invoking a function on the event listener
you are calling the deltion (maybe you mean deletion :P) function when you register the event listener.
remov.addEventListener("click", this.deltion());
the correct form is
remov.addEventListener("click", this.deltion);
Because you want to pass the function body to the event listener, not the function result.
(you can wrap the function in an arrow function if you want to call it, but at the end is the same).
Second Issue: this is not this
If you fix the first one, you'll find another one (the life of programmers). this is a special keyword in js, the context of this will change depending on the caller.
showinfo is called the this keyword refers to the component instance.
db.collection("Studnets").get().then(function(querySnapshot) {}) promise is called and on resolve it will call the function that has querySnapshot parameter. **this**
keyword context changes.
you iterate the collection with a foreach querySnapshot.forEach(function(doc) {}) **this** keyword context is changed again.
A solution will be to use arrow functions so this don't get bonded to the parent function.
db.collection("Studnets").get().then(querySnapshot => {
querySnapshot.data.forEach(doc => {
// code
})
})
If for some reason you can't use arrow functions, you can add a variable that "shadows" the context of the this keyword on the showinfo function.
showinfo() {
const vm = this;
api.getStudentsCollection().then(function(querySnapshot) {
querySnapshot.data.forEach(function(doc) {
// setup code
remov.addEventListener("click", vm.deltion);
// setup code
});
});
}
Third Issue: clicking the arrow image will delete the button but not the tr
Use currentTarget instead of the target, the target is the element the user click, it can be any element inside the button like the image, currentTarget is the element that the event listener is attached aka the button.
{
deltion: function(e) {
const rawStudent = e.currentTarget;
const raw = rawStudent.parentElement;
console.log(raw);
raw.remove();
}
}
Unsolicited Advice
Vue excels with its simplicity and declarative code. You can solve the problem like your original code, but there is a simpler way to let the component manage its state.
I replicated your code with the original version fixed and the simplest vue way (with a simple empty and loading state toggle). Hope you find it useful and learn from this. :) https://codesandbox.io/s/student-list-ammar-yasir-b5sxo?file=/src/App.vue
Learning references
https://javascript.info/
https://dev.to/cilvako/this-keyword-in-javascript-an-explanation-1con

Related

Why wouldn't the event listener get removed from the tds in this html row? [duplicate]

I have the following code to add eventListener
area.addEventListener('click',function(event) {
app.addSpot(event.clientX,event.clientY);
app.addFlag = 1;
},true);
It is working correctly as expected..Later in another function i tried to remove the event listener using the following code
area.removeEventListener('click',function(event) {
app.addSpot(event.clientX,event.clientY);
app.addFlag = 1;
},true);
But the even listener is not removed..Why is it happening?Is there any problem with my removeEventListener()?
Note:Here area is something like document.getElementById('myId')
This is because that two anonymous functions are completely different functions. Your removeEventListener's argument is not a reference to the function object that was previously attached.
function foo(event) {
app.addSpot(event.clientX,event.clientY);
app.addFlag = 1;
}
area.addEventListener('click',foo,true);
area.removeEventListener('click',foo,true);
I find that for the windows object, the last param "true" is required.
The remove doesn't work if there is no capture flag.
In a React function component, make sure to define the callback with the useCallback(() => {}) hook. If you fail to do this, the callback will be a different one on every re-render and the removeEventListener method will not work.
const scrollCallback = useCallback(() => { // do sth. });
window.addEventListener("scroll", scrollCallback, true);
window.removeEventListener("scroll", scrollCallback, true);
It looks like no one's covered the part of the DOM specification (that both browsers and Node.js implement) that now gives you a mechanism to remove your event listener without using removeEventListener.
If we look at https://dom.spec.whatwg.org/#concept-event-listener we see that there are a number of properties that can be passed as options when setting up an event listener:
{
type (a string)
callback (an EventListener object, null by default)
capture (a boolean, false by default)
passive (a boolean, false by default)
once (a boolean, false by default)
signal (an AbortSignal object, null by default)
removed (a boolean for bookkeeping purposes, false by default)
}
Now, there's a lot of useful properties in that list, but for the purposes of removing an event listener it's the signal property that we want to make use of (which was added to the DOM level 3 in late 2020), because it lets us remove an event listener by using an AbortController instead of having to bother with keeping a reference to the exact handler function and listener options "because otherwise removeEventListener won't even work properly":
const areaListener = new AbortController();
area.addEventListener(
`click`,
({clientX: x, clientY: y}) => {
app.addSpot(x, y);
app.addFlag = 1;
},
{ signal: areaListener.signal }
);
And now, when it's time to remove that event listener, we simply run:
areaListener.abort()
And done: the JS engine will abort and clean up our event listener. No keeping a reference to the handling function, no making sure we call removeEventListener with the exact same funcation and properties as we called addEventListener: we just cancel the listener with a single, argumentless, abort call.
And of course, also note that if we want to do this "because we only want the handler to fire once", then we don't even need to do this, we can just create an event listener with { once: true } and JS will take care of the rest. No removal code required.
area.addEventListener(
`click`,
() => app.bootstrapSomething(),
{ once: true }
);
You are creating two different functions in both calls. So the second function does not relate in any way to the first one and the engine is able to remove the function. Use a common identifier for the function instead.
var handler = function(event) {
app.addSpot(event.clientX,event.clientY);
app.addFlag = 1;
};
area.addEventListener('click', handler,true);
later you can then remove the handler by calling
area.removeEventListener('click', handler,true);
To remove it, store the function in a variable or simply use a named function and pass that function to the removeEventListener call:
function areaClicked(event) {
app.addSpot(event.clientX, event.clientY);
app.addFlag = 1;
}
area.addEventListener('click', areaClicked, true);
// ...
area.removeEventListener('click', areaClicked, true);
If you want to pass local variables to the function called by the event listener, you can define the function inside the function (to get the local variables) and pass the name of the function in the function itself. For example, let's start inside the function that adds the event listener with app as a local variable. You would write a function inside this function such as,
function yourFunction () {
var app;
function waitListen () {
waitExecute(app, waitListen);
}
area.addEventListener('click', waitListen, true);
}
Then you have what you need to remove it when waitExecute is called.
function waitExecute (app, waitListen) {
... // other code
area.removeEventListener('click', waitListen, true);
}
define your Event Handler first,
and then
area.addEventListener('click',handler);
area.removeEventListener('click',handler);
This is what I ended up doing but it's in a route class but should not make much difference, I wanted for the event listener not to accumulate each time afterModel() is called but also needed arguments and scope so that the model is changed each time.
export default class iFrameRoute extends Route {
afterModel(model) {
this.initFrame = function(event) {
alert("I am being called");
window.removeEventListener("message", this.route.test);
}.bind({route: this, data: model});
window.addEventListener("message", this.initFrame );
}
}
I went through this same problem recently. A reasonble solution that I found was remove attribute "onclick" on element from HTMLElement class.
Let's imagine that you already got your component from DOM - using document.getElementById or document.querySelector - you can try that code:
js
const element = document.getElementById("myelement");
element.attributes.removeNamedItem('onclick');
html example
<div onClick="memoryGame.flipCard(this)">
.... // children elements
</div>
I know which this solution it ins't the best, but it works!
I hope I was able to help you.
Cheers!
PS: please, give me a "useful answer"... thanks :D
while adding function store in array and removing pass by map work for me
const [functionObjects, setfunctionObjects] = useState([]);
const addListener = (beforeUnloadListener) =>{
setfunctionObjects([...nano, beforeUnloadListener]);
addEventListener("beforeunload", beforeUnloadListener, {capture: true});
};
const removeListener = (beforeUnloadListener) => {
functionObjects.map((item) => {
removeEventListener("beforeunload", item, {capture: true});});
};
In case of React we can use useRef() to store our listener function in current property. So that in case of re-render and in case of remove listener it will maintain the same reference to the function.
const handleWindowClick = useRef(() => {
console.log("window clicked");
});
// for attaching event listener
window.addEventListener("click", handleWindowClick.current);
// for detaching event listener
window.removeEventListener("click", handleWindowClick.current);
Update 2023
I was using Angular and after numerous tries using AbortController() nothing solved my problem.
Finally renderer2 to the rescue. Here's what I did
mouseMoveListener :any;
mouseUpListener :any;
this.mouseMoveListener = this._renderer.listen("document", "mousemove", (event) => {
this.onMouseMove(event);
});
this.mouseUpListener = this._renderer.listen("document", "mouseup", (event) => {
this.onMouseUp(event);
});
ngOnDestroy(){
this.mouseMoveListener();
this.mouseUpListener();
}

Is using a variable in an onclick worse than using e.target?

Wondering if there is a performance/memory area I could improve when setting onclick listeners to elements.
Example:
let btn = document.querySelector('.example')
btn.addEventListener('click', (e) => {
btn.classList.add('active');
btn.onmouseleave = () => {
console.warn('leave')
}
})
compared to:
let btn = document.querySelector('.example')
btn.addEventListener('click', (e) => {
e.target.classList.add('active');
e.target.onmouseleave = () => {
console.warn('leave')
}
})
I would think that using btn would take a lot more work as the computer has to save the variable and remember it, whereas e.target uses the event temporarily. Which one is best for the least amount of memory consumption?
TL;TiM: Event delegation* is better.
Event target vs currentTarget
Event.target might not be your btn Button.
Say the click casually landed on an icon inside of your button <button type="button">Contact <i class="icon-envelope"></i></button>, the Event.target would be — your icon!
To get your button Element (on which the handler is attached) use Event.currentTarget.
The rule of thumbs is really simple:
Always use Event.currentTarget (even if suggested differently, unless you really, really know what you're doing)
Use Event.target when in need to do stuff like: getting a closest ancestor (or self) from where a click landed. I.e: if (!evt.target.closest("#popup")) // Click landed outside of #popup. Close the popup - or when using Event delegation (more on that later).
Variable vs. Argument performance
There's not much difference in performance in using a variable holding a single Element vs. the Event argument, other than perhaps memory and engine implementation.
If btn is a single Element stored in a variable, it already is the currentTarget: btn === e.currentTarget // always true therefore it's eventually up to your use case, and that's if you need to reuse the btn variable in other parts of the app, which would reduce queries to the DOM.
If instead btns is a huge NodeList, such as the one given back by const btns = ancestorElement.querySelectorAll("selector"), memory could be a factor.
on* event handlers are arguably bad
onmouseleave and other on* handlers should not be used - unless you're creating (in a really small and readable script) brand new Elements from in-memory, or you intentionally want to override a previously assigned on* handler listener of the same eventName.
Use EventTarget.addEventListener() instead with the third argument options {once: true} (for if an event handler is needed only once).
Third, improved version:
const addActive = (evt) => {
const elBtn = evt.currentTarget;
elBtn.classList.add("active");
elBtn.addEventListener("pointerleave", () => {
console.log("leave");
}, {once: true});
};
// Since you're using classes instead of ID,
// make sure to refer to All of your elements by class,
// and assign an event listener to all of them:
const elsBtns = document.querySelectorAll('.example');
elsBtns.forEach((elBtn) => {
elBtn.addEventListener("click", addActive);
});
or if the "mouseleave" or "pointerleave" is always needed:
const handleClick = (evt) => {
const elBtn = evt.currentTarget;
elBtn.classList.add("active");
};
const handleLeave = () => {
console.log("leave");
};
const elsBtns = document.querySelectorAll('.example');
elsBtns.forEach((elBtn) => {
elBtn.addEventListener("click", handleClick);
elBtn.addEventListener("mouseleave", handleLeave);
});
Event delegation
Say you have thousands of buttons. Storing them all in a variable might consume loads of memory (unless till the value is garbage collected). What better solution do we have?
Instead of a NodeList collection of N elements hanging in memory, and assigning to every single one a function handler for a specific Event: target a single common, static parent or ancestor:
document.querySelector("#commonParent").addEventListener("click", (evt) => {
const elBtn = evt.target.closest(".example");
if (!elBtn) return; // Do nothing.
// ".example" Element was clicked. Do something:
elBtn.classList.add("active");
});
Event delegation is not only nice because it's memory friendly, it also works for current of future Elements added into the DOM.
Also that's the other most used case when using specifically Event.target - but as you can see it's again in combination with the Element.closest() Method.

removeEventListener on document in functional component react is not working [duplicate]

I have the following code to add eventListener
area.addEventListener('click',function(event) {
app.addSpot(event.clientX,event.clientY);
app.addFlag = 1;
},true);
It is working correctly as expected..Later in another function i tried to remove the event listener using the following code
area.removeEventListener('click',function(event) {
app.addSpot(event.clientX,event.clientY);
app.addFlag = 1;
},true);
But the even listener is not removed..Why is it happening?Is there any problem with my removeEventListener()?
Note:Here area is something like document.getElementById('myId')
This is because that two anonymous functions are completely different functions. Your removeEventListener's argument is not a reference to the function object that was previously attached.
function foo(event) {
app.addSpot(event.clientX,event.clientY);
app.addFlag = 1;
}
area.addEventListener('click',foo,true);
area.removeEventListener('click',foo,true);
I find that for the windows object, the last param "true" is required.
The remove doesn't work if there is no capture flag.
In a React function component, make sure to define the callback with the useCallback(() => {}) hook. If you fail to do this, the callback will be a different one on every re-render and the removeEventListener method will not work.
const scrollCallback = useCallback(() => { // do sth. });
window.addEventListener("scroll", scrollCallback, true);
window.removeEventListener("scroll", scrollCallback, true);
It looks like no one's covered the part of the DOM specification (that both browsers and Node.js implement) that now gives you a mechanism to remove your event listener without using removeEventListener.
If we look at https://dom.spec.whatwg.org/#concept-event-listener we see that there are a number of properties that can be passed as options when setting up an event listener:
{
type (a string)
callback (an EventListener object, null by default)
capture (a boolean, false by default)
passive (a boolean, false by default)
once (a boolean, false by default)
signal (an AbortSignal object, null by default)
removed (a boolean for bookkeeping purposes, false by default)
}
Now, there's a lot of useful properties in that list, but for the purposes of removing an event listener it's the signal property that we want to make use of (which was added to the DOM level 3 in late 2020), because it lets us remove an event listener by using an AbortController instead of having to bother with keeping a reference to the exact handler function and listener options "because otherwise removeEventListener won't even work properly":
const areaListener = new AbortController();
area.addEventListener(
`click`,
({clientX: x, clientY: y}) => {
app.addSpot(x, y);
app.addFlag = 1;
},
{ signal: areaListener.signal }
);
And now, when it's time to remove that event listener, we simply run:
areaListener.abort()
And done: the JS engine will abort and clean up our event listener. No keeping a reference to the handling function, no making sure we call removeEventListener with the exact same funcation and properties as we called addEventListener: we just cancel the listener with a single, argumentless, abort call.
And of course, also note that if we want to do this "because we only want the handler to fire once", then we don't even need to do this, we can just create an event listener with { once: true } and JS will take care of the rest. No removal code required.
area.addEventListener(
`click`,
() => app.bootstrapSomething(),
{ once: true }
);
You are creating two different functions in both calls. So the second function does not relate in any way to the first one and the engine is able to remove the function. Use a common identifier for the function instead.
var handler = function(event) {
app.addSpot(event.clientX,event.clientY);
app.addFlag = 1;
};
area.addEventListener('click', handler,true);
later you can then remove the handler by calling
area.removeEventListener('click', handler,true);
To remove it, store the function in a variable or simply use a named function and pass that function to the removeEventListener call:
function areaClicked(event) {
app.addSpot(event.clientX, event.clientY);
app.addFlag = 1;
}
area.addEventListener('click', areaClicked, true);
// ...
area.removeEventListener('click', areaClicked, true);
If you want to pass local variables to the function called by the event listener, you can define the function inside the function (to get the local variables) and pass the name of the function in the function itself. For example, let's start inside the function that adds the event listener with app as a local variable. You would write a function inside this function such as,
function yourFunction () {
var app;
function waitListen () {
waitExecute(app, waitListen);
}
area.addEventListener('click', waitListen, true);
}
Then you have what you need to remove it when waitExecute is called.
function waitExecute (app, waitListen) {
... // other code
area.removeEventListener('click', waitListen, true);
}
define your Event Handler first,
and then
area.addEventListener('click',handler);
area.removeEventListener('click',handler);
This is what I ended up doing but it's in a route class but should not make much difference, I wanted for the event listener not to accumulate each time afterModel() is called but also needed arguments and scope so that the model is changed each time.
export default class iFrameRoute extends Route {
afterModel(model) {
this.initFrame = function(event) {
alert("I am being called");
window.removeEventListener("message", this.route.test);
}.bind({route: this, data: model});
window.addEventListener("message", this.initFrame );
}
}
I went through this same problem recently. A reasonble solution that I found was remove attribute "onclick" on element from HTMLElement class.
Let's imagine that you already got your component from DOM - using document.getElementById or document.querySelector - you can try that code:
js
const element = document.getElementById("myelement");
element.attributes.removeNamedItem('onclick');
html example
<div onClick="memoryGame.flipCard(this)">
.... // children elements
</div>
I know which this solution it ins't the best, but it works!
I hope I was able to help you.
Cheers!
PS: please, give me a "useful answer"... thanks :D
while adding function store in array and removing pass by map work for me
const [functionObjects, setfunctionObjects] = useState([]);
const addListener = (beforeUnloadListener) =>{
setfunctionObjects([...nano, beforeUnloadListener]);
addEventListener("beforeunload", beforeUnloadListener, {capture: true});
};
const removeListener = (beforeUnloadListener) => {
functionObjects.map((item) => {
removeEventListener("beforeunload", item, {capture: true});});
};
In case of React we can use useRef() to store our listener function in current property. So that in case of re-render and in case of remove listener it will maintain the same reference to the function.
const handleWindowClick = useRef(() => {
console.log("window clicked");
});
// for attaching event listener
window.addEventListener("click", handleWindowClick.current);
// for detaching event listener
window.removeEventListener("click", handleWindowClick.current);
Update 2023
I was using Angular and after numerous tries using AbortController() nothing solved my problem.
Finally renderer2 to the rescue. Here's what I did
mouseMoveListener :any;
mouseUpListener :any;
this.mouseMoveListener = this._renderer.listen("document", "mousemove", (event) => {
this.onMouseMove(event);
});
this.mouseUpListener = this._renderer.listen("document", "mouseup", (event) => {
this.onMouseUp(event);
});
ngOnDestroy(){
this.mouseMoveListener();
this.mouseUpListener();
}

Click event returns undefined, but when I run a load event on the window the function works

I am trying to simply put a word from my array into the DOM. When the button is clicked it returns undefined and if I console.log the randIndex I get NaN.
Ok, so I have tried to figure this out but I think I am missing something. When I click on the mealBtn it should run a function to show a meal item above the button. However, it returns undefined and puts undefined in the DOM. The thing that most confuses me is if I run an initialize function on window.load it does exactly what its suppose to.
//load an item from menu on window load
window.addEventListener('load', init);
const mealBtn = document.getElementById('mealBtn');
const currentMeal = document.getElementById('current-meal');
const message = document.getElementById('message');
const menu = [
'Macaroni',
'Burgers',
'Chili',
'Breakfast',
'Chicken',
'Take Out?'
];
function init(){
showMeal(menu);
}
mealBtn.addEventListener('click', showMeal);
//show a meal from menu array
function showMeal(menu){
const randIndex = Math.floor(Math.random() * menu.length);
currentMeal.innerHTML = menu[randIndex];
message.innerHTML = 'How about this?';
message.style.color = '#003b6f'
};
I expect that when I click the button it should give a menu suggestion in the DOM right above the button. It works on the init function when the window is loaded just not when the button is clicked.
mealBtn.addEventListener('click', showMeal);
the argument being passed to showMeal when this is triggered is the event, not menu
you want either
mealBtn.addEventListener('click', () => showMeal(menu));
// or
mealBtn.addEventListener('click', showMeal.bind(null, menu));
The second is an example of partial application... it's succinct but not exactly immediately readable.
Event listener callback in javascript accepts single parameter: object based on Event (see here).
MouseEvent (click) has a detail property, whoch can be used as obj.addEventListener("click", (e) => doSomethingWith(e.detail))
In your case, the Event parameter is no use to you and you want to pass custom parameters to the handler. Tyler's answer shows you the way to adapt the handler. However, you could also simply
mealBtn.addEventListener('click', init);

My EventListener to a button continues to fire despite ownership being changed, why? (JavaScript)

I have a function, which at the end of its task assigns a button to a new id.
function altChecker() {
var doc = document,
out = [],
appButton = doc.getElementById('appButton'),
//re = /click-me/gi,
output = doc.createElement('p');
output.setAttribute('id', 'output');
EventUtility.addHandler(appButton, 'click', function(e) {
//I have not included all the function details to concentrate on the question
appButton.id = 'appButtonNextChecker';
var appButtonNextChecker = doc.getElementById('appButtonNextChecker');
nextChecker(appButtonNextChecker);
});
}
function nextChecker(newBtnName) {
EventUtility.addHandler(newBtnName, 'click', function(e) {
$('#output').innerHTML = "";
console.log('next Checker, button!')
});
}
So basically there is one button in the DOM assigned to appButton ID initially, and then I change it doing:
appButton.id = 'appButtonNextChecker';
when the altChecker function fires...
Then I assign the button to a new variable, and pass in the variable to the next function...
var appButtonNextChecker = doc.getElementById('appButtonNextChecker');
nextChecker(appButtonNextChecker);
While I can see the buttons' ID change in the DOM, and I see the console.log fire in the nextChecker function,
$('#output').innerHTML = ""; //doesn't fire
AND the altChecker function fires as well (again)?! Haven't I severed the connection to the click function when I reassigned the new ID?
Any help would be appreciated!
Javascript doesn't remember that you initially attached the event through it's id. The event is attached to the element itself, not the ID. It's not like CSS that way.
In fact your variables are still holding the same element as well, so there's no need to create a new variable after changing the ID, either. Since you're using jQuery you can just type $(appButton).unbind(); to remove the event handler. You may also want to look into .on() and .off()
The problem is that you're trying to use the innerHTML property in a jQuery's object.
That property belongs to Element, and it will not work in the way you're using it.
You can use the document.getElementById method, and it will work fine:
document.getElementById('output').innerHTML = '';
Or you can use jQuery's html method:
$('#output').html('');
And you can even use the first element of the jQuery's array, and use innerHTML again:
$('#output')[0].innerHTML = '';
It's up to you, but the first option will be faster, for sure.

Categories

Resources