Variable Scope - jQuery/JavaScript - javascript

I'm having a bit of trouble with variable scope within jQuery/JavaScript. I'll just give a code example demonstrating my issue as it's probably easier to understand than trying to explain with words.
var example_1 = function() {
row = 10;
}
var example_2 = function() {
something.click(function(){
var something_3 = function() {
alert(row);
}
something_3();
}
}
The problem is, the row variable cannot be accessed within the something_3 function. I have other variables from the example_1 function being accessed in the example_2 function, but when put in the something_3 function it no longer works. Thanks for any help!
Edit: Added extra code to show what works and what isn't. Row is defined within the Build function as are board and mines. Board and mines can be accessed but row cannot.
var build = function(row) {
row = typeof row !== 'undefined' ? row : 5;
board = $('.container');
mines = [1,2,3,4];
}
build();
var start = function() {
var tile = $('.container div');
tile.click(function() {
var this_index = $(this).index();
var has_mine = $.inArray(this_index, mines);
var scan = function() {
alert(row);
}
if (has_mine > -1) {
add_alert("Has mine, you're dead!");
} else {
scan();
$(this).html('Clear!');
}
});
var add_alert = function(msg) {
board.append("<span class='alert'>" + msg + "</span>")
$('body').append("<div class='blackout'></div>");
}
}
start();

var build = function(row) {
row = typeof row !== 'undefined' ? row : 5;
//[...]
}
rowis not being defined as a variable, but instead is a parameter. This way, it doesn't leak as a global variable.
Simply changing the name of parameter should work:
var build = function(rowValue) {
row = typeof rowValue !== 'undefined' ? rowValue: 5;
//[...]
}
However, you shouldn't be using implicitly global variable. Global variables are already bad enough.
Option one, declaring variables:
var row, board, mines;
var build = function(rowValue) {
row = typeof rowValue !== 'undefined' ? rowValue : 5;
board = $('.container');
mines = [1,2,3,4];
}
Option two, using the global identifier:
var build = function(row) {
window.row = typeof row !== 'undefined' ? row : 5;
window.board = $('.container');
window.mines = [1,2,3,4];
}

Multiple issues:
If you don't specify var in front of a variable inside a function it becomes a global variable
row doesn't get intialzed because you didn't call example_1(); yet
In an unrelated note, don't forget to use semicolons at end of the anonymous functions as it might be interpreted as self executing functions.
EDIT 2:
var build = function(row) {};
ok so your issue is the row variable is a parameter so isn't a global variable anymore. If you remove your row parameter / update variable name passed to build method, it will work.

Related

Passing values from an object inside a function

I have been working all day trying to pass the value of "returnData.salary" inside the "readData" function to
the object inside the "calculateTax" function which is suppose to take the salary value and calculate state and federal taxes. I am stumped, I can't find anything on the internet which provides a good example for me to work with. The examples are either way to simple or super complex. Any help would be appreciated.
I apologize in advance if I did not submit this question in the correct format. This is my first time asking for help on stackoverflow.
function readForm() {
var returnData = {};
returnData.name = $("#name").val();
returnData.lastName = $("#lastName").val();
returnData.age = $("#age").val();
returnData.gender = $("[name=gender]:checked").val();
returnData.salary = $("#salary").val();
returnData.isManager = $("#isManager").val();
returnData.myTextArea = $("#myTextArea").val();
$("#name2").text(returnData.name);
$("#lastName2").text(returnData.lastName);
$("#age2").text(returnData.age);
$("#gender2").text(returnData.gender);
$("#salary2").text(returnData.salary);
$("#myTextArea2").text(returnData.myTextArea);
if ($(isManager).is(':checked')) {
$("#isManager2").text("Yes");
}
else {
$("#isManager2").text("No");
}
//$("#employeeForm")[0].reset();
} //end of readForm function
function calculateTax() {
console.log("Button Works");
var calculateTax = {
state: function(num) {
num *= 0.09;
return num;
}
, federal: function(num) {
if (num > 10000) {
num *= 0.2;
return num;
}
else {
num * 0.1;
return num;
}
}
, exempt: true
};
}
//Invoke readForm function when the submit button is clicked.
$(document).ready(function () {
$("#btnSubmit").on("click", readForm);
$("#btnCalculate").on("click", calculateTax);
})
</script>
Well, simply put; you can't. Not like this anyway. Or, at least not pass the value to the function directly.
You are using global functions right now, which are not inside a class. If it was inside a class, you could instantiate the class and save it to this (which would be the class' instance). However, I'm assuming classes are a bit over complicated in this case. What you could do, is set variables globally so all functions can use them, like this;
//declare the global variable so it exists for every function
var returnData = {};
function readForm() {
//We do NOT redeclare the "var" again. It's global now.
returnData = {}; //Reset the global variable when this function is called
returnData.name = $("#name").val();
returnData.lastName = $("#lastName").val();
returnData.age = $("#age").val();
returnData.gender = $("[name=gender]:checked").val();
returnData.salary = $("#salary").val();
returnData.isManager = $("#isManager").val();
returnData.myTextArea = $("#myTextArea").val();
//Rest of your function
}
function calculateTax(){
console.log(returnData) //works here
}
Note that you do overwrite global variables, so it's best to reset them on every function call. You might get old data stuck in there, otherwise.

Local variable lost in closure

The variable cont is being lost in the following:
__factory.setupMenu = function(cont,input,multiSelect,exclusive,popMenu){
var __menu = {multiSelect:multiSelect};
spotter.events.setEventTrigger(input,'change');
__menu.exclusive = {inputs:[],values:exclusive||[],simpleValues:[]};
alert(cont);//<-- is defined here
window.popSelectComponent= cont;//<-- saved it globally to test reference
return function(ajaxResult){
var data = ajaxResult.template.response||[];
var info = {},l=data.length;
while(--l > -1){
info[String(data[l].value)] = data[l].abbr||data[l].name;
}
var textTarget;
alert(window.popSelectComponent);//<-- this is defined as expected
alert(cont);//<-- is now undefined
alert(input);//<-- this is defined as expected
if(!(textTarget = cont.querySelector('[data-pop-selected]'))){textTarget = cont;}
if(!input.popSelectTemplate){
spotter.data.bindElementToInput(textTarget,input,function(content){
content = content.split(',');
var l=content.length;
while(--l > -1){
content[l] = info[content[l]];
}
content = content.join(',');
return (content.length ? content : 'ignore');
});
}
else{
var cont = document.createElement('SPAN');//<-- PROBLEM IS CAUSED HERE. HOISTING IS CAUSING CONT TO BE UNDEFINED AT CLOSURE START
cont.className="multi-select";
cont.appendChild(cont);
//removal function
var remove = (function(input){
return function(e){
var evt = e ? e:window.event;
if (evt.stopPropagation) evt.stopPropagation();
if (evt.cancelBubble!=null) evt.cancelBubble = true;
if(input.value !== input.spotterPopSelectDefaultValue){
input.value = input.value.removeListValue(this.getAttribute('data-id'),',');
spotter.deleteElement(this);
if(input.value === '' && input.value !== input.spotterPopSelectDefaultValue){
input.value = input.spotterPopSelectDefaultValue;
input.eventTriggers['pop-select-change']();
}
}
};
}(input));
input.spotterPopMenuOptions = __menu;
input.addEventListener('pop-select-change',(function(cont, info, template){
return function(){
var HTML = '';
this.value.split(',').forEach(function(val){
HTML += template.replace('$[ID]', val).replace('$[NAME]', info[val]);
});
cont.innerHTML = HTML;
spotter.castToArray(cont.children).forEach(function(el){ console.log('option el',el); el.addEventListener('click',remove,false); });
console.log('input.spotterPopMenuOptions',input.spotterPopMenuOptions);
};
}(cont, info, input.popSelectTemplate.innerHTML)),false);
}
....
So running var func = __factory.setupMenu(...)({template:{}}) I am receiving an error message that cont is undefined while window.popSelectComponent is defined like expected. I tried changing the name of cont thinking I was overlooking something that was changing the value but that did not work either.
After running the function, I check cont in the context that initially created this closure and cont is still defined so it is not a matter of an object reference being lost as far as I can tell.
Perhaps a highly simplified example would make the problem more obvious:
var outer = function(theVariable) {
console.log("In the outer function, theVariable is", theVariable);
var inner = function() {
console.log("In the inner function, theVariable is", theVariable);
if (false) {
var theVariable = 2;
}
};
inner();
}
outer(1)
In the outer function, theVariable is 1
In the inner function, theVariable is undefined
As you have seen, the fact that a different variable with the same name has been declared (even though not initialized) in the inner function hides the properly initialized variable in the outer function that would have otherwise been visible.
You might think that because the variable is declared in a block, it would not affect other parts of the function. No, var is function scoped, not block scope.
This flaw has been addressed in modern versions of Javascript, and the var keyword has been superseded by let, which has the block scope you were expecting. var is kept for backwards compatibility, but you should not use it in new code.

Why I can't pass parameter into .then function (AngularJS)?

I have this function
$scope.updateValue = function(key, selectedProductname, numberUsed){
var selectedKey = key;
var selectedProductname = selectedProductname;
var numberUsed = numberUsed;
var useageRef = ref.child('/useage/');
var updateObj = $firebase(useageRef);
var myData = {
productName : selectedProductname,
numberUsed : numberUsed
}
var decrementLocation = inventoryRef.child(key + '/amount')
updateObj.$push(myData).then(
decrementLocation.transaction(function (current_value, numberUsed) {
console.log(numberUsed);
return (current_value || 0) - 1;
})
);
}
I pass "numberUsed" into $scope.updateValue and use it inside myData and then push it to the server and there is no problem with that but when I use it at this line "decrementLocation.transaction(function (current_value, numberUsed) {" and then I try to console.log(numberUsed); the console says undefined. Why? and how can I use numberUsed in this line "decrementLocation.transaction(function (current_value, numberUsed) {" ? how to code it successfully?
There is a number of things going on here.
First of all, in the following code:
decrementLocation.transaction(function (current_value, numberUsed) {
console.log(numberUsed);
return (current_value || 0) - 1;
})
You are re-declaring numberUsed as the second parameter of the .transaction() callback function. Thus, whatever numberUsed was outside of this small function does not matter. If you want to use the var from the surrounding function, you would need to do:
decrementLocation.transaction(function (current_value) {
console.log(numberUsed);
return (current_value || 0) - 1;
})
Second of all, there is no closing ; to your .transaction() function. I don't think it will materially affect your operating here, but cannot be sure. This should be run through jslint/jshint.
Third, you are redeclaring numberUsed inside the entire surrounding $scope.updateValue() function.
$scope.updateValue = function(key, selectedProductname, numberUsed){
var numberUsed = numberUsed;
So you are declaring a new variable numberUsed, whose value will be, well, numberUsed, but it is a new var, so it should be set to undefined. If it is set to anything at all, that would be surprising. If you need the var, then you should do:
$scope.updateValue = function(key, selectedProductname, numberUsed){
var nu2 = numberUsed;
or something similar. But even then, why bother to redeclare the var? It is copied by value anyways.
A good linter will catch any of this.

jQuery 'if' statement not working

This should be a simple if statement, but it's not working for me. Essentially, when you click an element, I want that element to be highlighted and the ID to be put into a the variable value. However, if in the situation the same element is clicked twice, I want to value = NULL.
(function($){
$(".list").click(function() {
$(this).toggleClass("hilite");
var temp = $(this).attr('id');
if (value != temp) {
var value = $(this).attr('id');
} else {
value = NULL;
}
});
})(jQuery);
Your primary problem is that you're "hoisting" the value variable by redefining it with the var keyword. This code can also be written more efficiently with a lot less code. This should work:
(function($) {
// somewhere outside click handler
var value = '';
// click handler
$(".list").click(function() {
var id = $(this).toggleClass('hilite').attr('id');
value = (value === id) ? null : id;
/* or if you prefer an actual if/else...
if (value === id) {
value = null;
else {
value = id;
}
*/
});
})(jQuery);
Edit: a couple general comments about the original snippet that might be useful:
NULL should be null
Try not to run the same selector multiple times, or recreate a jQuery object from the same DOM object multiple times - it's much more efficient and maintainable to simply cache the result to a variable (e.g., var $this = $(this);)
Your comparison there is probably "safe", but better to use !== than != to avoid unintentional type coercion.
Not sure how exactly you intended to use value in the original example, but always remember that variables are function-scoped in JavaScript, so your var value statement is hoisting the value identifier for that entire function, which means your assignments have no effect on anything outside that click handler.
You need to declare var value outside the scope of the function, so that its value is maintained across function calls. As it is, the value variable is lost right after it is set, because it goes out of scope.
var value = null;
(function($){
$(".list").click(function() {
$(this).toggleClass("hilite");
var temp = $(this).attr('id');
if (value != temp) {
value = temp;
} else {
value = null;
}
});
})(jQuery);
You could do:
(function($){
var tmp = {};
$(".list").click(function() {
$(this).toggleClass("hilite");
var id = $(this).attr('id');
if (!tmp[id]) {
var value = id;
tmp[id] = true;
} else {
value = NULL;
tmp[id] = false;
}
});
})(jQuery);
In this way you use a tmp object that stores the state for all the different id's
It might not be skipping that statement, you might just be getting a confusion over the implied global "value" and the local "value".
(function($){
$(".list").click(function() {
$(this).toggleClass("hilite");
var temp = $(this).attr('id');
if (value != temp) { // <-----------------Implied global var called "value"
var value = $(this).attr('id'); // <---Local variable valled "value"
} else {
value = NULL; // <---------------------Which one am I
}
});
})(jQuery);
Also, it ought to be value = null as NULL is just an undefined variable.
This should be a working example of both points:
var value = null;
(function($){
$(".list").click(function() {
$(this).toggleClass("hilite");
var temp = $(this).attr('id');
if (value != temp) {
value = $(this).attr('id');
} else {
value = null;
}
});
})(jQuery);
Do you not need to declare value before you use it in the conditional statement?
you aren't setting a value in this function.
var value = "NULL";
(function($){
$(".list").click(function() {
$(this).toggleClass("hilite");
var temp = $(this).attr('id');
if (value != temp) {
value = $(this).attr('id');
} else {
value = "NULL";
}
});
})(jQuery);
The variable value is not defined. And it either needs to be a global variable or you could use jQuery's $('.selector).data() method to attach it to the element:
http://api.jquery.com/data/
I also recommend using !== for the comparison, since that compares the type of the variable as well as the content.

JavaScript closures and variable scope

I am having trouble with JS closures:
// arg: an array of strings. each string is a mentioned user.
// fills in the list of mentioned users. Click on a mentioned user's name causes the page to load that user's info.
function fillInMentioned(mentions) {
var mentionList = document.getElementById("mention-list");
mentionList.innerHTML = "";
for (var i = 0; i < mentions.length; i++) {
var newAnchor = document.createElement("a");
// cause the page to load info for this screen name
newAnchor.onclick = function () { loadUsernameInfo(mentions[i]) };
// give this anchor the necessary content
newAnchor.innerHTML = mentions[i];
var newListItem = document.createElement("li");
newListItem.appendChild(newAnchor);
mentionList.appendChild(newListItem);
}
document.getElementById("mentions").setAttribute("class", ""); // unhide. hacky hack hack.
}
Unfortunately, clicking on one of these anchor tags results in a call like this:
loadUserNameInfo(undefined);
Why is this? My goal is an anchor like this:
<a onclick="loadUserNameInfo(someguy)">someguy</a>
How can I produce this?
Update This works:
newAnchor.onclick = function () { loadUsernameInfo(this.innerHTML) };
newAnchor.innerHTML = mentions[i];
The "i" reference inside the closure for the onclick handlers is trapping a live reference to "i". It gets updated for every loop, which affects all the closures created so far as well. When your while loop ends, "i" is just past the end of the mentions array, so mentions[i] == undefined for all of them.
Do this:
newAnchor.onclick = (function(idx) {
return function () { loadUsernameInfo(mentions[idx]) };
})(i);
to force the "i" to lock into a value idx inside the closure.
Your iterator i is stored as a reference, not as a value and so, as it is changed outside the closure, all the references to it are changing.
try this
function fillInMentioned(mentions) {
var mentionList = document.getElementById("mention-list");
mentionList.innerHTML = "";
for (var i = 0; i < mentions.length; i++) {
var newAnchor = document.createElement("a");
// Set the index as a property of the object
newAnchor.idx = i;
newAnchor.onclick = function () {
// Now use the property of the current object
loadUsernameInfo(mentions[this.idx])
};
// give this anchor the necessary content
newAnchor.innerHTML = mentions[i];
var newListItem = document.createElement("li");
newListItem.appendChild(newAnchor);
mentionList.appendChild(newListItem);
}
document.getElementById("mentions").setAttribute("class", "");
}

Categories