Pass iteration variable to click events inside for loop - javascript

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.

Related

Unable to remove event listener 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);
}
}

Jquery setTimeout is making issues and not working properly in loop

I am working on below code snippet. Without setTimeOut(), its working perfect and displaying me the id in loaded(id) function. But with setTimeOut() this is not working properly.
var menuLink = document.getElementsByClassName("li_common_class");
for(var i = 0; i < 5; i++ ) {
var childElement = menuLink[i];
childElement.addEventListener('click',setTimeout(function(){
loaded(childElement.id);
},100), true);
}
function loaded(id){
alert(id);
}
Passing a function
You should be assigning an event handler, but instead you're invoking setTimeout immediately.
Pass a function to .addEventListener(), and use const to declare the variable.
var menuLink = document.getElementsByClassName("li_common_class");
for (var i = 0; i < 5; i++) {
const childElement = menuLink[i];
childElement.addEventListener('click', function() {
setTimeout(function() {
loaded(childElement.id);
}, 100)
}, true);
}
function loaded(id) {
alert(id);
}
So now that pass a function as the second argument to .addEventListener. That function gets assigned as the event handler for the child element. I also declared childElement using const, otherwise you'd always get the last value assigned to that variable instead of the respective value for each loop iteration.
Eliminating the need for a closure reference
However, this still isn't ideal. You really don't need childElement at all, since you have a reference to the element inside the handler already.
var menuLink = document.getElementsByClassName("li_common_class");
for (var i = 0; i < 5; i++) {
menuLink[i].addEventListener('click', function(event) {
var targ = event.currentTarget
setTimeout(function() {
loaded(targ.id);
}, 100)
}, true);
}
function loaded(id) {
alert(id);
}
See now that I added an event parameter to the handler function. This lets you grab the element to which the handler was bound.
We could have used this instead of event.currentTarget, but we actually lose that value inside the setTimeout callback. If we passed an arrow function to setTimeout, then the event handler's this would be reachable.
Reusing the function
But since there's no longer any need for a function to be associated with each iteration of the loop, we can actually move the function outside the loop, so that we're reusing it.
var menuLink = document.getElementsByClassName("li_common_class");
for (var i = 0; i < menuLink.length; i++) {
menuLink[i].addEventListener('click', handler, true);
}
function handler(event) {
var targ = event.currentTarget
setTimeout(function() {
loaded(targ.id);
}, 100)
}
function loaded(id) {
alert(id);
}
<ul>
<li class="li_common_class" id="foo">CLICK ME</li>
<li class="li_common_class" id="bar">CLICK ME</li>
<li class="li_common_class" id="baz">CLICK ME</li>
</ul>
ES6
The same code can be greatly shortened if we are to use ES6:
const menuLinkOnClick = event => setTimeout(() => alert(event.target.id), 100),
menuLinks = document.getElementsByClassName("li_common_class");
for (let menuLink of menuLinks) {
menuLink.addEventListener('click', menuLinkOnClick);
}
Try this:
var menuLink = document.getElementsByClassName("li_common_class");
for (var i = 0; i < 5; i++) {
var childElement = menuLink[i];
(function (ce) {
ce.addEventListener('click', function () {
setTimeout(function () {
loaded(ce.id);
}, 100);
}, true);
})(childElement);
}
function loaded(id) {
alert(id);
}
Try this:
var menuLink = document.getElementsByClassName("li_common_class");
for(i = 0; i < 5; i++) {
var childElement = menuLink[i];
childElement.addEventListener('click', function(){setTimeout(function(){loaded(childElement.id)},100)}, true);
}
function loaded(id){
alert(id);
}
You had a function (setTimeout) in the addEventListener() without having it inside "" or function(){}. You need it in one of those except if you want it to call a function woth no parameters by putting functionName with no quotes, or ().
Hope this works.

Pass dynamic params to IIFE

I've got this issue with passing a variable to an IFFE. did some reading, still didn't figure it out. would really appreciate some guidance here.
i have a click event handler function that gets a certain ID from the
DOM when clicked.
i need to pass that ID to an IIFE
that IFFE needs to either add/remove that ID from an array,
depending if it's already there or not.
This is what I got:
Event:
$(document).on('click', 'input[type="checkbox"]', check);
Click Handler:
function check() {
var id = $(this).closest('ul').attr('data-id');
return id;
}
IIFE:
var checkID = (function (val) {
var arr = [];
return function () {
var i = arr.indexOf(val);
if (i === -1) {
arr.push(val);
} else {
arr.splice(i, 1);
}
return arr;
}
})(id);
right now i'm getting the ID, but returning it to nowhere.
in my IIFE, i did pass an id variable, but it's undefined.
so, how do I pass the ID variable im getting from check() to checkID IIFE?
other solutions are also welcome.
Thanks
In your clickHandler
function check() {
var id = $(this).closest('ul').attr('data-id');
checkID(id);
}
and change checkID to
var checkID = (function () {
var arr = [];
return function (val) {
var i = arr.indexOf(val);
if (i === -1) {
arr.push(val);
} else {
arr.splice(i, 1);
}
return arr;
}
})();
I think you need to do things sort of the other way around. Your check function would return a function used by the event handler, but it would also take a callback to be called after the click handler has run, passing your array.
The check function would look like a mash-up of both your functions:
function check(callback){
var arr = [];
return function(){
var id = $(this).closest('ul').attr('data-id');
var i = arr.indexOf(id);
if (i === -1) {
arr.push(id);
} else {
arr.splice(i, 1);
}
callback(arr);
}
}
As you can see, it takes as a parameter a callback function, which will be called on each execution, passing the current array arr. For example, this is my test callback:
function handler(arr){
alert("Array has " + arr.length + " elements");
}
Finally, your event handler would look like this:
$(document).on('click', 'input[type="checkbox"]', check(handler));
Live example: https://jsfiddle.net/src282d6/
Using getter/setter-like functions in your IIFE function makes it much more organized and readable. Then, use these functions to pass, store, and read data across your IIFE function.
var checkID = (function () {
// your array
var arr = [];
// public
return {
// get
getArray: function(){
return arr;
},
// set value
setArray: function(val) {
var i = arr.indexOf(val);
if (i === -1) {
arr.push(val);
} else {
arr.splice(i, 1);
}
}
}
})();
Use it as follows:
checkID.getArray(); // returns default empty array []
checkID.setArray('car1');
checkID.setArray('car2');
checkID.setArray('car3');
checkID.setArray('car4');
checkID.setArray('car4'); // test splice()
checkID.getArray(); // returns ["car1", "car2", "car3"]

How can I pass a function reference with multiple arguments to the callback of my jQuery(document).on('click' event?

I have the following code definition
$(document).on('click', firstRow, processEvent(firstRow, rowArray));
Which uses the parameters below. As it currently stands I am passing the function processEvent rather than the function definition stored in the variable, and so the function is being invoked immediately.
I wish to set up click handlers dynamically, and for my feature to work I need to be able to pass two parameters to the callback on the click event. The first is a reference to the DOM element(s) the click is attached to, and the second is an array of DOM elements indentifiers (stored as strings) .
How can I pass a function reference with multiple arguments to the callback of my jQuery(document).on('click' event?
var firstRow = '.first-row';
var secondRow = '.second-row';
var thirdRow = '.third-row';
var rowArray = [firstRow, secondRow, thirdRow];
var hideRow = function(input){
var Input = input;
$(Input).show();
};
var showRow = function(input){
var Input = input;
$(Input).hide();
}
// alert(rowArray);
//Hide second and third rows
$('.second-row, .third-row').hide();
var processEvent = function(e, fooArray){ // currently set to one
var E = e; // Cache ID of calling object
var a = fooArray;
// alert(E);
// alert(a);
var arrayLength = a.length;
for (var i = 0; i < arrayLength; i++) {
foo = a[i];
// alert(row); //
alert(foo);
if (foo = e){
showRow(e);
}
else {
hideRow(foo);
}
}
} // function testProcessEvent(){ processEvent(); }
$(document).on('click', firstRow, processEvent(firstRow, rowArray));
This is my first attempt
$(document).on('click', firstRow , someFunction(firstRow ));
Use
$(document).on('click', firstRow, function () {
//place the code here
processEvent(firstRow, rowArray);
});

alert -1 instead of their respective counter inside of the loop?

Why do the anchors, when clicked on, alert -1 instead of their respective counter inside of the loop? How can you fix the code so that it does alert the right number?
text<br>link
<script>
var as = document.getElementsByTagName('a');
for ( var i = as.length; i--; ) {
as[i].onclick = function() {
alert(i);
return false;
}
}
</script>
That's a closure problem: every anonymous function you define inside your loop reads the value of "i" at the end of the for loop.
You need another scope, for example calling a function that sets the onclick handler.
function add_onclick(el, i) {
el.onclick = function() {
alert(i);
return false;
}
}
var as = document.getElementsByTagName('a');
for ( var i = as.length; i--; ) {
add_onclick(as[i],i);
}
An alternative way to achieve the same result as Keeper's answer is to use a function expression for the closure, which you immediately invoke, using your value as a parameter to create the closure
var as = document.getElementsByTagName('a');
for ( var i = as.length; i--; ) (function (i) {
as[i].onclick = function() {
alert(i);
return false;
}
}(i));
This way requires minimal code refactoring
You are printing the value of variable i. After the loop, it's value is -1.

Categories

Resources