scroll function inside a loop not working because of undefined property - javascript

I want to change the style of items while scrolling.
My code is working if I target the ID, but I have to target many items.
So I changed it for class name and add a "for" loop to get through every items.
It ended with the error "Cannot read property 'style' of undefined".
Can someone explain me where I am wrong ?
var gear = document.getElementsByClassName("rotate-block");
for (var i = 0; i < gear.length; i++) {
window.addEventListener("scroll", function() {
gear[i].style.transform = "rotate("+window.pageYOffset/2+"deg)";
});
};

Your code is using a closure-based access to i inside the scroll listeners.
Because you defined your index using var rather than let, all these closures reference the same i, which is evaluated when the listener is executed, not when it is defined.
After your last iteration of the for-loop, i is equal to gear.length, which means any of the listeners is trying to access gear[gear.length]. The highest index available on any array is length - 1 though.
To fix your issue, simply switch from
for (var i = 0; i < gear.length; i++)
to
for (let i = 0; i < gear.length; i++)
So this is the basis of the error you are describing...
...but
Why are you adding more than one scroll listener in the first place?
You probably instead want to iterate over gear inside the listener, at which point using var is perfectly fine since it's no longer accessed as a closure.
var gear = document.getElementsByClassName("rotate-block");
window.addEventListener("scroll", function() {
for (var i = 0; i < gear.length; i++) {
gear[i].style.transform = "rotate("+window.pageYOffset/2+"deg)";
}
});
For the future, I highly recommend to switch to using for...of to iterate over iterables:
window.addEventListener("scroll", function() {
for (const gear of document.getElementsByClassName("rotate-block")) {
gear.style.transform = "rotate("+window.pageYOffset/2+"deg)";
}
});

Related

Accordion For Loop JS Selector

I'm trying to connect an array of objects referring to SVG Icons in a for loop to an array of matching content by letting the for loop index act as a pairing mechanism. Both Array's log as identifying all the relevant objects but when I call the panel[i] changes I get that panel[i] is undefined? acc[i] seems to work just fine inside the for loop. How can I make sure the acc[i] event listeners pair up with their corresponding index position panels to then control css on the panels to change display from hidden to block?
Thanks!
const acc = document.querySelectorAll(".fas.fa-plus-circle.fa-3x");
const panel = document.querySelectorAll(".panel");
let i;
console.log(acc);
console.log(panel);
for (i = 0; i < acc.length; i++) {
acc[i].addEventListener("click", function() {
this.classList.toggle("active");
console.log(panel[i]);
if (panel[i].style.display === "block") {
panel[i].style.display = "none";
} else {
panel[i].style.display = "block";
}
});
}
Because you have declared i outside the for loop, there is only one i variable, and it will have become equal to acc.length when the loop has completed. So when you click, and the event handler is executed, that value of i is not what you want to have there.
To solve this, make i a block-scoped variable:
for (let i = 0; i < acc.length; i++) {
Now each iteration of the loop will have its own i variable, which will retain its value. There will be an i that is 0, another that is 1, ...Etc. As a consequence the event handlers will each access the right i having the expected value.

I want to get an element tag printed in the console just by clicking on it

I want to get an element tag printed in the console just by clicking on it but it doesn't seem to work and I don't get why?
can anyone point the error in my logic?
let bodyChildren = document.body.children;
let bodyArr = Object.values(bodyChildren);
for (i = 0; i < bodyChildren.length; i++) {
bodyArr[i].onclick = function () {
console.log(bodyArr[i].tagName);
};
}
The problem is that when you define a function, everything in it is contained in a separate scope. Within the function bodyArr is not known. You can use this instead to refer to the clicked element, like below:
document.body.children will only refer to the direct children of the body element. If you want to refer to every element in the DOM, you can use document.getElementsByTagName("*") instead.
When the code is written globally, like in the snippet below, the variable bodyArr is actually available in the global scope, as is the variable i. But keep in mind that the code inside the function is only executed when an element is clicked. At that point in time the for loop has been fully executed leaving i with the value 3 (since in the snippet below the script tag also counts). bodyArr will always contain exactly 1 element less, no matter how many elements are in the DOM. In this case it has 3 elements with the last element being saved at position 2 (zero based) in the array, hence bodyArr[i] equals undefined.
let bodyChildren = document.body.children;
let bodyArr = Object.values(bodyChildren);
for (i = 0; i < bodyChildren.length; i++) {
bodyArr[i].onclick = function () {
console.log(this.tagName);
}
}
<span>child1</span>
<p>child2</p>
You need to get ALL elements from the document body and this is KEY: var all = document.getElementsByTagName("*");
var all = document.getElementsByTagName("*");
let bodyArr = Object.values(all);
for (i = 0; i < bodyArr.length; i++) {
bodyArr[i].onclick = function () {
console.log(this.tagName);
};
}
<span>Hello world</span>

Loop and Functions

I'm having troubles gathering information about clicked eventListeners.
I have this loop which builds an array:
myButtonList = document.getElementsByTagName('a');
myAnchorList = [];
for (i=0; i < myButtonList.length;i++) {
if (myButtonList[i].getAttribute('class') == 'flagged') {
myAnchorList.push(myButtonList[i]);
}
}
For each <a> put into myAnchorList array, I also create another array storing other informations from the same tag (classe and other atrributes).
Here's where I'm struggling. I'm trying to set up an eventListener to send me back those information when those <a> are being clicked. But somehow, the fact that I create a function (for the eventListener) within a loop breaks everything.
for (i=0; i < myAnchorList.length; i++) {
myAnchorList[i].addEventListener("click", function(i){
console.log(alpha+' - '+beta[i]+" - "+charlie[i]);
});
}
My values will either be undefined or some other values which will be the same for each buttons I clicked. alpha is working well as it doesn't depend on any iteration of the loop, but not the others.
Can anybody see what I'm doing wrong here?
for (var i = 0; i < myAnchorList.length; i++) {
(function (i) { //Passes i to your function
myAnchorList[i].addEventListener("click", function () {
console.log(alpha+' - '+beta[i]+" - "+charlie[i]);
});
})(i);
}
The variable "i" in closure that you created in the loop will always retrieve the last value(myAnchorList.length - 1). You shouldn't create closure in a loop, and you can use a "factory" method to create closure instead.

For cycle or for each with js objects

I got a problem to add a onclick event to object who can be many times in same page
I am trying to
var i;
for (i = 1; i<=10; i++) {
var tmpObj='lov_DgId_D_'+i;
var tmpObj2=tmpObj.getElementsByTagName('a')[0];
if (tmpObj2 != null) {
tmpObj2.onclick= DgIdOnClick;
}
}
But got a error TypeError:
Object lov_DgId_D_1 has no method 'getElementsByTagName' , but this is working
lov_DgId_D_2.getElementsByTagName('a')[0].onclick= DgIdOnClick;
This ibject lov_DgId_D_ can be from 1 like lov_DgId_D_1 or lov_DgId_D_99 u.t.c
What wil be the best solution to add onclick to all lov_DgId_D_* objects ?
As you use jquery, the simplest is
for (i = 1; i<=10; i++) {
$('#lov_DgId_D_'+ i+ ' a').click(DgIdOnClick);
}
If you want to bind your event handler to all a elements inside elements whose id starts with lov_DgId_D_, then it's as simple as
$('[id^="lov_DgId_D_"] a').click(DgIdOnClick);
The problem you have is a confusion between the id of an element and the actual element. Some code that should work for you is this one:
var i;
for (i = 1; i<=10; i++) {
var tmpObj=document.getElementById('lov_DgId_D_'+i); // <-- here
var tmpObj2=tmpObj.getElementsByTagName('a')[0];
if (tmpObj2 != null) {
tmpObj2.onclick= DgIdOnClick;
}
}
Slightly easier to read code:
for (var i=0; i<=10; i++) {
var anchor = document.querySelector('#lov_DgId_D_'+i + ' a');
if (anchor) anchor.onclick = DgIdOnClick;
}
A note: this code attaches a click event to the first anchor (a element) inside each element with the id lov_DgId_D_n, with n being 1->10. Your original code seems to want to do the same thing.
Another note: usually when you iterate over elements using their id's to identify them, you are better suited to add a class to those elements instead. It provides for more maintaintable code and probably easier to understand as well.

JavaScript DOM references not holding up

For some reason ss.transition() does not affect the appropriate DOM elements after ss.goTo() is triggered by an onclick. The ss.transition() call under //Init does work as expected. I assume this is a scope problem. Little help?
var ss = {};
ss.goTo = function(i) {
ss.old = ss.current;
ss.current = ss.slides[i];
ss.transition();
}
ss.transition = function() {
ss.old.style.display = "none";
ss.current.style.display = "block";
}
// Hooks
ss.div = document.getElementById("slides");
ss.as = ss.div.getElementsByTagName("a");
// References
ss.slides = [];
for (i in ss.as) {
if (ss.as[i].rel == "slide") {
ss.slides.push(ss.as[i]);
}
}
ss.first = ss.slides[0];
ss.last = ss.slides[ss.slides.length-1];
// Init
ss.current = ss.first;
ss.old = ss.last;
ss.transition();
for (i in ss.as) {
You shouldn't use the for...in loop over an Array or, in this case, NodeList. You'll get member properties you don't want, like item and length. You also can't rely on the items being returned in any particular order; it is very likely that at least ss.last will not be what you expect. If it's a non-item property, ss.old.style.display will definitely fail with an exception, breaking the script.
The correct loop for a sequence is the old-school C construct:
for (var i= 0; i<ss.as.length; i++)
Also, where are you binding the calls to goTo? If you are doing it in a loop with a function inside you, you may well also have the classic loop closure problem. See eg. this question.
The reason for the failure is because you lose the reference to the currently hidden element before you make it show up again. You need to assign old to display:block, then do the switch of old = current, current = variable, then hide old.

Categories