Setting onclick to use current value of variable in loop [duplicate] - javascript

This question already has answers here:
JavaScript closure inside loops – simple practical example
(44 answers)
Closed 5 years ago.
The title may not make sense and nor would a description alone, so here's a sample of the code:
for(var a=0;a<10;a++) {
var b=document.createElement('b')
b.onclick=function() {
alert(a)
}
b.innerHTML=a
document.body.appendChild(b)
}
What's odd is that when I set innerHTML to a, it uses the current value of a. So, this code creates ten <b> elements with the values 0, 1, 2, 3, 4, 5, 6, 7, 8, & 9. This works perfectly. What doesn't, however, is the onclick function. It returns the final value of a (10) when any of these elements is clicked. I've tried setting another variable to a and then using that other variable in the onclick event, but still it returns the final value of a. How would I make it use the value of a when onclick is set?

Try the following
b.onclick= function(arg) {
return function() {
alert(arg);
}
}(a);
The problem you were hitting is that javascript doesn't use block scope. Instead all variables have a lifetime of their containing function. So there was only ever one a captured in all of the onclick functions and they all see it's final value.
The solution works around this by creating a new function and value per scope and then immediately setting that value to the current value of a.
Here's a version that's a bit expanded and perhaps a bit more readable
var createClickHandler = function(arg) {
return function() { alert(arg); };
}
for(var a=0;a<10;a++) {
var b=document.createElement('b')
b.onclick = createClickHandler(a);
b.innerHTML=a
document.body.appendChild(b)
}

One dirty workaround which I found out is to use .value property of the created object to store the argument which we want to pass to .onclick function. So I can do something like
for(var a=0;a<10;a++) {
var b=document.createElement('b')
b.value=a
b.onclick=function() {
myfunc(this.value)
}
b.innerHTML=a
document.body.appendChild(b)
}
function myfunc(n){
console.log(n)
}
I am not sure if .value works with every element, but it does for DIV for example.
EDIT: To pass more values then one you can concatenate them using some character like _ and then split it
b.value=x+"_"+y+"_"+z
function myfunc(str){
t=str.split("_")
x=t[0]
y=t[1]
z=t[2]
console.log(x+","+y+","+z)
}

Related

How can I make a javascript function never return the same value as before? [duplicate]

This question already has answers here:
How to efficiently randomly select array item without repeats?
(14 answers)
Closed 1 year ago.
I am working on a random generator made using javascript for a html page I am working on, and I have been able to make a function that uses math.random to generate random value from an array of values (in my case pokemon names, and i am generating random pokemon that have been added into this array.) This is linked to a button, and every time the button is pressed the function runs and a new pokemon name is generated.
However, I am struggling to make it so that the function generates a completely different name each time, and sometimes i click the button more than once and it just shows the same pokemon, and i feel it is not a good look as it feels broken sometimes. I was wondering if someone can look at my code and help me out.
var pokemonNames = ["charmander", "squirtle", "bulbasaur"];
function generateRandomPoke() {
var randPoke = pokemonNames[Math.floor(Math.random() * (pokemonNames.length))];
return randPoke;
}
$("#randomizebutton").click( function() {
$("#pokemonname").html(generateRandomPoke);
});
Get the index and remove the element from the array:
function generateRandomPoke() {
var index = Math.floor(Math.random() * (pokemonNames.length))
var randPoke = pokemonNames[index];
pokemonNames.splice(index, 1);
return randPoke;
}
As #jabaa said, you could remove the elements of the array, or another alternative is to have a global variable that stores the names already returned. In case the value to be returned is on the global variable, a new name must be selected.
You have to generate the index until its not match the previous one see the following code
var index = 0;
do{
Index = Math.floor(Math.random() * (pokemonNames.length));
} while(previous_index==index)
previous_index = index;
var randPoke = pokemonNames[index];
return randPoke;
}

Why is for loop setting all jquery onclick to last iteration [duplicate]

This question already has answers here:
JavaScript closure inside loops – simple practical example
(44 answers)
Closed 5 years ago.
I have the following code that works
var btns = $('.gotobtn');
$('#'+btns.get(0).id).click(function() {
document.querySelector('#navigator').pushPage('directions.html', myInfo[0]); });
$('#'+btns.get(1).id).click(function() {
document.querySelector('#navigator').pushPage('directions.html', myInfo[1]); });
$('#'+btns.get(2).id).click(function() {
document.querySelector('#navigator').pushPage('directions.html', myInfo[2]); });
// this works. I click on button 0 and get myInfo[0],
// on 1 and get myInfo[1], on 2 and get myInfo[2]
But replacing it with a loop does not work correctly. Instead, I always get the last element: myIfno[2] for any button I press.
var btns = $('.gotobtn');
var i = 0;
for (i = 0; i<3; i++){
var btnid = "#" + btns.get(i).id;
$(btnid).click(function() {
document.querySelector('#navigator').pushPage('directions.html', myInfo[i]); });
}
// this does set the buttons on-click but when I click on them,
// all get the latest iteration, in this example myInfo[2]
Why is this? And how do I fix that, without defining each button manually?
I want to see how to do it in jquery.
Because: JavaScript does not have block scope. Variables introduced with a block are scoped to the containing function or script
Replace:
$(btnid).click(function() {
document.querySelector('#navigator').pushPage('directions.html', myInfo[i]);
});
With:
$(btnid).click(customFunction(i));
And declare this function outside the loop:
function customFunction(i) {
document.querySelector('#navigator').pushPage('directions.html', myInfo[i]);
}
your
$(btnid).click(function() {
document.querySelector('#navigator').pushPage('directions.html', myInfo[i]); });
Must be outside your for loop.
#ibrahim mahrir is correct. It's caused by the same phenomena as described in 46039325 although this question is specific for JQuery binding and will probably be useful to some. (I saw the unanswered question in several places on the web)
It happens because I'm binding to i, and in the meantime i has changed to the last iteration (which is 2 in this example). I need to bind to the value of i while iterating.
This will happen (due to quirks in javascript) if I define the binding to a parameter of a function. The parameter is "dynamically created" each time and the value of that param (during that iteration) will be bound.
So when I finally do click on the second button (id:1, the first is id:0), it will invoke the method with the value of 1, correctly.
Here's an example of how the fix looks in jQuery:
$(function(){ // document ready
function btnaction(i){
var btns = $('.gotobtn');
$('#'+btns.get(i).id).click(function() {
document.querySelector('#navigator').pushPage('directions.html', gotoInfo[i]);
});
}
and I call it in the loop
for (i = 0; i<6; i++)
btnaction(i);
Alls well that ends well...

javascript: var i is not defined? (clearly defined)

WHAT I WANT TO HAPPEN
So what I want to happen is function partA() to click button [z] every 2 seconds. The button that is being clicked should change, because the script is a looping script, so for instance. The first loop, it would click button 1, then button 2, then button 3, because var z = 1 + i++. Is this possible? z is supposed to equal the number 1, plus the loop number. This should loop as long as variable i is less than 50.
WHAT IS HAPPENING
It works properly, looping and all, if I test the script without variable z. However, without variable z, I have to manually change the number that would equal z, which is painstaking, and annoying.
var z = 1 + i++
for(i=0;i<50;i++) {
setInterval(partA, 2000);
function partA() {
buttons = document.getElementsByTagName('button');
document.getElementsByTagName('button')[z].click();
}
}
Should i++ be defined another way? The error I'm getting when running the script in console is:
Uncaught ReferenceError: i is not defined (...)
at :2:13
at Object.InjectedScript._evaluateOn (:878:140)
at Object.InjectedScript._evaluateAndWrap (:811:34)
at Object.InjectedScript.evaluate (:667:21)
There's a couple of suggestions I could advise with your code so I'll try and address each one individually.
Firstly define your function outside of your loop. If you would like to know the reasons behind this please read: Don't make functions within a loop
Secondly you should really declare i as a variable to set the scope to which it applies. Some good information on this is at: Declaring variables without var keyword
Thirdly when you run your loop you could run the code inside an IIFE. The reason for this is when you run setInterval, by the time it runs i will actually be 3 (or the last number of your loop). This is due to the asynchronous nature of setInterval, and that the reference to i is bound to the function, not the value of i.
Example
for(var i=0;i<3;i++) {
(function(i) {
setInterval(clickButton(i), 2000);
})(i)
}
function clickButton(idx) {
return function() {
buttons = document.getElementsByTagName('button');
document.getElementsByTagName('button')[idx].click();
}
}
JSBin Demo
http://jsbin.com/harigewuze/edit?html,js,output
Why are you trying to define z outside the loop? Just use i.
for (var i = 0; i < 50; i++) {
...
document.getElementsByTagName('button')[i].click();
}
without changing your code too much I would write it like this...
you know its looping 50 times, you know i is incrementing from 0 to 49, use i to change the button name and you don't need z...
for(i=0;i<50;i++) {
setInterval(partA, 2000);
function partA() {
buttons = document.getElementsByTagName('button');
document.getElementsByTagName('button')[i + 1].click();
}
}
1) This is how you want your code to look like :
var z;
for(i=0;i<50;i++) {
z=i;
setInterval(partA, 2000);
}
function partA() {
buttons = document.getElementsByTagName('button');
document.getElementsByTagName('button')[z].click();
}
2) Unfortunately, in javascript you have a problem with this code due to the fact of scopes. My recommendation is to read this link first http://www.mennovanslooten.nl/blog/post/62 and understand how it works.
If you did understand it, then thumb up..you just promoted yourself to a higher level in javascript ;)
3) If you are still having issues, post it on JSFiddle

For loop function, unexpected output - always the same [duplicate]

This question already has answers here:
JavaScript closure inside loops – simple practical example
(44 answers)
Javascript infamous Loop issue? [duplicate]
(5 answers)
Closed 8 years ago.
I have this weird situation and I don't know why do the output is not what I expect. This is only a simple for-loop function. Can somebody explain me why this happens?
var pm = 2;
for (var i = 0; i < pm; i++) {
$("#specialty_pm_"+i).mouseenter(function(){
alert(i);
});
};
I have 2 divs in my html that has id="specialty_pm_<?php echo $countPM; ?>" and that div is inside a for loop function in php. foreach ($employee_info as $emp_info){ $countPM++; }
I expect that the alert on hover in the 1st div is '1' and the 2nd div is '2'. but when I hover the 1st div, it will alert '2'.
you should use JavaScript closure:
var pm = 2;
for (var i = 0; i < pm; i++) {
var func = (function(i){
return function(){
alert(i);
}
})(i);
$("#specialty_pm_"+i).mouseenter(func);
};
The point is in your code all the mouseenter functions use the same variable i, and after your loop ends, it has its last value which is 2. Using a scope chain, with nested functions and their closures in JavaScript, you can create safe scopes for your variables. Basically what nested functions do, is to provide a outer LexicalEnvironment for the inner function. You can find more information in this post:
Scope Chain in Javascript.
You alert can't wirks because i has only one instance.
Yoi can check variable i inside your div in this case.
try this:
$("#specialty_pm_"+i).mouseenter(function(){
var id = $(this).attr('id');
alert(id.substring(0,13));
});
The reason, as already mentioned, is that the scope of i is the same for both eventhandlers, and as such it will have the same value for both of them.
There is a couple of solution for this problem.
Solution 1: create a new scope via a immediate function
var pm = 2;
for (var i = 0; i < pm; i++) {
$("#specialty_pm_"+i).mouseenter(function(instance){
return function() { alert(instance); };
}(i));
};
You can see a fiddle of it in action here: http://jsfiddle.net/LP6ZQ/
Solution 2: use jQuerys data method to store the value
var pm = 2;
for (var i = 0; i < pm; i++) {
$("#specialty_pm_"+i).data('instance',i).mouseenter(function(){
alert($(this).data('instance'));
});
};
You can see a fiddle of it in action here: http://jsfiddle.net/LP6ZQ/1/
Solution 3: bind the instance number to the eventhandler
var pm = 2;
for (var i = 0; i < pm; i++) {
$("#specialty_pm_"+i).mouseenter(function(instance){
alert(instance);
}.bind(null,i));
};
You can see a fiddle of it in action here: http://jsfiddle.net/LP6ZQ/2/
Solution 3 has a few caveats - this is being bound as null, and thus it can no longer be used as a reference to the dom element, like jQuery eventhandlers nomrally do. Also bind isn't supported by older browsers, but this can be mitigated by usinga polyfill, a good one can be found here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind
Solution 4: Be smart and use a delegate instead of binding event handlers in a loop
$(document.body).on('mouseenter','.specialty_pm',function(){
alert($(this).data('id'));
});
You can see a fiddle of it in action here: http://jsfiddle.net/LP6ZQ/4/
Solution 4 is the "right way" to do it, but it will require you to change the way you build your markup
You could get rid of the for loop in your JavaScript and use jQuery's Starts With Selector to select ALL elements whose id begins with 'specialty_pm_'
$("[id^='specialty_pm_']").on("mouseenter",function(){
var id = $(this).attr('id');
alert(id.substring(13));
});
E.G: http://jsfiddle.net/6bTJ3/
Alternatively you could add a class to each of these specialty_pm_[n] items to make it even simpler to select them in jQuery.

Each for object? [duplicate]

This question already has answers here:
Iterate over Object Literal Values
(8 answers)
Closed 3 years ago.
I have object in JavaScript:
var object = someobject;
Object { aaa=true, bbb=true, ccc=true }
How can I use each for this?
object.each(function(index, value)) {
console.log(value);
}
Not working.
A javascript Object does not have a standard .each function. jQuery provides a function. See http://api.jquery.com/jQuery.each/ The below should work
$.each(object, function(index, value) {
console.log(value);
});
Another option would be to use vanilla Javascript using the Object.keys() and the Array .map() functions like this
Object.keys(object).map(function(objectKey, index) {
var value = object[objectKey];
console.log(value);
});
See https://developer.mozilla.org/nl/docs/Web/JavaScript/Reference/Global_Objects/Object/keys and https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map
These are usually better than using a vanilla Javascript for-loop, unless you really understand the implications of using a normal for-loop and see use for it's specific characteristics like looping over the property chain.
But usually, a for-loop doesn't work better than jQuery or Object.keys().map(). I'll go into two potential issues with using a plain for-loop below.
Right, so also pointed out in other answers, a plain Javascript alternative would be
for(var index in object) {
var attr = object[index];
}
There are two potential issues with this:
1 . You want to check whether the attribute that you are finding is from the object itself and not from up the prototype chain. This can be checked with the hasOwnProperty function like so
for(var index in object) {
if (object.hasOwnProperty(index)) {
var attr = object[index];
}
}
See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/hasOwnProperty for more information.
The jQuery.each and Object.keys functions take care of this automatically.
2 . Another potential issue with a plain for-loop is that of scope and non-closures. This is a bit complicated, but take for example the following code. We have a bunch of buttons with ids button0, button1, button2 etc, and we want to set an onclick on them and do a console.log like this:
<button id='button0'>click</button>
<button id='button1'>click</button>
<button id='button2'>click</button>
var messagesByButtonId = {"button0" : "clicked first!", "button1" : "clicked middle!", "button2" : "clicked last!"];
for(var buttonId in messagesByButtonId ) {
if (messagesByButtonId.hasOwnProperty(buttonId)) {
$('#'+buttonId).click(function() {
var message = messagesByButtonId[buttonId];
console.log(message);
});
}
}
If, after some time, we click any of the buttons we will always get "clicked last!" in the console, and never "clicked first!" or "clicked middle!". Why? Because at the time that the onclick function is executed, it will display messagesByButtonId[buttonId] using the buttonId variable at that moment. And since the loop has finished at that moment, the buttonId variable will still be "button2" (the value it had during the last loop iteration), and so messagesByButtonId[buttonId] will be messagesByButtonId["button2"], i.e. "clicked last!".
See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures for more information on closures. Especially the last part of that page that covers our example.
Again, jQuery.each and Object.keys().map() solve this problem automatically for us, because it provides us with a function(index, value) (that has closure) so we are safe to use both index and value and rest assured that they have the value that we expect.
for(var key in object) {
console.log(object[key]);
}
var object = { "a": 1, "b": 2};
$.each(object, function(key, value){
console.log(key + ": " + object[key]);
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.0.0/jquery.min.js"></script>
//output
a: 1
b: 2

Categories

Resources