Difference of defining an array inside and outside a for loop - javascript

the title is already clear, what's the difference between die create an array inside or outside a for loop.
I will give you an example.
var studentsarray = [];
for(var i = 0; i < 5; i++){
var students = {
id:i,
roll:"9",
age:13
}//end students
studentsarray.push(students);
localStorage.setItem('veritabani', JSON.stringify(studentsarray));
}//end for
var aldim = $.parseJSON(localStorage.getItem('veritabani'));
$.each(aldim, function(i,item){
alert(item.id);
});
if i define inside a for loop, i can't reach all elements, but if i define outside the for loop, it is only the last value of(id) displayed.
Can you explain why?
Thanks in advance.

Few observations:
Javascript only has function level scoping, so defining a variable inside the for loop is equivalent to defining it outside.
However, the variable assignment will happen multiple times if inside the for loop
Please note JSON.stringify is setting studentarray by value multiple times, did you really mean to do this inside the for loop?
I wonder if this is really what you meant to do?
var studentsarray = [];
for(var i = 0; i < 5; i++){
studentsarray.push({id: i, roll:"9", age:13 });
}
localStorage.setItem('veritabani', JSON.stringify(studentsarray));
var aldim = $.parseJSON(localStorage.getItem('veritabani'));
$.each(aldim, function(i,item){
alert(item.id);
});

Related

Adding onlick event to multiple elements with params

I am dynamically creating a table of elements and storing them in an array. The following may seem like an absolute nightmare but this is how I have decided to sort it. My problem now comes to the addEventListener where I want to add an onclick event connected to PlayMusic(). I have tried a simple .onclick = and left out the function(){} but then the PlayMusic() gets executed immediately. Having the function(){} in there, when I click on one of these elements the first param (i) is the "last number used" (aka 22 out of 21 elements). How would I go about making sure each of these onclicks has the correct index in their params?
var thetable = document.getElementById("mustable");
for(var i=0; i<fullists.length-1; i++)
{
fullists[i][2] = [];
fullists[i][3] = [];
for(var j=0; j<fullists[i][1].length; j++)
{
var row = thetable.insertRow();
fullists[i][2][j] = row.insertCell();
fullists[i][2][j].className = "musentry";
var header = fullists[i][0].substring(0,fullists[i][0].lastIndexOf("."));
if(fullists[i][1][j][1] != undefined)
var title = fullists[i][1][j][1];
else
var title = fullists[i][1][j][0].substring(fullists[i][1][j][0].lastIndexOf("/"));
fullists[i][2][j].innerHTML = header + "<br /><b>" + title + "</b>";
fullists[i][2][j].addEventListener("click",function() { PlayMusic(i,j); },false);
fullists[i][3][j] = 0;
}
}
The issue is that by the time the function executes, i already has a different value because the loop already continued executing. If you change your loop to use let i instead of var i (same for j) it will work, because let in the for iterator variable has a special behavior where it actually creates another copy of the variable scoped to the inside of the loop on every iteration, so that copy won't change.
Another way, which is basically the same thing but done explicitly: Store it inside another block-scoped variable first. E.g. const i2 = i and then use i2 inside the function () {}. Same for j.
Alternatively, write .addEventListener(..., PlayMusic.bind(null, i, j)). With bind you can create a new function from a function, where a this and arguments are already bound to it. Since the binding happens immediately and thereby captures the current values of i and j, that solves it too.

How to map within a for loop

We need to map an object array within a for loop, which actually works, but the editor is giving us a warning saying not to put a function within a loop:
for(var i=0; i<$scope.data.list.length; i++){
$scope.data.list[i].isRowSelected=false;
var pos1 = $scope.selectedItems.map(function(e) { return e.sys_id; }).indexOf($scope.data.list[i].sys_id);
if(pos1!==-1){
var add = $scope.selectedItems.indexOf($scope.data.list[i].sys_id);
$scope.selectedItems.splice(add,1);
}
}
To mitigate this, we're thinking about creating a separate function for the mapping and then calling it within the loop, like this:
function mappingID(e){
return e.sys_id;
}
However, when we call upon it within the loop, we're lost as to what to pass in...any suggestions? Thanks!
two things, create a function outside the loop and avoid repeating indexing and object nesting. It will make your code much cleaner and easier to reason about. I'm pretty sure this whole function could be done a lot better but I'm not sure of the bigger scope
var items = $scope.selectedItems;
var sys_id = function(e) { return e.sys_id; }
for(var i=0; i<$scope.data.list.length; i++){
var data = $scope.data.list[i]; // might be a better name for this...
data.isRowSelected=false;
var pos1 = items.map(sys_id).indexOf(data.sys_id);
if(pos1!==-1){
var add = items.indexOf(data.sys_id);
items.splice(add,1);
}
}
The comments suggest lodash, which is a good suggestion. For the purposes of your original question, however, you can declare the function mappingID as you have it, and simply put
var pos1 = $scope.selectedItems.map(mappingID).indexOf($scope.data.list[i].sys_id);
and that will do the job.
You don't need to bring lodash to handle this, you can use find: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find
for(var i=0; i<$scope.data.list.length; i++){
$scope.data.list[i].isRowSelected=false;
var item = $scope.selectedItems.find(e => (e.sys_id === $scope.data.list[i].sys_id));
if (item) {
$scope.selectedItems.splice(item,1);
}
}
Also I suggest changing selectedItems to an plain-object/Map/Set so you can lookup in constant time.
To avoid doing the same mapping on each iteration of the loop, move the mapping outside the loop:
var idArr = $scope.selectedItems.map(function(e) { return e.sys_id; })
$scope.data.list.forEach(item => {
item.isRowSelected=false;
var pos1 = idArr.indexOf(item.sys_id);
if(pos1!==-1){
var add = $scope.selectedItems.indexOf(item.sys_id);
$scope.selectedItems.splice(add,1);
}
})

How do I loop through an array of objects and fill the object in the mouseover function?

I'm a first year student I've been scrolling through Stack Overflow and have read a lot about the object problem (reference) but I can't figure out the solution to my problem.
I have made arrays of objects and looping over them to fill a div with all the info like img, name, value, so far no problem here.
The problem is with filling a mouseover function (attached to the image) with the object I'm looping through at the moment, so later when I hover over the image all the info of that particular object is shown on another div.
for (i = 0; i < arrgezelschap.lenght; i++) {
var x = arrgezelschap[i];
var element = document.createElement("img");
element.src = x.artikelFoto + "k.jpg";
element.addEventListener('mouseover', function() {
showinfo(x)
});
inhoud.append(element);
}
In the function showinfo(object) the output is always the last object of the array.
Why is this and what do I need to do so that it saves or points to the object that it's looping through at the moment in my function?
TL;DR: change var x to let x
I can't really do a better job explaining than Creating closures in loops: A common mistake, but I'll take a shot at rephrasing it.
Compare the output of these two snippets (below). The only difference is var vs let. The example demonstrates creating 5 functions in a loop, but does not call them yet. Each function references variables declared inside the loop, outside the loop and in the for itself. Then, at the end of the loop, we call all the functions to see what we got.
In the first case, the variables outside, i (the loop variable) and inside (declared inside the loop) are all declared with var. They are the same variable on every iteration of the loop. The inside var is hoisted to the top of the scope (outside the loop).
When we call all the functions we created, we will see that they all refer to the one-and-only instance of each variable, and they all have the value that the variables have after completion of the loop.
let functions = [];
var outside = 0;
for (var i = 0; i < 5; ++i) {
outside = i * 10;
var inside = i * 100;
functions.push(() => { console.log(outside, i, inside); })
}
functions.map(f => f()); // call all the functions
Output:
40 5 400
40 5 400
40 5 400
40 5 400
40 5 400
In this second example, the variables are all declared with let. The variable i declared in the for and the variable inside declared inside the body of the loop are different variables on each iteration of the loop. But the outside variable is declared outside the loop, so there's still only one outside variable that is used in every iteration of the loop.
When we call all the functions we made this time, we see that each function is displaying a different variable i and inside and their values are the value they held during that particular iteration of the loop, because the variables only existed for that iteration of the loop and the function was bound to the instance of the variable that was used for that iteration. But the outside variable is the same variable every iteration and holds only one value: the value that it has at the end of the loop.
let functions = [];
let outside = 0;
for (let i = 0; i < 5; ++i) {
outside = i * 10;
let inside = i * 100;
functions.push(() => { console.log(outside, i, inside); })
}
functions.map(f => f()); // call all the functions
Output:
40 0 0
40 1 100
40 2 200
40 3 300
40 4 400
In your case, each function binds to the same (one and only) variable x. If you change your declaration of x from var x to let x then you will get a different variable x for each iteration of the loop, and the event listener function will be bound to a different x each time, which will have the value corresponding to that iteration of the loop.
Footnote: Hopefully functions.map(f => f()); is not confusing for you. It just calls all the functions in the array. It is the same as this:
for (var index = 0; index < functions.length; ++index) {
functions[index]();
}
This is because x is a reference here, not a value and it change while you loop. Have a look at this :
let x = 0;
let fcn = a => console.log(a);
function execAnotherFcn(fcn) {
fcn(x);
}
execAnotherFcn(fcn);
x++;
execAnotherFcn(fcn);
You could use the dataset attribute to store your information.
Here's my implementation:
const root = document.querySelector('#root');
function createImagePlaceholder(color, data) {
const el = document.createElement('div');
el.style.width = '50px';
el.style.height = '50px';
el.style.margin = '5px';
el.style.backgroundColor = color;
el.dataset = data;
root.appendChild(el);
el.addEventListener('mouseover', () => {
document.querySelector('pre').innerText = JSON.stringify(data);
});
el.addEventListener('mouseleave', () => {
document.querySelector('pre').innerText = '';
});
}
createImagePlaceholder('red', { text: 'I am a red block' });
createImagePlaceholder('blue', { text: 'I am a blue block' });
<div id="root"></div>
<pre><pre>
You can fix this by making the scope of element block level.
This happens because here the value of x is send as a closure and the var is defined as function level.The event listner function will get executed at a future time(not to the main thread), so at that time the value of x is changed by the loop to the last value.
This can be done using the let key word or using a IIFE.
1.
for (i = 0; i < arrgezelschap.length; i++) {
let x = arrgezelschap[i];
let element = document.createElement("img");
element.src = x.artikelFoto + "k.jpg";
element.addEventListener('mouseover', function() {
showinfo(x)
});
inhoud.append(element);
}
2.
for (i = 0; i < arrgezelschap.lenght; i++) {
var x = arrgezelschap[i];
var element = document.createElement("img");
element.src = x.artikelFoto + "k.jpg";
(function(x){element.addEventListener('mouseover', function() {
showinfo(x)
});})(x);
inhoud.append(element);
}
#PopHips answer explains the theory of what is going wrong. so here is a working example with your code so you can follow it.
for(i =0;i<arrgezelschap.lenght;i++){
var x = arrgezelschap[i];
var element = document.createElement("img");
element.src = x.artikelFoto + "k.jpg";
element.dataset.identifyer = i;
element.addEventListener('mouseover', function(e) {
showinfo(arrgezelschap[e.target.dataset.identifyer])
});
inhoud.append(element);
}
So because we're using an event listener it will give the first param as an EventArgs object, this contains a property called target that is the HTMLElement effected. we can use the dataset (data-) system to save the identifier to the object's dataset so we can use it in the event handler.
Please note this answer should not be used as it is, there is some really bad practice in this answer, NEVER CREATE A FUNCTION INSIDE A LOOP in production code.

Undefined is not an object in Dropdown

I want my code to show the menu by adding a slactive class and change the value of an input from ddown collection. I have some code, which isn't working as console says that on line 9 nor ddown[i], nor slitems[j] are objects, as they're undefined. How to fix this?
var slitems = document.getElementsByClassName('slitem');
ddown = document.getElementsByClassName('ddown');
for(i=0; i<ddown.length; i++) {
ddown[i].addEventListener('click', function(){document.getElementById('sl'+i).classList.add('slactive');valueChange()});
}
function valueChange(){
for(j=0;j<slitems.length;j++){
slitems[j].addEventListener('click', function(){
ddown[i].value = slitems[j].value;
document.getElementById('sl'+i).classList.remove('slactive');
});
}
}
P.S. slitems is a collection of menu elements.
Look, what you are doing has at least two flaws:
1st: when doing this: for(i=0; i < ddown.length; i++) ... you are declaring a global variable named i that, at the end of loop will have the value ddown.length; so, in valueChange, it will always have the same value
2nd: i is set to ddown.length, that is a position that doesn´t exists in the array, hence the error you got.
To fix this, set i as a local variable using var, and pass it as an argument:
var slitems = document.getElementsByClassName('slitem');
ddown = document.getElementsByClassName('ddown');
for(var i=0; i<ddown.length; i++) {
ddown[i].setAttribute("data-index", i);
ddown[i].addEventListener('click', function(e){
var i = e.target.dataset.index;
document.getElementById('sl'+i).classList.add('slactive');valueChange(i)
});
}
function valueChange(i){
for(var j=0;j<slitems.length;j++){
slitems[j].setAttribute("data-index", j);
slitems[j].setAttribute("data-index2", i);
slitems[j].addEventListener('click', function(e){
var j = e.target.dataset.index;
var i = e.target.dataset.index2;
ddown[i].value = slitems[j].value;
document.getElementById('sl'+i).classList.remove('slactive');
});
}
}
EDIT
Changed the code to add the variables used in iterators as node attributes, what should fix the variable scope issue.

How can I loop over element ids in an array and assign them to variables?

I'm trying to refactor my window.onload function so as to avoid redundancy. I'd like to loop over the elements I'm assigning to global variables, using their ids. Initially, I was able to assign onclick functions with a loop, but now I'm not able to reproduce this in a fiddle. But the main issue is simply trying to do this (see fiddle):
var gragh, gorgh;
var ids = ["gragh", "gorgh"];
for (var i = 0; i < ids.length; i++) {
ids[i] = document.getElementById(ids[i]);
// TypeError: document.getElementById(ids[i]).onclick = doStuff;
}
//console.log(gragh); undefined
This is supposed to assign the variables gragh and gorgh to p elements which have the same ids. Within the loop, ids[i] seems to refer to the p elements. After the loop, however, these variables are undefined. This also doesn't work when looping through an array with these variables not surrounded by quotes. I've even tried using eval(), with mixed results. So my question is, how can I get this to work? And also, why doesn't this work? If ids = [gragh, gorgh] (without the quotes), what do these variables within the array refer to?
Don't reassign it in your loop, try using a new array to populate. Think of it as a reference - you're modifying it while looping.
var gragh, gorgh;
var ids = ["gragh", "gorgh"];
var newSet = [];
for (var i = 0; i < ids.length; i++) {
newSet[i] = document.getElementById(ids[i]);
}
Loop will finish it's looping much before this onclick executes.So at that time the value of i will be the upper limit of the loop.
A work around of this a closure
var gragh;
var ids = ["gragh", "gorgh"];
for (var i=0; i<ids.length; i++) {
(function(i){ // creating closure
console.log(i)
document.getElementById(ids[i]).onclick = doStuff
})(i) // passing value of i
}
document.getElementById("gragh").innerHTML = "ids[0]: " + ids[0] + ", ids[1]: " + ids[1]
function doStuff() {
document.getElementById("gorgh").innerHTML = "ids[0]: " + ids[0] + ",ids[1]: " + ids[1] + ", var gragh: " + gragh;
}
gragh is undefined since you haveonly declared it but never initialized it
JSFIDDLE

Categories

Resources