Unable to remove event listener Javascript - javascript

I'm trying to remove an event handler before adding a new one. Otherwise it will fire multiple times. This is the method that gets called to attach/remove it.
function attachRemoveBookEvent(bookEl) {
function remove() {
bookEl.remove();
for(let i=0; i < bookObjects.length; i++) {
if(bookObjects[i].id == bookEl.getAttribute("data-id")) {
bookObjects.splice(i, 1);
break;
}
}
hideContainer(removeContainer);
}
removeConfirm.removeEventListener("click", remove);
removeConfirm.addEventListener("click", remove);
}
I'm not sure if it because the methods are actually not identical, it keeps firing multiple times when I press the button.

Every time the interpreter runs across a function keyword, a new function is created. So, if you call attachRemoveBookEvent and attach a listener, and then you call attachRemoveBookEvent again, you no longer have a reference to the original remove function that was created in the original call - rather, you have a reference to the new remove function that was just created. Somehow, you'll have to make sure that the function you pass in is the same as the one that addEventListener was called with. One possibility would be to have have a persistent reference to the current remove that's attached to removeConfirm:
let remove = null;
function attachRemoveBookEvent(bookEl) {
removeConfirm.removeEventListener("click", remove);
remove = function remove() {
bookEl.remove();
for(let i=0; i < bookObjects.length; i++) {
if(bookObjects[i].id == bookEl.getAttribute("data-id")) {
bookObjects.splice(i, 1);
break;
}
}
hideContainer(removeContainer);
}
removeConfirm.addEventListener("click", remove);
}
Or, depending on the sort of logic you want, you might be able to keep the event listener on permanently, and rather than reattaching it, only reassign the bookEl, in a persistent outer variable, something a bit like:
let currentBookEl = null;
function remove() {
if (currentBookEl) currentBookEl.remove();
for(let i=0; i < bookObjects.length; i++) {
if(bookObjects[i].id == bookEl.getAttribute("data-id")) {
bookObjects.splice(i, 1);
break;
}
}
hideContainer(removeContainer);
}
function attachRemoveBookEvent(bookEl) {
currentBookEl = bookEl;
}
Also, rather than manually iterating over bookObjects and breaking, a better option when you're trying to find the index of an element in an array is findIndex:
const idToFind = bookEl.getAttribute("data-id");
const index = bookObjects.find(({ id }) => id === idToFind);
bookObjects.splice(i, 1);

You could add a class to the element and only add the listener if the class doesn't exist
function attachRemoveBookEvent(bookEl) {
function remove() {
bookEl.remove();
for (let i = 0; i < bookObjects.length; i++) {
if (bookObjects[i].id == bookEl.getAttribute("data-id")) {
bookObjects.splice(i, 1);
break;
}
}
hideContainer(removeContainer);
}
if(!removeConfirm.classList.contains('someClass')) {
removeConfirm.classList.add('someClass')
removeConfirm.addEventListener("click", remove);
}
}

Related

EventListener doesn't work on dynamic elements added

I have checked another questions like this one on stackoverflow, but it doesn't solved my problem.
My problem is that whenever I add events to dynamic added elements. but it doesn't work in case I click on that element. It means that it doesn't work properly.
Here is what I have implemented:
function init() {
let promptMessage = 'Enter the number of rows and columns'
let promptDefault = '1 1';
let prompt = this.prompt(promptMessage, promptDefault);
if (prompt) {
prompt = prompt.split(" ")
console.log(prompt);
prompt[0] = Number(prompt[0]);
prompt[1] = Number(prompt[1]);
return prompt;
}
return init()
}
function selected(i, j) {
return console.log(`You clicked on row ${i} and column ${j}`)
}
var gameSize = init(),
rows = gameSize[0],
cols = gameSize[1],
boardGame = document.querySelector('.board');
for (var i = 0; i < rows; i++) {
for (var j = 0; j < cols; j++) {
let div = document.createElement('div');
div.className = `block cell-${i}-${j}`;
div.addEventListener("click", selected(i, j));
boardGame.appendChild(div);
}
}
Problem: I expect that after entering numbers in prompt whenever I inspect the document see onclick="selected(i, j)" for each of elements. But it doesn't work so. Since the browser render the html file, it console.log all the elements, in case I didn't click on them. Where is the problem?
From what I see, div.addEventListener("click", selected(i, j)); is the problematic line.
You're invoking the function selected here (this is why you see the logging on load) and assigning its value as the handler (which in this case is undefined). You're actually intending to assign the function.
Change
function selected(i, j) {
return console.log(`You clicked on row ${i} and column ${j}`)
}
to
function selected(i, j) {
return function(){
console.log(`You clicked on row ${i} and column ${j}`);
}
}
This use of higher order functions lets you close over i and j to build a function that, once invoked, logs the appropriate values
You did bind the function wrong, it is executed the instantly, and the return value (null) is now your event listener. You need to bind it like a function.
ES5
div.addEventListener("click", function(){ selected(i, j) });
ES6
div.addEventListener("click", _ => selected(i, j));
You have another bug as well if you use the variables in the bind function as I showed, when you use var in your loop, the last value will the executed value when the event listener is executed, that bug can be solved if let is used instead
As sunrize920 and Benjaco wrote, you have you eventListener wrong. What you probably need is a closure, like this:
div.addEventListener("click", function() {
return selected(i, j)
});

How to pass an object's method as a parameter to another function in Javascript

First take a look at my simple codes below:
function mySecondFunction(objArray,setFunc)
{
for (let i = 0; i < objArray.length; i++)
{
objArray[i].info.setTop(72);
}
}
function myFunction()
{
let myObjArray = [];
for (let i = 0; i < 10; i++)
{
myObjArray.push({
info:{topVar:0,
bottomVar:0,
get top() {return this.topVar;},
get bottom() {return this.bottomVar;},
setTop: function(input) {this.topVar = input;},
setBottom: function(input) {this.bottomVar = input; }
}
});
}
mySecondFunction(myObjArray); // This works Fine
mySecondFunction(myObjArray,setTop); // I want something like this!!!
}
As you can see, I want to pass a method of an object to another function. I know a lot of possible solutions to avoid this, but I want to know whether it is possible or not.
Detach it and pass as an argument. Remember to use call to set the intended this value.
function mySecondFunction(objArray, setFunc)
{
for (let i = 0; i < objArray.length; i++)
{
setFunc.call(objArray[i].info, 72);
/* explicitly telling that:
please set 'this' value in this function to be 'objArray[i].info' when running,
allowing, e.g. `this.topVar` in
`setTop: function(input) {this.topVar = input;}`
to be operating on `objArray[i].info.topVar` */
}
}
function myFunction()
{
let myObjArray = [];
for (let i = 0; i < 10; i++)
{
myObjArray.push({
info:{topVar:0,
bottomVar:0,
get top() {return this.topVar;},
get bottom() {return this.bottomVar;},
setTop: function(input) {this.topVar = input;},
setBottom: function(input) {this.bottomVar = input; }
}
});
}
mySecondFunction(myObjArray, myObjArray[0].info.setTop);
/* once detaching the method from the object,
(if we are not using arrow functions),
we lose 'this' value, meaning we are losing
the target of object that we want to operate on */
console.log(myObjArray)
}
myFunction();
You can target item number in the array list. You can do statically (i.e. 1-???) or dynamically with an iteration and a variable. You can then the object property within that. For example:
myObjArray[0].info.setTop
That will target the 1st item in the array. Be sure to omit parentheses (()) when passing the method as you want to pass the function reference not the result

Trigger event for each object

I want website to change class of an element if it is filled. So, when user blurs out of an input field the program checks if it has any value and if yes adds a class. The problem is to pass this behaviour to each element in class' collection.
var input = document.getElementsByClassName('input');
contentCheck = function(i){
if(input[i].value>0) input[i].classList.add('filled');
else input[i].classList.remove('filled');
};
for(var i=0; i<input.length; i++) {
input[i].addEventListener('blur',contentCheck(i));
}
This works once after reloading the page (if there's any content in cache), but contentCheck() should trigger each time you leave the focus.
You've half-applied the "closures in loops" solution to that code, but you don't need the closures in loops solution, just use this within contentCheck and assign it as the handler (rather than calling it and using its return value as the handler):
var input = document.getElementsByClassName('input');
var contentCheck = function(){ // <== No `i` argument (and note the `var`)
// Use `this` here
if(this.value>0) this.classList.add('filled');
else this.classList.remove('filled');
};
for(var i=0; i<input.length; i++) {
input[i].addEventListener('blur',contentCheck);
// No () here -------------------------------------^
}
Side note: classList has a handy toggle function that takes an optional flag:
var contentCheck = function(){
this.classList.toggle('filled', this.value > 0);
};
If you needed the "closures in loops" solution (again, you don't, but just for completeness), you'd have contentCheck return a function that did the check:
var input = document.getElementsByClassName('input');
var makeContentCheckHandler = function(i){
return function() {
if(input[i].value>0) input[i].classList.add('filled');
else input[i].classList.remove('filled');
};
};
for(var i=0; i<input.length; i++) {
input[i].addEventListener('blur', makeContentCheckHandler(i));
}
Note I changed the name for clarity about what it does.
Try to use anonymous function
input[i].addEventListener('blur',function(e){
console.log(e);
});
Example: https://jsfiddle.net/42etb4st/4/

Pass iteration variable to click events inside for loop

I have the following piece of code:
var i = 0;
for (var anchor in window.globals.html.anchors) {
var value = window.globals.html.anchors[anchor];
value.addEventListener('click', function(i) {
var currData = window.globals.data[i],
data_clientId = 0;
if (currData.client_product_id !== undefined) {
data_clientId = currData.getAttribute('data_clientId');
} else if (currData.product_id !== undefined) {
data_clientId = currData.product_id;
}
Statistics.send(data_clientId);
window.open(window.globals.data[i].url, '_blank');
}(i));
i++;
}
Which means I want to access global array by interator inside the click event listener. If I don't pass no i to the click event I get the maximum number possible in each iteration, as i would be a global variable.
But right here it seems that all iterations of click events are invoke before clicking anything, onload.
Why is that so, what's wrong?
Looks like you are calling the handler whilst trying to add it to addEventListener. You need to wrap it in a function thus creating a closure that encapsulates your i variable.
var i = 0;
for (var anchor in window.globals.html.anchors) {
var value = window.globals.html.anchors[anchor];
value.addEventListener('click', (function(i) {
return function() {
var currData = window.globals.data[i],
data_clientId = 0;
if (currData.client_product_id !== undefined) {
data_clientId = currData.getAttribute('data_clientId');
} else if (currData.product_id !== undefined) {
data_clientId = currData.product_id;
}
Statistics.send(data_clientId);
window.open(window.globals.data[i].url, '_blank');
}
}(i)));
i++;
}
Notice now that the function you are passing to addEventListener is invoked immediately and returns a function itself where your variable i is scoped.

Sliding accordion with pure JS and for loop

I've written a for loop, which should run through all accordions and their children, but I can't figure out why it's only working on the first object.
Fiddle Example
for (
var i = 0,
accordion = document.getElementsByClassName('accordion');
i < accordion.length;
i++
) {
var accordion_section = accordion[i].children[i],
accordion_key = accordion[i].children[i].children[0],
accordion_bellow = accordion[i].children[i].children[1];
function accordion_bellow_MarginTop( value ) {
accordion_bellow.style.marginTop = value + 'px';
}
accordion_bellow_MarginTop( -accordion_bellow.offsetHeight );
accordion_key.onclick = function() {
if ( accordion_section.getAttribute('class' ) == 'active' ) {
accordion_section.setAttribute('class', '');
accordion_bellow_MarginTop( -accordion_bellow.offsetHeight );
}
else {
accordion_section.setAttribute('class', 'active');
accordion_bellow_MarginTop( 0 );
}
return false;
}
}
There are a couple of issues at play here. As previous commenters noted, you are not properly looping over each of the sections within your accordion. After fixing that, you will also need to address the fact that your onClick handler will not work correctly.
The problem with looping over each section is that you are using improper variable scoping. What happens is only the last element you loop over will be affected by the click handler. This is because the variables "accordion_section" and "accordion_bellow" will always reference the last set of elements in your main for loop.
This is contrary to the expectation that they will be the unique element assigned during the loop. The reason for this is because the variables "accordion_section" and "accordion_bellow" are defined outside the scope of the onClick function. In order for your onClick to work, those variables need to be defined within a separate scope during each iteration of your for loop.
In order to do this, you can use an anonymous function like this:
for (var i = 0; i < sections.length; i++)
{
(function() {
var section = sections.item(i),
anchor = sections.item(i).children[0],
below = sections.item(i).children[1];
closeBelow(below, -below.offsetHeight);
anchor.onclick = function () {
if (section.getAttribute('class' ) == 'active' ) {
section.setAttribute('class', '');
closeBelow(below);
}
else {
section.setAttribute('class', 'active');
openBelow(below);
}
}
})();
}
In this solution, the variables "section", "anchor", and "below" are always specific to the elements you are looping over. By using a self-executing function, you ensure that each click handler only works with locally scoped variables.
Solution: http://jsfiddle.net/b0u916p4/4/
you need to make another loop inside first
this way you get all accordion and all sections of each
try this:
for (i = 0,
accordion = document.getElementsByClassName('accordion');
i < accordion.length;
i++) {
for (j = 0;
j <= accordion[i].children.length;
j++) {
//your code here
}
}

Categories

Resources