Logical loop confusion javascript [duplicate] - javascript

This question already has answers here:
JavaScript closure inside loops – simple practical example
(44 answers)
Closed 5 years ago.
I have an array of songs, where each song has its index number in the array and the .source property. I wrote a script that creates a table row for each song:
var list = document.getElementsByClassName("preview")[1];
for (y = 0; y < allSongs.length; y ++) {
var row = list.insertRow(-1);
var celeft = row.insertCell(0);
var celright = row.insertCell(1);
celright.innerHTML = allSongs[y].name;
celeft.setAttribute("class", "left");
But now I want to make all those left cells (0) linked to the songs, which I tried to do this way:
x = document.getElementsByClassName("left");
var counter = 0;
while (counter < allSongs.length) {
x[counter+1].addEventListener('click', function() {
document.getElementById("audplayer").setAttribute("src", allSongs[counter].source);
});
counter++;
}
Basically, I made them clickable with the addEventListener property, but the problem is that the addEventListener function is not excecuted with the loop, so the counter value is not saved, and everytime I click one of the table cells, counter has a value of 3 at that moment. I understand where the problem comes from but I cannot solve it. How can this be solved?
More details (if the explanation wasnt clear enough):
I want each of the table cells to perform a different action (play different songs), but they all play the same one - allSongs[3], because counter value is taken when the click event happens, not every time the while loop is executed.

Try replacing your loops with map and _.zip from lodash.
Example pseudocode:
song_and_elem_pairs = _.zip(allSongs, x);
song_and_elem_pairs.map(function (song, elem) {
elem.addEventListener('click', function() {
document.getElementById("audplayer").setAttribute("src",
song.source);
}
});
You can also similarly replace the for (y = 0; y < allSongs.length; y ++) loop with a map.
This is certainly not the only way to do this, but it's how I would approach it and I think it should fix your problem.

Related

JavaScript Why does the index of a for loop add one when adding eventlisteners [duplicate]

This question already has answers here:
JavaScript closure inside loops – simple practical example
(44 answers)
Closed 4 years ago.
I have a question which might sound silly. In the code below there are 2 console.log(i) statements. I want to know why does the second console.log(i) statement returns value of 2 and not 1 as the former on the first iteration (i.e. 1st statement i=n, 2nd: i=n+1). Shouldn't both be equal to 1 until the end of the loop?
function toggleWrapper(){
var el1 = document.querySelectorAll('[class="tCell entryDesc"]');
for (var i = 1; i < el1.length; i++) {
console.log(i);
el1[i].addEventListener('click', function(ev){
console.log(i);
var el2=document.querySelectorAll('[class="additionalInfoContainer"]');
if (el2[i-2].clientHeight) {
el2[i-2].style.maxHeight = 0;
}
else{
el2[i-2].style.maxHeight = el2[i-2].scrollHeight +"px";
}
},
false);
}
}
The problem is that the variable i, within each of your addEventListener() functions, is bound to the same variable outside of the function. simply change your for loop to :
for (let i = 1; i < el1.length; i++)
In the loop with let based index, each iteration through the loop will have a new value of i where each value is scoped inside the loop, so your code would work fine.
i think is something in your code because if you try to make a for loop with two "console.log()" it doesn't do that

Calling event listener using a FOR loop javascript [duplicate]

This question already has answers here:
JavaScript closure inside loops – simple practical example
(44 answers)
Closed 5 years ago.
I have a question on the following for loop in javascript - the purpose of the for loop is simple, they are meant to listen to the event for all of the columns that i have.
I have two methods to achieve this, i wonder why one works but not another.
First method that DOES NOT work:
var column = document.querySelectorAll("td");
for (var i =0 ; i< column.length; i++)//column.length is 9
{
column[i].addEventListener("click",function(){
column[i].innerText = "X";
})
}
it prints out the following error when the event is triggered:
Uncaught TypeError: Cannot set property 'innerText' of undefined
at HTMLTableCellElement. (:6:21)
I replaced "column[i].innerText = "X" with console.log(i), i get 9.
But according to my for loop condition, it is supposed to end when i reaches 8 as my column.length is 9, and i use i < column.length, so it should stop at 8.
Question: Why can i get 9 in this for loop ? And why my second approach below can work ?
Second method that DOES work:
var column = document.querySelectorAll("td");
for ( var i = 0 ; i < column.length; i++ )
{
column[i] = clickAction(i);
}
function clickAction(param)
{
column[param].addEventListener("click",function(){
column[param].innerText = "X";
})
}
It works fine if i put the action into a function externally.
Thanks
In the first method when your loop exist that time value of i is 9 and it register events on all column with value 9. So when you fire the event it throws an error.
i found out that to avoid this problem, other than calling the function externally, i can also do the following:
var column = document.querySelectorAll("td");
for (var i =0 ; i< column.length; i++)//column.length is 9
{
column[i].addEventListener("click",function(){
this.innerText = "X"; //using this keyword here so that current object is being called.
})
}

Having issues preserving variable contents in closure [duplicate]

This question already has answers here:
JavaScript closure inside loops – simple practical example
(44 answers)
Closed 7 years ago.
(This question is related to this and this but answers there haven't helped me figure out what's wrong in my case.)
I am trying to create an array of clickable elements where each element is bound to a separate instance of some object.
I've simplified the real code I'm working on as much as possible for this question here:
//----------
// Setup part
// SomeObject just holds a number
var SomeObject = function(number) {
this.number = number;
this.getNumber = function() {
return this.number;
};
};
// contains SomeObject(1) through SomeObject(9)
var someArrayContainingObjects = [];
for(var i=1; i<=9; i++)
{
someArrayContainingObjects.push(new SomeObject(i));
}
//----------
// Problem part
for(var y=0; y<3; y++)
{
for(var x=0; x<3; x++)
{
var obj = someArrayContainingObjects[y*3 + x]; // Creating new variable in the loop every time explicitly with var statement?
$("body").append(
$("<button />")
.text("Should output ("+obj.getNumber()+")")
.click(function() {
alert(obj.getNumber()); // Will always be 9
})
);
}
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
I thought by explicitly using var obj = ... inside the loop I would create a new context/scope/however it's called for each anonymous click() callback function I'm creating – so that when I click one of the objects, the appropriate number of the respective SomeObject is alert()ed and not always the number of the last SomeObject the loop takes from the array.
Could someone please explain to me why this code snippet does not work as expected, and what to change to have the code function correctly?
To create closure scope in JavaScript you need to invoke a function. In JavaScript we can also invoke functions as soon as you declare them. They are called immediately invoked function expressions
This way you can preserve your x and y values in the scope of the IIFE.
for(var y=0; y<3; y++) {
for(var x=0; x<3; x++) {
(function (x, y) {
var obj = someArrayContainingObjects[y * 3 + x]
$("body").append(
$("<button />")
.text("Should output ("+obj.getNumber()+")")
.click(function() {
alert(obj.getNumber())
})
)
}(x, y))
}
}
Working codepen
Also, this is a big problem that people encounter when they try to write JavaScript as if it was a class based language. I would try to look into writing JS from a more functional perspective

Optimal way of showing/hiding images in JavaScript

I read this question here "Is it faster to swap an img src or show/hide multiple images?"
The answer to the question has two options.
1) Alter the image source - Page loads faster, swapping consumes time.
2) Pre load images and then simply show/hide - Page loads a little slower, swapping is quicker
To my problem, I go with the second option as load time is 2nd priority for me. But is there best or most optimal way of writing the code?
Let's say I keep getting numbers (10,11,15,25,13,19 etc randomly) from somewhere. I have to show that many images (small dots, 30 in number, each image about 1kb).. I also have the condition, every dot represents 1.5. So I have written the following code.
var dots = new Array(30);
function onBodyLoad() {
for(var j=0;j<30;j++)
dots[j] = document.getElementById("dotimg"+j);
}
//called by some method every second.
//argument: the value received from somewhere.
function updateImages(s) {
var x = Math.round(s);
var dotPos = x/1.5;
for(var i=0;i<dotPos;i++) {
dots[i].style.visibility='visible'; //show that many dots
document.getElementById('dot-val').textContent=s;//value received shown in span
}
for(var j=dotPos;j<30;j++) dots[j].style.visibility='hidden';//hide remaining dots
}
So if the value received is 25 in one second, 17 dots will be shown.. if in the next second the value received is 15, 10 dots will be shown, the rest will be hidden. Is there a better way of writing the above code?
First of all, move the 'dot-val' setting out of the for loop (You're assigning the same value to it in each iteration of the for loop).
Also, you can change the display states in 1 loop, saving a for:
function updateImages(s) {
var x = Math.round(s);
var dotPos = x/1.5;
for(var i=0;i<30;i++) {
if(i < dotPos){
dots[i].style.display='inline-block'; // Assuming they're inline-block elements.
}else{
dots[i].style.display='none';
}
}
document.getElementById('dot-val').textContent=s;//value received shown in span
}
Now, if you really want to trim your code, skip the temp x var, and use a Ternary operator:
function updateImages(s) {
var dotPos = Math.round(s)/1.5;
for(var i=0;i<30;i++) {
dots[i].style.display = (i < dotPos)? 'inline-block' : 'none';
}
document.getElementById('dot-val').textContent = s;//value received shown in span
}

Array iteration issue in Javascript

I´m trying to make a function that reads elements from an array and distributes its values to <tr> tags of a table.
So I wrote this code:
(function () {
"use strict";
var selectQG = {
distributeCat : function (tablerow, categories) {
var tr = $(tablerow), tri = 0;
tr.each(function () {
if (tri > 2) {
for (var i = 0; i<categories.length; i++) {
this.setAttribute('categoria',categories[i]);
}
}
tri++;
});
}
}
var categories = ['1,2','3,4','5,6','7,8','9,10','11,12'];
selectQG.distributeCat('table tr', categories);
} () );
Check this fiddle to see the html code and the function working: http://jsfiddle.net/5kqEf/2/ )
The loop is being executed 6 times and I can´t figure out why. I´m still a novice at programming, so I´m in need of some help to know what I´m doing wrong. Any help would be very appreciated.
EDIT: The ideia is that every <tr> get the array value. It is supposed to happen starting from the 3rd <tr>, that´s why I put that "if" conditional. So, the first <tr> get "categories[1]", the second <tr> get "categories[2]" and so on.
The outer loop is executed 9 times, since you have 9 table tr elements. The .each will loop over every tr element, which happen to be 9 elements. See http://jsfiddle.net/5kqEf/4/
The inner for loop loops over 6 elements (the array var categories = ['1,2','3,4','5,6','7,8','9,10','11,12'];), so that makes sense...
What is your exact question?
If you want to pick a categorie based on index (and startover if there are more tr elements than categories), you might want to replace this:
for (var i = 0; i<categories.length; i++) {
this.setAttribute('categoria',categories[i]);
}
with this
var index = tri % categories.length;
this.setAttribute('categoria',categories[index]);
See https://developer.mozilla.org/en/JavaScript/Reference/Operators/Arithmetic_Operators for use of the modulus operator.

Categories