Number value wrong in event bound through a for loop - javascript

var rows = document.getElementsByClassName('row');
for (var i = 0, l = rows.length; i < l; i++) {
if (i % 2 === 0) {
$(rows[i]).click(function () {
alert('I am line number ' + i);
}
}
}
Hi,
how would I get actual line number for each row ? since all I get when I trigger click event on an even row, upper bound value is alerted (i.e: rows.length = 7, i would be 6 for each row clicked).

The problem is that upon click event is triggered, the i variable was already changed by the loop iteration. Theoretically you can use closure to make things working, i.e.
for (var i = 0, l = rows.length; i < l; i++) {
if (i % 2 === 0) {
(function(i) {
$(rows[i]).click(function() {
alert("I am line number " + i);
});
)(i);
}
}
Practically, if you use jQuery (as I understood from the code), it is easier to use :even selector:
$(".row:even").click(function() {
alert("I am line number " + $(".row").index(this));
});

The reason you're getting the wrong number is that the event handler functions you're creating get an enduring reference to the i variable, not a copy of it as of when they're created.
The way to solve it is to have the handler close over something that won't change. Here are three ways to do that, the first is specific to jQuery (it looks like you're using jQuery):
jQuery's each
It looks like you're using jQuery, in which case you can use its each to get an index to use that won't change:
var rows = $(".row");
rows.each(function(index) {
if (index % 2 === 0) {
$(this).click(function() {
alert('I am line number ' + index);
});
}
});
Now, the event handler function closes over the index argument of the call to the function we give each, and since that index argument never changes, you see the right number in the alert.
Use a builder function
(Non-jQuery) You can solve this with a builder function:
var rows = document.getElementsByClassName('row');
for (var i = 0, l = rows.length; i < l; i++) {
if (i % 2 === 0) {
$(rows[i]).click(buildHandler(i));
}
}
function buildHandler(index) {
return function () {
alert('I am line number ' + index);
};
}
Here, the event handler function closes over the index argument in buildHandler, and since that index argument never changes, you see the right number in the alert.
forEach
(Non-jQuery) You can also use ES5's forEach function (which is one of the ES5 features you can shim on a pre-ES5 environment) to solve this:
var rows = document.getElementsByClassName('row');
Array.prototype.forEach.call(rows, function(row, index) {
if (index % 2 === 0) {
$(row).click(function () {
alert('I am line number ' + index);
});
}
});
This works the same way as the two above, by closing over index, which doesn't change.

Related

When combining 2 strings, Dom element is not showing same results as static entered query

I'm sure I'm overlooking the most obvious, but I need to loop through a few links on the page, but some reason when I try to introduce the iterated number, and change to a string, it doesn't work. By not working, I mean when I run the function, it gives me error ' Uncaught TypeError: Cannot read property 'click' of null'. The first is an example showing it works without the loop. The 2nd is the string combo that doesn't work.
This works!
function myFunction() {
var grids = document.getElementsByClassName('gridRow')
setInterval(function(){
for(i=1;i<grids.length;i++) {
var test = '_resultsGrid__Button_1'
document.getElementById(test).click()
}
}, 2000);
}
This doesn't!
function myFunction() {
var grids = document.getElementsByClassName('gridRow')
setInterval(function() {
for(i=1;i<grids.length;i++) {
var test = '_resultsGrid__Button_' + String(i)
document.getElementById(String(test)).click()
}
}, 2000);
}
Instead of doing
for (i = 1;i < grids.length;i++) { ... }
You should be doing
for (i = 1;i <= grids.length;i++) { ... }
In your first example you were looping only while i was less than (<) the elements length, so you would loop one less time than the length; you'll want to use the less than or equal to (<=) operator instead to include that last iteration
function myFunction() {
var grids = document.getElementsByClassName('gridRow')
setInterval(function() {
for(i = 1; i <= grids.length; i++) {
var test = '_resultsGrid__Button_'+String(i)
document.getElementById(String(test)).click()
}
}, 2000);
}

Uncaught ReferenceError: i is not defined

I'm trying to make a for-loop base on my array
var lists = [ "a", "b", "c", "d" ];
JS
for ( i = 0; i < lists.length; i++) {
// console.log(lists[i]);
$(".sa-report-btn-"+lists[i] ).click(function () {
$(".sa-hide-"+lists[i]).removeClass("hidden");
$(".sa-report-"+lists[i]).addClass("hidden");
});
$(".sa-hide-btn-"+lists[i]).click(function () {
$(".sa-hide-"+lists[i]).addClass("hidden");
$(".sa-report-"+lists[i]).removeClass("hidden");
});
}
Am I doing it correctly ? I got Uncaught ReferenceError: i is not defined
Can I concat each loop with my jQuery selector like this --> $(".sa-hide-"+lists[i]) ? just curious ...
First off, it sounds like you're using strict mode — good! It's saved you from falling prey to The Horror of Implicit Globals.
There are two issues with the code.
The first one is that you're missing the declaration for i. You need to add var i; above the loop, e.g:
var i;
for ( i = 0; i < lists.length; i++) {
// ...
or
for (var i = 0; i < lists.length; i++) {
Note, though, that even in that latter example, the i variable is function-wide, not limited to the for loop.
The second one is more subtle, and is outlined in this question and its answers: Your click handlers will have an enduring reference to the i variable, not a copy of it as of where they were created. So when they run in response to a click, they'll see i as the value lists.length (the value it has when the loop has finished).
In your case, it's really easy to fix (and you don't have to declare i anymore): Remove the loop entirely, and replace it with Array#forEach or jQuery.each:
lists.forEach(function(list) {
$(".sa-report-btn-" + list).click(function () {
$(".sa-hide-" + list).removeClass("hidden");
$(".sa-report-" + list).addClass("hidden");
});
$(".sa-hide-btn-" + list).click(function () {
$(".sa-hide-" + list).addClass("hidden");
$(".sa-report-" + list).removeClass("hidden");
});
});
If you need to support really old browsers, you can either shim Array#forEach (which was added in 2009, as part of ECMAScript5), or you can use $.each (jQuery.each) instead:
$.each(lists, function(index, list) {
// Note addition ------^
$(".sa-report-btn-" + list).click(function () {
$(".sa-hide-" + list).removeClass("hidden");
$(".sa-report-" + list).addClass("hidden");
});
$(".sa-hide-btn-" + list).click(function () {
$(".sa-hide-" + list).addClass("hidden");
$(".sa-report-" + list).removeClass("hidden");
});
});
Note that we don't actually use index anywhere in our callback, but we have to specify it because $.each calls our callback with the index as the first argument, and the value as the second. (Which is why I prefer Array#forEach.) So we have to accept two arguments, with the one we want being the second one.

Skipping multiple elements in a FOR loop, Javascript

I have some file contents I'd like to pass on to my server using a javascript function. In the file, there are many empty lines, or those which contain useless text. So far I read every line in as a string stored in an array.
How do I then loop through that content skipping multiple lines such as lines 24,25, 36, 42, 125 etc. Can I put these element id's into an array and tell my for loop to run on every element except these?
Thanks
you can't tell your for loop to iterate all, but skip certain elements. it will basically just count in any direction (simplified) until a certain critera has been met.
you can however put an if inside your loop to check for certain conditions, and chose to do nothing, if the condition is met. e.g.:
(pseudo code below, beware of typing errors)
for(var line=0; line < fileContents.length; line++) {
if(isUselessLine(line)) {
continue;
}
// process that line
}
the continue keyword basically tells the for loop to "jump over" the rest of the current iteration and continue with the next value.
The isUselessLine function is something you'll have to implement yourself, in a way, that it returns true, if the line with the given linenumber is useless for you.
You can try this its not much elegent but will suerly do the trick
<html>
<body>
<p>A loop which will skip the step where i = 3,4,6,9.</p>
<p id="demo"></p>
<script>
var text = "";
var num = [3,4,6,9];
var i;
for (i = 0; i < 10; i++) {
var a = num.indexOf(i);
if (a>=0) {
continue;
}
text += "The number is " + i + "<br>";
}
document.getElementById("demo").innerHTML = text;
</script>
</body>
You could use something like this
var i = 0, len = array1.length;
for (; i < len; i++) {
if (i == 24 || i == 25) {
array1.splice(i, 1);
}
}
Or you can have an another array variable which got all the items that need to be removed from array1
Another method:
var lines = fileContents.match(/[^\r\n]+/g).filter(function(str,index,arr){
return !(str=="") && uselessLines.indexOf(index+1)<0;
});
If you have many indices to skip, and this depends on the elements of the array, you could write a function that returns the number of elements to skip over for each index in that array (or returns 1, if no skipping required):
for ( let i = 0;
i < array.length;
i += calcNumberOfIndicesToSkip( array, i )){
// do stuff to the elements that aren't
// automatically skipped
}
function calcNumberOfIndicesToSkip( array, i ){
// logic to determine number of elements to skip
// (this may be irregular)
return numberOfElementsToSkip ;
}
In your case:
// skip the next index (i+1)?
for ( let i=0; i<array.length; i+=skipThisIndex(i+1) ){
// do stuff
}
function skipThisIndex(i){
const indicesToSkip = [ 24, 25, 36, 42, 125 ];
return 1 + indicesToSkip.includes(i);
}
// returns 1 if i is not within indicesToSkip
// (there will be no skipping)
// => (equivalent to i++; normal iteration)
// or returns 1 + true (ie: 2) if i is in indicesToSkip
// => (index will be skipped)

Counting how many times a function has been called recursivly in JS

I have got a homework where i am supposed to write a pseudo random number generator in JavaScript. This is the code bit i wrote
var k = 0;
var slump = function(n, k) {
if (k < 10) {
console.log("stop");
}
else {
k++;
console.log((5*n + 1) % 8);
return slump((5*n + 1) % 8, k);
}
};
slump(0);
k is supposed to hold the amount of times the function has been called. But instead of just running the function ten times, it just keeps running. Is there any way to get around this?
You have two subtly different options here, depending on how idiomatic and clever you'd like to get.
The classic implementation, with a slight tweak as JS doesn't support default parameters, would be to use something like:
var finalDepth = 0;
function slump(n, k) {
k = k || 0; // Set to 0 if falsy (null, undef, or 0)
if (logic) {
finalDepth = k; // Record the depth on the last call
} else {
return slump((5*n + 1) % 8, k + 1);
}
}
This will very simply record the deepest the stack has been, and hang onto the value until the next call.
If you want to be slightly more JS-like, you can use closure to keep track of the calls:
function createGenerator() {
var counter = 0;
return {
slump: function (n) {
++counter; // Closure captures counter, counter persists between slump calls but is unique for each createGenerator
if (logic) {
// stop
} else {
return slump((5*n + 1) % 8, k + 1);
}
},
getCounter: function () { return counter; }
}
}
You may be able to use some of the features from ES6 iterators (or generators) to make this more clever.
The function parameter k is uninitialized, therefore not a number. this means in particular that the termination test k < 10 fails as well as the k++ statement doesn't change k' s value. so slump gets always called with the same value for parameter k and the recursion never stops.
Whenever you write a recursive function, you need to make sure that:
There's a base case (in your case, when the console.log statement runs)
The function proceeds towards the base case, and
The function works, assuming the success of the recursive call.
You're running into a problem with the second part; you increment k, but that doesn't bring you any closer to the part where k < 10. In short, you probably want to switch that test around and make sure you're initially calling the function with the right number of arguments. (Aadit M Shah pointed out that you're calling it with one, and it expects two, meaning that it ends up undefined when you call it.)
Either way, iteration would definitely work better here:
var n = 0;
for(var i = 0; i < 10; i++) {
n = (5 * n + 1) % 8;
console.log(n);
}

JQuery for loop stuck at last index [duplicate]

This question already has answers here:
JavaScript closure inside loops – simple practical example
(44 answers)
jQuery Looping and Attaching Click Events
(4 answers)
Closed 9 years ago.
I have function process_row that appends tags to html, and those tags are chained to a function upon clicked. (in this case, simply alert(i), its position in the result array).
But however, upon being clicked, the newly generated alerts the length of the entire result array. I have tried many, many changes to try and make it work, but it doesn't.
Strange thou, fab_div.attr("id", result_data[0]); works fine !! In Chrome inspect element the id tags are displayed as they are, but the click function points everything to the last element in the array.
for example, if I do, fab_div.click(function () { alert(result_data[0]) });, I get the name of the LAST element in the array, doesn't matter which element was clicked.
can anyone please explain to me... WHY??
I think it may have something to do with $("<div>") where JQuery thinks it's the same div that it's assigning to. Is there any way around this? The 's are generated dynamically and I would not want to let PHP do the echoing. Plus the content may be updated realtime.
Example dataset :
Smith_Jones#Smith#Jones#janet_Moore#Janet#Moore#Andrew_Wilson#Andrew#Wilson
After many, many changes, still not working:
function process_row(data){
result_array = data.split("#");
if(result_array.length > 0){
result_data =result_array[0].split("#");
for(i = 0; i < result_array.length; i++){
result_data =result_array[i].split("#");
var fab_text = result_data[1] + " " + result_data[2]
var fab_div = $("<div>");
fab_div.addClass('scroll_tap');
fab_div.attr("id", result_data[0]);
fab_div.append(fab_text)
// fab_div.click(function () { alert(i) });
// ^ not working, try appending list of id's to id_list
id_list.push(result_data[0])
$('#ls_admin').append(fab_div)
}
for(j = 0; j < id_list.length; j++){
$('#' + id_list[j]).click(function () { alert(j) })
}
}
}
Original Attempt:
function process_row(data){
result_array = data.split("#");
if(result_array.length > 0){
result_data =result_array[0].split("#");
for(i = 0; i < result_array.length; i++){
result_data =result_array[i].split("#");
var fab_text = result_data[1] + " " + result_data[2]
var fab_div = $("<div>").append(fab_text).click(function () { alert(i) });
fab_div.addClass('scroll_tap');
fab_div.attr("id", result_data[0]);
$('#ls_admin').append(fab_div)
}
}
}
If you must use an alert, then you can encapsulate the click handler in a self executing function and pass the index to it. Like,
(function (index) {
fab_div.click(function () {
alert(index);
});
})(i);
Although, this is not a clean way to do it. Otherwise, if you are looking to just manipulate the div element is any way, then adding any method directly will also work. Like,
fab_div.click(function () {
alert($(this).attr('id'));
});
You can refer a jsFiddle here
Wonky Solution, but it worked! Haha! Big thanks to Kevin B.
function process_row(data){
result_array = data.split("#");
if(result_array.length > 0){
result_data =result_array[0].split("#");
for(i = 0; i < result_array.length; i++){
result_data =result_array[i].split("#");
var fab_text = result_data[1] + " " + result_data[2]
var fab_div = $("<div>").append(fab_text);
fab_div.addClass('scroll_tap');
fab_div.attr("id", result_data[0]);
$('#ls_admin').append(fab_div)
}
$("#ls_admin").children(this).each(function( index ) {
$(this).append($(this).click(function () { alert($(this).text()) }));
});
}
}

Categories

Resources