Javascript returns negative number for index - javascript

Getting the negative value when i perform the onclick function in javascript
function sun()
{
var d,i;
var t = document.getElementById("table");
var rows = t.getElementsByTagName("tr");
for (i = 0; i < rows.length; i++) {
console.log("inside............." + i);
rows[i].onclick = function() {
d = (this.rowIndex);
console.log(d);
};
}
}

Though I'm not sure it's what's causing the exact issue you're noticing, you've encountered a pretty common JavaScript pitfall here by using a closure (anonymous function) inside of a loop. JavaScript, like many other languages that support functional programming, has the convenient property that functions can "close scope" around any variables visible to them at the time of their creation. So, as you've done there, you can use the value of d (or i) inside your function so long as it can see them when your function is declared.
Something funny happens inside a loop, though: every function you create within the loop shares the same scope, meaning they all share the exact same copies of d and i. As a result, when you click on any of your rows, the values of d and i used will be their values at the end of the loop, not the particular iteration you're targeting.
This is ordinarily fixed using something known as the "generator pattern," where you create a separate function that returns new functions closed over your desired scope. For example, in your code, you might do something like
function generateClickHandler(i, d) {
return function() {
d = (this.rowIndex);
console.log(d);
};
}
function sun()
{
var d,i;
var t = document.getElementById("table");
var rows = t.getElementsByTagName("tr");
for (i = 0; i < rows.length; i++) {
console.log("inside............." + i);
rows[i].onclick = generateClickHandler(i, d);
}
}
The new function generateClickHandler returns a function itself, but the important thing to notice here is that the returned function closes over the local arguments i and d, not the shared i and d values used in the loop — their values get copied when you call generateClickHandler. In this way, your code won't be subject to strange closure effects.

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.

Is it possble to do comparion of two variables in javascript of different types

I am having a jstree and in javascript function i am checking which node has been selected thats working fine. But when i assign that node value to a variable then do comparasion with a normal string variable thats not working.
$(document).ready(function() {
$('#bdeViewNew').on('changed.jstree', function(e, data) {
var i, j, r = [];
for (i = 0, j = data.selected.length; i < j; i++) {
var x = data.instance.get_node(data.selected[i]).text;
r.push(data.instance.get_node(data.selected[i]).text);
if(x=="Hadoop")
{alert("hi");}
else{
alert("hello");
}
}
});
});
any one know how we can do such comparasion?
thanks in advance
It is possible that your variable has some unexpected whitespace at the start or the end, that's a common problem. You should be able to get rid of it with
var x = data.instance.get_node(data.selected[i]).text.trim();
Should work on all modern browsers.
Also keep in mind that JavaScript has function scope, not block scope like you might be used to from C/C++ or Java. The x you declare there is visible throughout the function, not just in the for loop. You should probably declare it at the same time as i, j and r.

Javascript create a list of functions dynamically

I have a piece of JavaScript code that I want to create a list of functions. All the functions will be put in a dictionary d. d["a"] will give me the function function() {console.log("a")} and d["b"] will give me the function function() {console.log("b")} etc. This is my code:
var a = "abcdefghijklmnopqrstuvwxyz1234567890".split("");
var d = {};
for(var l = a.length, i = 0; i < l; i++)
{
d[a[i]] = function(){console.log(a[i])};
}
However, when I run the above code, d["a"] and d["b"] will be the same, they all point to function(){console.log(a[i])}. How to get what I want?
Thanks.
You need to give each instance of the function its own variable:
for(var l = a.length, i = 0; i < l; i++)
{
(function (x) {
d[a[x]] = function(){console.log(a[x])};
})(i)
}
They don't point to the same instance of function(){console.log(a[i])}, instead, you've created a bunch of functions that all use the same reference to i. The value that i points at changes as the for loop executes.
The other answers provided will work, but it involves generating twice as many functions as you need.
function makeLogFunction(whatToLog) {
return function() {
console.log(whatToLog);
}
}
var a = "abcdefghijklmnopqrstuvwxyz1234567890";
var d = {};
for(var l = a.length, i = 0; i < l; i++) {
d[a[i]] = makeLogFunction(a[i]);
}
Here, I have a makeLogFunction that will return a new function that always prints whatToLog. The other answers will generate a new "version" of the makeLogFunction every time the loop executes. For very large sets of data, this is a waste of time and memory.
This approach has added advantages of clarity and reusability. If there's a significant amount of logic happening in your loop, encapsulating it in a named function allows future reviewers to get a sense of what's going on by the name you give to the function. You can also reuse the function in other parts of your application.

add multiple charts d3, nvd3 using for loop

I'm trying to generate multiple charts using nvd3 and d3. I have the right amount of divs.
If I remove the forloop, then I get a chart in #chart1. If I put the for loop then I get a chart ONLY in #chart2.
Can anyone see why?
for (var j = 1; j <= 2; j += 1) {
var s = '#chart' + j.toString() + ' svg';
console.log(s);
nv.addGraph(function() {
var chart = nv.models.lineChart();
chart.xAxis.axisLabel('Time step').tickFormat(d3.format(',r'));
chart.yAxis.axisLabel('eig(' + j.toString() + ')').tickFormat(d3.format('.02f'));
d3.select(s).datum(function() {
var sin = [], cos = [];
for (var i = 0; i < 100; i++) {
sin.push({
x : i,
y : Math.sin(i / 10)
});
cos.push({
x : i,
y : .5 * Math.cos(i / 10)
});
}
result = [];
result.push({
values : sin,
key : 'sin',
});
return result;
}).transition().duration(500).call(chart);
nv.utils.windowResize(chart.update);
return chart;
});
}
Firstly it's not common to use a for loop like you have (Data Driven Documents). In d3 it is preferred to select all the elements you want and use .each() like so
d3.selectAll('.chart svg')
.each(function(data){
// Do what you would have done in the loop here
})
Secondly it looks like there is an issue with using an anonymous function the way you have (not sure why and not spent too much time looking). By calling it as an actual function it works.
nv.addGraph(addMyChart(this))
See this JSFiddle http://jsfiddle.net/a5BYP/
Stumbled upon this when I was facing the same issue. Hope this helps another beginner like myself.
nv.addGraph() takes a function as callback. This function you passed is not executed immediately but is instead pushed onto the event loop and executed some time later. Internally nv.addGraph is actually quite simple and it makes use of setTimeout.
The reason why for-loop didn't work is because of Javascript scoping. It's the same reason why this code prints 5 5 times instead of 0,1,2,3,4.
for (var i = 0; i < 5; i++) {
setTimeout(function() {
console.log(i)
}, 0)
}
In JS, the var keyword declares a variable to the enclosing function scope (it ignores block scope - for or if curly braces). If you put all the above code in $() then the i variable would be available everywhere inside the $().
When the callback function is executed, it has access to the parent environment where it was first declared.
Inside the callback function, it encounters i. Since i is not declared inside the callback function, it goes one level up to look for i. It finds i in the enclosing function scope but i variable has already been updated to 5 before the callback function was even run.

JavaScript converting an array to array of functions

Hello I'm working on a problem that requires me to change an set array of numbers into an array that returns the original numbers as a function. So we get a return of a2 instead of a[2].
I dont want the answer I just need a hint. I know i can loop through the array and use .pop() to get the last value of the array, but then I dont know how to convert it to a function from there. any hints?
var numToFun = [1, 2, 3];
var numToFunLength = numToFun.length;
for (var i = 0; i < numToFunLength; i++) {
(function(num){
numToFun.unshift(function() {
return num;
});
}(numToFun.pop()))
}
DEMO
basically it pops out a number from the last, builds a function with that number returned, and put back into the first of the array. after one full cycle, all of them are functions.
here's the catch: how this works, it's up to you to research
why the loop does not look like the straightforward pop-unshift:
for (var i = 0; i < numToFunLength; i++) {
numToFun.unshift(function() { //put into first a function
return numToFun.pop() //that returns a number
});
}
and why i did this: (HINT: performance)
var numToFunLength = numToFun.length;
There's three important steps here:
Extract the number value from the array. Within a loop with an iterator of i, it might look like this:
var num = numArray[i];
This is important, because i will not retain its value that it had when you created the new function - it'll end up with the last value it had, once the for loop is finished. The function itself might look like this:
function() { return num; }
There's no reference to i any more, which is important - to understand better, read about closures. The final step would be to add the new function to the array of functions that you want.
...and you're done!
EDIT: See other's answers for good explanations of how to do this right, I will fix mine also though
As others have pointed out, one of the tricky things in javascript that many struggle with (myself included, obviously) is that scoping variables in javascript is dissimilar to many other languages; scopes are almost purely defined by functions, not the {} blocks of, for example, a for loop, as java/C would be.
So, below you can see (and in other answers here) a scoping function can aid with such a problem.
var numArray = [12, 33, 55];
var funcArray = [];
var numArrLength = numArray.length; // Don't do this in for loop to avoid the check multiple times
for(var j=0; j < numArrLength; j++) {
var scopeMe = function() {
var numToReturn = numArray[j];
console.log('now loading... ' + numToReturn);
var newFunc = function() {
return numToReturn;
};
return newFunc;
}();
funcArray.push(scopeMe);
};
console.log('now me');
console.log(funcArray);
console.log(funcArray[0]());
console.log(funcArray[1]());
console.log(funcArray[2]());
console.log(funcArray[1]()); // To ensure it's repeatable
EDIT my old bad answer below
What you'll want to do is something like
var funcArray = [];
for(...) {
var newFunc = function() {
return numArray.pop();
}
funcArray.push(newFunc);
}
The key here is that functions in javascript can be named variables, and passed around as such :)

Categories

Resources