I am learning web designing these days, but in this code, I tried to replace this keyword with acc[i], but why the program can't run like before?!
Why can't I replace this keyword with acc[i]?! Is it unreasonable? why?
var panel = this.nextElementSibling;
this.classList.toggle("active");
In two lines above I can't do this
https://www.w3schools.com/howto/tryit.asp?filename=tryhow_js_accordion
if you want to replace this to acc[i] then you have to use a[i-1] because array starts from zero
here is full js code
var acc = document.getElementsByClassName("accordion");
var i;
for (i = 0; i < acc.length; i++) {
acc[i].addEventListener("click", function() {
acc[i-1].classList.toggle("active");
var panel = this.nextElementSibling;
if (panel.style.display === "block") {
panel.style.display = "none";
} else {
panel.style.display = "block";
}
});
}
working demo link
https://jsfiddle.net/er51xcqs/
You can't use acc[i] because you are first adding an event of click to each of "acc" elements, and when the event is triggered you need to have the exact reference of the item you clicked and keyword "this" provides you that reference. If you user acc[i] instead, the i variable by now has the value 3, and acc[3] doesn't exist.
You cannot replace this keyword with acc[i] because at the end of the loop, the variable i will be assigned with the last value that loop receives. So whenever an event happens in some other element , the acc[i] would point to the last element and not the actual element on which the event occured.
you actually create a callback function there to be triggered on clicking acc[i]. So it cannot access acc[i] from inside the function as you don't send it while calling the function.
Related
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 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>
This question already has answers here:
JavaScript closure inside loops – simple practical example
(44 answers)
Closed 6 years ago.
OK, I'm sure this is a super simple question but other answers I found were a bit too complex for me to understand. I want to create a series of buttons, each sending an alert of the loop iteration it was created in. However, every button sends an alert of "5", the value of i after the loop is finished. How do I make a copy of i instead of accessing the actual value? Is this passing by value or reference?
<span id="buttons"></span>
<script>
var buttons = document.getElementById("buttons");
for (var i = 0; i < 5; i++) {
var btn = document.createElement("button");
btn.onclick = function(){alert(i);}
btn.innerHTML = "Button " + i;
buttons.appendChild(btn);
}
</script>
JSFiddle
This is because you have a closure around the i variable that is declared outside of the function that you are using it inside of.
Closures can be difficult to understand and you can learn more about them here.
Change the variable declaration to use let instead of var so that each loop iteration will get its own i scoped to it.
// let causes the variable to have block level scope and with a loop
// that means that a separately scoped variable is created for each
// loop iteration. This allows each iteration to have its own unique
// value for the variable that doesn't share a higher level scope.
for (let i = 0; i < 5; i++) {
var btn = document.createElement("button");
btn.onclick = function(){alert(i);}
btn.innerHTML = "Button " + i;
document.body.appendChild(btn);
}
One option is to wrap the event handler in a IIFE (immediately invoked function expression).
In doing so, you are capturing and passing the value of i on each iteration.
Updated Example
(function (i) {
btn.onclick = function() {
alert(i);
}
})(i);
Another option would be to use the bind() method to pass the value of i on each iteration:
Updated Example
btn.onclick = function(index) {
alert(index);
}.bind(null, i);
However, a better option may be to set a data-* attribute on the elements on each iteration, and then in the event listener you can access that element's data attribute:
Updated Example
btn.dataset.index = i;
btn.addEventListener('click', function() {
alert(this.dataset.index);
});
Basic question, but I have been pounding my head for a bit so thought id bring it here.
html looks like this (edit, fixed the closing quotes)
<span class='deleteimage-119'>delete</span>
<span class='deleteimage-120'>delete</span>
<span class='deleteimage-121'>delete</span>
<span class='deleteimage-122'>delete</span>
<span class='deleteimage-123'>delete</span>
javascript/jquery looks like this
iids = ['119','120','121','122','123'];
for (i=0; i<iids.length; i++) {
place = iids[i];
$(".deleteimage-" + place).click(function () {
alert(place);
});
}
The click functionality gets attached to each individual span, but the alert after clicking just shows the last item in the array.
You have a scoping issue. By the time the callback fires, place has the last value from the loop.
You need to create a new variable for the closure; one variable per iteration, each of which will then be "caught" by the closure and used in the callback.
It would be nice if the solution were this:
var iids = ['119','120','121','122','123']; // don't forget `var` please
for (var i=0; i<iids.length; i++) {
var place = iids[i]; // local variable?
$(".deleteimage-" + place).click(function () {
alert(place);
});
}
Alas, Javascript has no block scope so there's still only one variable here called place, and it keeps getting updated as the loop runs.
So, you have to use a function instead:
var iids = ['119','120','121','122','123'];
function f(place) {
// NOW `place` is a local variable.
$(".deleteimage-" + place).click(function () {
alert(place);
});
}
for (var i=0; i<iids.length; i++) {
f(iids[i]);
}
There are neater ways to employ this function approach using closures and bound variables, and the other answers cover those neater ways quite well. My answer has focused on explaining the issue.
The issue is with scopes and closure.
In JS the scope is # function level and not block level.
Try this:
var iids = ['119','120','121','122','123'];
for (i=0; i<iids.length; i++) {
place = iids[i];
var clickFn = function(a){
return function(){
alert(a);
}
}(place);
$(".deleteimage-" + place).click(clickFn );
}
This is because the click is occurring after the loop is completed, so you're alerting place = iids[iids.length - 1]. In order to achieve the result you're looking for you need to create a function closure and pass place in as a parameter:
iids = ['119', '120', '121', '122', '123'];
for (i = 0; i < iids.length; i++) {
place = iids[i];
(function(_place) {
$(".deleteimage-" + _place).click(function() {
alert(_place);
});
} (place));
}
Inside the loop, you are binding the click event to those span, but the events are fired only after the loop is complete. So, it will always show the last value.
As others have mentioned, you have a scoping issue. Unless all of those classes have a unique meaning, I'd move the place value to a data attribute and leave deleteimage as the class name. Something like:
<span class='deleteimage' data-place='119'>delete</span>
<span class='deleteimage' data-place='120'>delete</span>
<span class='deleteimage' data-place='121'>delete</span>
<span class='deleteimage' data-place='122'>delete</span>
<span class='deleteimage' data-place='123'>delete</span>
$(".deleteimage").click(function() {
var place = $(this).data("place");
alert(place);
});
If the place values aren't unique values, then this answer doesn't apply.
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.