Confusion around closure/scope - javascript

Today while working with some JS I had a bug in my code. I was able to resolve it but I don't really understand why the change I made works. All I can guess is that it comes down to either closure or variable scope.
I was trying to build up a nested hash of arrays like so:
var maxNumberOfPairs = 2;
var container = {};
var pairsHash = {};
$.each(["nurse", "doctor", "janitor", "chef", "surgeon"], function(index, role) {
for(var i = 0; i < maxNumberOfPairs; i++){
var pairIdSubString = "attribute_" + i + "_" + role;
pairsHash["attribute_" + i] = [pairIdSubString + "_night", pairIdSubString + "_day"];
}
container [role] = pairsHash;
});
If you run this you get a nice nested output inside container but when you look at each array in the hash you get a weird behaviour with the string produced.
Each one has the last role in each string like so:
"attribute_0_surgeon_night"
If you log out the variable pairIdSubString it correctly has the role in the string, but as soon as this is added to pairHash it just uses the last element in the $.each array.
I was able to fix it by moving pairsHash inside the $.each but outside the for loop.
Can anyone explain to my why the output was different after moving it inside the each?
Thanks

It actually has to do with reference vs value. When its outside the each you are operating on the same object over and over so every time you set it to the container you are just setting a reference to the same object that is constantly changing. So every reference in container after the loop is the last state of the pairsHash because they all point to the same object.
When you put the pairsHash in the each it is reinitialized every time so they all point to different memory addresses. Not the same one since a new one is created every loop.
To further clarify all objects are just references to a memory address In JavaScript so in order to get new one you need to initialize or to pass by value to a function clone it.

Related

Function stored in a global variable doesn't run when called

I'm a noob and also new to this site, so let me know if there are things I should do to improve this post. Anyway, I have a function that is re-used frequently in my site, so I stored it in a global variable and want to call it when a certain button is clicked.
The code looks like this (see below). My problem is that although I can confirm that the button click tries to call the function, it is clearly never actually called (none of my alerts fire and the changes to the text fields are not saved). All of this is contained in the $(document).read(function...
Have I made a dumb mistake somewhere, or is there something I'm doing clearly wrong?
$(document).ready(function () {
//Description:
//Global wrapper variable that contains all global functions. These include:
// 1. saveAll: Saves all values not stored in session data to hidden fields - this includes
// all added ingredient information. This allows us to manually pass values between
// client and server to save to db and also means we can eliminate Null values in table
// storage using a manual delimiter.
//----------------------------------------------------------------------------------------------
var Global = (function () {
return {
saveAll: function () {
alert("entering save");
//start by creating an array and initializing the length of the for loop
var saveValues = [];
var numVals = $('#HidRowCt').val();
alert("numVals: " + numVals);
//Now loop through each ingredient row and create a string containing all textbox values
//in this case, we'll do so by creating an array and then combining the values with a custom delimiter
//the strings will then be saved, one by one, into the saveValues array, which will be serialized as a JSON object,
//stored in a hidden field, and passed to the server
for (i = 1; i < numVals; i++) {
var TxtIngName = $('#TxtIngName' + i).val();
var TxtIngNumUnits = $('#TxtIngNumUnits' + i).val();
var SelIngUnits = $('#SelIngUnits' + i).val();
//make temporary array and string
var saveArr = new Array(TxtIngName, TxtIngNumUnits, SelIngUnits);
var saveStr = saveArr.join("-||-");
saveValues.push(saveStr);
}
alert("Save Values: " + saveValues);
//this will automatically escape quotes, delimited with ","
var jsoncvt = JSON.stringify(saveValues);
$("#HidSave").val(jsoncvt);
}
};
});
//----------------------------------------------------------------------------------------------
//Description:
//Hijack the click event for the save button. Saves values not saved in session data.
//
//Functions:
// Global.saveAll()
//----------------------------------------------------------------------------------------------
$("#SaveChanges").data.clickEvent = $("#SaveChanges").attr('onClick'); //save onclick event locally
$("#SaveChanges").removeAttr('onClick'); //and remove the onclick event
$('#SaveChanges').on('click', function (event) {
Global.saveAll();
//eval($("#SaveChanges").data.clickEvent); //now go ahead with the click event
});
Well, I never figured out why this didn't work, but....
I just removed the global variable and created a separate function for saveAll() and it works. Interestingly, I have a second application using the same code that uses the Global.saveAll (with the same innards) and works fine, so I must have something unusual in one of my earlier lines.
Thanks for your suggestions!
Try setting window.Global = ..., since declaring var Global sets the scope to be within the ready closure.
Then you should be able to use it later.
I just removed the global variable and created a separate function for saveAll() and it works.

TweenJS not respecting instances?

The Situation
I need to loop through an array, and run a tween based on each item's value. Thus, for each item in the array, there must be a separate Tween instance. According to the TweenJS documentation, the get() method "Returns a new tween instance".
The Problem
It appears as though there is only a single instance when running a new tween for each item in the array inside a for loop
The Code
HTML
<div id='log'></div>
Javascript
var items = [3,6,2,8,9,6];
var log = document.getElementById('log');
for(var i = 0; i < items.length; i++){
var item = items[i];
var start = {
x: item * 10,
y: item * 15
};
var end = {
x: item * 10,
y: item * 30
};
createjs.Tween
.get(start, {
onChange: function () {
log.innerHTML = log.innerHTML + i + " - " + JSON.stringify(start) + "<br/>";
}
})
.to(end, 1000)
};
The Demo
http://codepen.io/anon/pen/pilAL - Notice the output. The number at the beginning is the index of the current array's value. It only shows the last item.
Ideally...
Ideally, each item in the array would tween separately between its values.
Thanks in advance!
The quick-and-dirty solution is to use the "event" argument in onChange, and inspect its target.target property.
The trick is that it is properly firing for everything, but the variable scoping is such that i and start no longer contain the values you seek by the time the event method fires.
Here it is with that change: http://codepen.io/anon/pen/FhsHz
Aye, as I expected, it's coming from a lexical scoping issue... I had to refresh myself on it, as it's a while since I've had to deal with Javascript in such a way (and thus worry about it!)
Read through this guide, it's very insightful and starts to address your issue and ways to work around it in the subsection "Creating closures in loops":
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Closures
The gist is that, if you want that anonymous function you create to use the values captured upon creation of the function, you have to get the scoping of the variable you end up using into such a state that it actually is pointing to the desired value.
It's annoying, and they really should provide a more intuitive parameter to you than "event.target.target" as a place to get the value you seek, but the alternative to that is to do something like what they do in this example and essentially make a function generating function, into which you pass the variable. At that point, the scope of the variable that the function uses becomes the scope of the passed-in argument in the "generating function", and it will be pointing to the proper value. Y u c k.

How do I increment an integer inside a variable, every time that variable is called? Javascript

How do I increment an integer inside a variable, every time that variable is called? Javascript.
var a=0;
var t=loadXMLDoc("http://ws.audioscrobbler.com/2.0/?method=artist.getinfo&artist="+x[a].getElementsByTagName("name")[0].childNodes[0].nodeValue+"&api_key=83e386b0ba08735e3dee9b118478e56d&lang=en").getElementsByTagName("bio");
for (i=0;i<20;i++)
{
document.write("<div><button type='button' onclick='document.getElementById("+i+").innerHTML=t[0].getElementsByTagName(\"summary\")[0].childNodes[1].nodeValue;'>Open Bio</button></div>");
}
I'm not sure how I would go about incrementing variable a. I need it to increase by 1 every time variable t is called in the for loop.
When I put all of the code in the for loop I get [object node list] returned so this method is not desired.
If I understood your question correctly, you could define your own getters and setters for the property.
var o = {}
o.__defineSetter__('property', function(value) { this._counter = 0; this._holder = value; })
o.__defineGetter__('property', function() { console.log(this._counter++); return this._holder; })
The counter would be reset every time o.property is assigned a value
o.property = 'Some value'
and then increase every time the property is accessed.
So,
console.log(o.property)
would print
0
Some value
to the console. And if you do it again, it would print
1
Some value
After your edit I think I can see your problem now. You will need to put the loadXMLDoc statement in the loop (since you want to load 20 different XML files), but you can't assign the result of every call to the same variable t - as once the button is clicked, the handler will evaluate t and get only the last value.
Instead, use an array:
var bios = []; // empty array
for (var i=0; i<20; i++) {
var artist = x[i].getElementsByTagName("name")[0].childNodes[0].nodeValue,
doc = loadXMLDoc("http://ws.audioscrobbler.com/2.0/?method=artist.getinfo&artist="+artist+"&api_key=83e386b0ba08735e3dee9b118478e56d&lang=en"),
bio = doc.getElementsByTagName("bio")[0].getElementsByTagName("summary")[0].childNodes[1].nodeValue;
bios[i] = bio; // store it in the array
document.write("<div><button type='button' onclick='document.getElementById("+i+").innerHTML=bios["+i+"];'>Open Bio</button></div>");
}
Of course, while that will work it's a bunch of bad practises, including
unsecured accessing of DOM nodes/properties. If the xml changes its format, you will get lots of exceptions here. You might be sure now that this never happens, but wrapping artist and bio in try-catch might not be a bad idea.
snychronous Ajax. One can do better than that.
loading 20 documents (and that sequentially!) even if you don't need them. It might be worth to try loading each of them only when the respective button is clicked.
document.write
Inline attribute event handlers
…and creating them even by JS.

How to add index to constructed object?

On (document).ready im would like to dynamically generate elements inside a certain parent-element. Lets call them "Candles".
Each "Candle" needs different properties for backgroundImage and color depending on their index().
After creating the page, these attributes need to be changeable via the interface. So its important to save the properties of the "candles" independent from each other.
Thats why I thought it might be useful, to generate for an object for each "Candle" to save their individual properties and to make them editable.
var candleAmount = 3;
for (var i=1; i <= candleAmount; i++) {
$("#container #candles").append("<li><img src=''></img></li>");
var Candle+i = [ "background":"+i+", "color":" - random - (ignore)" ]
};
(please dont mind any failures in the code besides the "Candle+i", I'll figure it out.)
EDIT: Ok, thank you so far. I might not made myself clear enaugh.
Here is an way more reduced example:
$("ul#candles li").each( function(i) {
candle+i = i+" Anything";
});
alert(candle4);
I would love to create an amount of variables depending on the Amount of child-objects.
What would be the correct syntax, or isn't there any?
Thank you
just put them in an array and access them via index. Result is most likely the same for you and is much better than let them floating in your scope
So is there any way, to generate object-names with an index?
Yes, in JavaScript, global variables are defined as properties of the global-object. (Inside a function, variables are defined as properties of the Activation object.) You can reference the global object by window (for browser applications) or just by this.
And because all objects are associative you can give there properties just the name you want. So, setting a global variable is equal to set a property into the global object.
var foo = "bar"; === this["foo"] = "bar";
Now, its just a small step to add a dynamic part to the name:
for(var i=0;i<10;i++) {
this['candle' + i] = i;
}
alert(candle7);
For your specific code:
$("ul#candles li").each( function(i) {
window["candle" + i] = i+" Anything";
});
alert(candle4);

Remembering the last value passed to a JavaScript function called on click

Below is my code fragment:
<div onclick = "myClick('value 1')">
button 1
</div>
<div onclick = "myClick('value 2')">
button 2
</div>
Basically when I for each click on a different div, a different value will be passed to the JavaScript function.
My Question is how can I keep track of the value passed in the previous click?
For example, I click "button 1", and "value 1" will be passed to the function. Later, I click on "button 2", I want to be able to know whether I have clicked "button 1" before and get "value 1".
Just add it to a variable in your script:
var lastClicked;
var myClick = function(value) {
lastClicked = value;
};
You can define somekind of variable, like var lastUsed;
add additional line to your function:
var lastUsed = null;
function myClick(value){
prevClicked = lastUsed; //get the last saved value
...
lastUsed = value; //update the saved value to the new value
...
}
And here you go
You need a variable. Variables are like little boxes in which you can store values. In this case, we can store the value that was last passed to the function myClick.
In Javascript, you can define a variable like this:
var lastClickedValue;
You can "put" a value into that variable. Let's say you want to put your name in there. You would do this:
lastClickedValue = 'sams5817';
Now here's the tricky bit. Variables have "scope". You might want to think about it as their "life-time". When a variable reaches the end of its scope, you cannot read or write to it anymore. It's as if it's never been. Functions define a scope. So any variable you define in a function will disappear at the end of the function. For example:
function myClick(value)
{
var lastClickedValue;
alert('lastClickedValue is = ' + value);
lastClickedValue = value;
}
That looks almost right, doesn't it? We declared a variable, display its last value, and update it with the new value.
However, since the lastClickedValue was declared in the function myClick, once we've reached the end of that function, it's gone. So the next time we call myClick, lastClickedValue will be create all over again. It will be empty. We call that an "uninitialized" variable.
So what's the problem? We're trying to remember a value even after the end of myClick. But we declared lastClickedValue inside myClick, so it stops existing at the end of myClick.
The solution is to make sure that lastClickedValue continues to exist after myClick is done.
So we must delcare lastClickedValue in a different scope. Luckily, there's a larger scope called the "global scope". It exists from the moment your page loads, and until the user moves on to another webpage. So let's do it this way:
var lastClickedValue;
function myClick(value)
{
alert('lastClickedValue is = ' + value);
lastClickedValue = value;
}
It's a very small difference. We moved the declaration of the variable lastClickedValue to be outside the function myClick. Since it's outside, it will keep existing after myClick is done. Which means that each time we call myClick, then lastClickedValue will still be there.
This will let you know what the last value passed to myClick was.
Finally, I'd like to advise you to look for some kind of Javascript tutorials. I wish I knew of some good ones to recommend, but I'm certain you can find a few on the Internet. If you try to write programs before understanding what you're doing, you'll find yourself producing work that is less than what you're capable of. Good luck!
I suppose you need something like this
var clickedButtons = [];
function myClick(value){
...
clickedButtons.push(value);
...
}
I am surprised that no one else mentioned this, but since functions are first class objects in JavaScript, you can also assign attributes and methods to functions. So in order to remember a value between function calls you can do something like I have with this function here:
function toggleHelpDialog() {
if (typeof toggleHelpDialog.status === 'undefined')
toggleHelpDialog.status = true;
else
toggleHelpDialog.status = !toggleHelpDialog.status;
var layer = this.getLayer();
if (toggleHelpDialog.status) layer.add(helpDialog);
else helpDialog.remove();
layer.draw();
}
Here I have added an attribute named 'status' to the toggleHelpDialog function. This value is associated with the function itself and has the same scope as the toggleHelpDialog function. Values stored in the status attribute will persist over multiple calls to the function. Careful though, as it can be accessed by other code and inadvertently changed.
we can leverage javascript static variables
One interesting aspect of the nature of functions as objects is that you can create static
variables. A static variable is a variable in a function‘s local scope whose value persists across
function invocations. Creating a static variable in JavaScript is achieved by adding an instance
property to the function in question. For example, consider the code here that defines a function
doSum that adds two numbers and keeps a running sum:
function doSum(x,y){
if (typeof doSum.static==='undefined'){
doSum.static = x+y;
}else{
doSum.static += x+y;
}
if (doSum.static >= 100){doSum.static = 0;doSum.static += x+y;}
return doSum.static;
}
alert(doSum(5,15))
alert(doSum(10,10))
alert(doSum(10,30))
alert(doSum(20,30))

Categories