How to resolve a Javascript Scope issue. Is closure the answer? [duplicate] - javascript

This question already has an answer here:
Saving geocoder results to an array - Closure Trouble
(1 answer)
Closed 9 years ago.
I have a function
function getCustomAddress() {
alert(results[i].formatted_address)
}
alert(results[i].formatted_address) is defined in another function. It clearly means that it is undefined in getCustomAddress, so how do I resolve this issue and alert the values. I have set up a fiddle as well.
http://jsfiddle.net/KEdrq/5/

You could just pass it as a function parameter
function getCustomAddress(result) {
alert(result.formatted_address)
}
so when you call the function you need to supply one parameter:
getCustomAddress(results[i]); for example

You could create a private scope with a function and define all your global variables there:
(function(){
var results = [];
function getCustomerAdress(){
//... call result etс
}
function set result(){
//... set result etc
}
// some code for initialization, setting onload handlers etc
})();

I checked out the jsFiddle, the results are fetched as an ajax request.
You need to store the results in a variable with a global scope and then set a timeout to fetch the result. You can also execute your function before the end of geocoder request and pass it the results variable.
geocoder.geocode(geocoderRequest, function (results, status) {
// execute your function here. getCustomAddress(result)
}
Check the changes I have made.
http://jsfiddle.net/KEdrq/7/
Summary of code changes.
var _results;
function initialize() {
.
.
.
google.maps.event.addListener(marker, 'dragend', function (e) {
getAddress(e.latLng);
setTimeout('getCustomAddress(0);', 500);
})
function getAddress(latLng) {
if (!geocoder) {
geocoder = new google.maps.Geocoder();
}
var geocoderRequest = {
latLng: latLng
}
geocoder.geocode(geocoderRequest, function (results, status) {
_results = results;
.
.
.
function getCustomAddress(i) {
alert(_results[i].formatted_address)
}
You might want to create a for loop to alert all the results instead of passing the result id in the getCustomAddress function.

Related

Should global variables in javascript be always avoided?

I have seen, google places api docs, suggesting use of global variables outside of the functions, like:
var placeSearch, pauto, geocoder;
I want to use data returned from $.get, outside of the function.
Could I also use a global variable that will be fed by data inside a callback function?
var glo_var;
function callback(data){
glo_var = data;
}
function codeAddress(address) {
geocoder.geocode({ 'address': address},
function(response, status) {
if (status == 'OK')
{
var senddata = $.get('/myurl',{params}, function (data){ callback(data) });
} else {
}
});
}
Any other way, that I could make use of returned datafrom $.get. I do not understand closures.
EDIT - Why is this a duplicate, I am asking where to store the returned data, I already know how to get it? It's the use of globals that i am not sure of, but since google api uses it, i asked about it here.
so the reason for to avoid using global variables as much as possible as stated in the previous answers. It is about easily overriding problem and troubleshooting when some global values are being overriden. From my own experience, I usually create an utility to handle sharing values. The idea is as following
//global.js
(function(){
const context = {};
function setGlobalValue(key, value) {
context[key] = value;
}
function getGlobalValue(key) {
return context[key];
}
window.setGlobalValue = setGlobalValue;
window.getGlobalValue = getGlobalValue;
}());
// app.js
function codeAddress(address) {
geocoder.geocode({ 'address': address},
function(response, status) {
if (status == 'OK')
{
var senddata = $.get('/myurl',{params}, function (data){ setGlobalValue('codeAddress', data) });
} else {
}
});
}
// get value here
console.log(getGlobalValue('codeAddress'));
by this way we can track all the global values by searching for setGlobalValue since this is the only way to set "global" value context.
You can just wrap everything inside self invoked function (it's closures but it's simple)
(function(){...})() means function will call itself, that's all.
Best practice is when you wrap every .js file inside this self invoked function so it's not global.
Your code:
(function(){
var glo_var;
function callback(data){
glo_var = data;
}
function codeAddress(address) {
geocoder.geocode({ 'address': address},
function(response, status) {
if (status == 'OK')
{
var senddata = $.get('/myurl',{params}, function (data){ callback(data) });
} else {
}
});
}
})();
Avoid global variables or minimize the usage of global variables in JavaScript. This is because global variables are easily overwritten by other scripts. Global variables are not bad and not even a security concern, but it shouldn’t overwrite the values of another variable.
On the usage of more global variables in our code, it may lead to a maintenance issue. Let’s say we added a variable with the same name. In that case, get ready for some serious bugs.
To avoid the usage of global variables, use the local variables and wrap your code in closures. You can also avoid this by wrapping the variables with json:
var wrapperDemo= {
x:5,
y:function(myObj){
}
};
Above, if you want to call x, then call it using:
wrapperDemo.x
Which would return 5 for you...

Why does my javascript geocode function return blank results?

I hope you can help. Ive made a function that receives a lnglat point object and returns just the town. I can get it to print the correct town in the console.log but it doesnt return the data back from the function.
I know its going to be a basic error but can someone have a look at the code and let me know please.
Thanks in advance.
function getTownFromPoint(point){
var geocoder ;
var geocoder = new google.maps.Geocoder();
var townset = false;
mylocation = "";
geocoder.geocode({latLng: point}, function(results, status) {
if (status == google.maps.GeocoderStatus.OK) {
if (results[0]) {
var components=results[0].address_components;
for (var component=0;component<(components.length);component++){
if(components[component].types[0]=="country" & !townset){
mylocation = components[component].long_name;
}
if(components[component].types[0]=="postal_code" & !townset){
mylocation = components[component].long_name;
}
if(components[component].types[0]=="locality"){
mylocation = components[component].long_name;
townset = true;
console.log(mylocation);
}
}
}
}
});
return(mylocation);
}
Geocoder is asynchronous - You are returning the value before you value is set.
It has been answered here:
Waiting for google maps geocoder?
That's because geocode is an ajax call and they are asynchronous. You need to provide a callback function or use a a promise to get the data. Since you're not using jQuery by the looks of your question a callback might be easier:
Here's a simplified version of your code with an example of how the callback function can be used:
// we pass in the callback as a function parameter
function getTownFromPoint(point, callback) {
geocoder.geocode({
latLng: point
}, function (results, status) {
var myLocation = results.myLocation;
// then we call the callback with the myLocation variable
callback(mylocation);
}
);
}
// call the function with the point variable and the function
// we will use for our callback
getTownFromPoint(1.223, function (myLocation) {
console.log(myLocation)
});
The problem you're facing is that you're treating the geocoder.geocode function as immediately completing before you do the return result. What's really happening is that the geocoder.geocode is triggered, then you get an immediate return of result. Because the asynchronous result has most likely not returned, your result is empty. Think of the geocoding result as a push, not a pull. The storeResult function, not shown, is whatever code you need to do to save the information. Because you're combining a result with an error string, you have to handle that in your storeResult function. As an alternative, you can have a status in the result that indicates succcess or failure.
store the result:
storeResult(result);
use this function inside your function. this will solve your problem

Jasmine test for object methods

Hello I am a bit inexperienced in Javascript and jasmine and am attempting to write some simple tests for a javascript object.
var googleMap = {
geoCode: function(code, fn) {
var geocoder = new google.maps.Geocoder();
geocoder.geocode({
'address': code
}, function(results, status) {
if (status == google.maps.GeocoderStatus.OK) {
fn(results[0].geometry.location);
} else {
alert("ALL IS LOST");
}
})
},
render: function(LatLng) {
var mapOptions = {
zoom: 8,
center: LatLng
}
map = new google.maps.Map(document.getElementById('map-canvas'), mapOptions);
var marker = new google.maps.Marker({
map: map,
position: LatLng
});
}
};
-Here is my jasmine script; I am attempting to make sure that the render function is being called whenever we run the geocode function...
describe("Testing of googleMap.js", function() {
it("Test GeoCode", function() {
var input = "Columbus";
spyOn(googleMap, 'render');
googleMap.geoCode(input, googleMap.render);
expect(googleMap.render).toHaveBeenCalled();
});
});
whenever I run this my specRunner returns an error claiming the expect claimed spy render to have been called, I am a bit baffled as to why it wouldn't have been.... is this to do with how I am attempting to set up my spy, or is the javascript object I made impossible to test. I am having trouble finding similar examples to work from.
The method .geocode of the object google.maps.Geocoder is asynchronous.
Therefore at the time of checking if the callback function was executed, you haven't got your response back and this means your function will not be called.
If all you want is to test if the function will be called then you can do something like the following:
describe("Testing of googleMap.js", function() {
it("Test GeoCode", function(done) {
var input = "Columbus";
googleMap.geoCode(input, function() {
// here you know that your callback function was called
done();
});
});
});
Or if you want still use a mock function you can do this (with jasmine 2.0):
describe("Testing of googleMap.js", function() {
it("Test GeoCode", function(done) {
var input = "Columbus";
var callbackSpy = jasmine.createSpy("callback").and.callFake(function() {
done();
});
googleMap.geoCode(input, callbackSpy);
});
});
With the given code blocks you get an timeout error if the callback function doesn't get called:
Error: Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.
Here is a demo (with an mocked google geocoder).
This is how you can fix your code. The question is: Is it reasonable to make a test like this?
In unit tests you should test your code separated from other resources (like google geocoder). So imho in this situation it is better to mock the geocoder object and test if the mock was called with the right arguments.

Methods not completing before the next one is executed (javascript jquery google maps v3 api)

I have been trying to place multiple map markers from lat lng coords in an xml file.
this is my code
var stockistName=[];
var stockistAddress=[];
var stockistLat=[];
var stockistLng=[];
function getAddresses(country){
$.get('xml/stockists/'+country+'.xml', function(d){
alert("file Opened");
$(d).find('stockist').each(function(){
$stockist = $(this);
stockistName.push($stockist.attr("name"));
stockistAddress.push($stockist.attr("address"));
stockistLat.push($stockist.attr("lat"));
stockistLng.push($stockist.attr("lng"));
});
});
placeMarkerArray();
}
function placeMarkerArray(){
alert("this is element 0"+stockistName[0]);
for(var i=0; i<stockistName.length;i++){
alert("in the function got addresses");
//alert("inloop"+i+"");
var newLatLng = new google.maps.LatLng(stockistLat[i], stockistLng[i]);
//alert("making marker lat lng:"+newLatLng+"")
if (markerArray[i] != undefined){
markerArray[i].setPosition(newLatLng);
}
else{
markerArray[i] = new google.maps.Marker({
position: newLatLng,
map: map,
icon:'images/icons/MAP/van.png'
});
}
}
}`
I have been reading about callback functions but can't seem to grasp the idea. I always thought that things would be executed in the order you write them. If anyone could give me any direction that would be great.
I think things are usually clearest with an example, so:
// define a function that accepts a function as a parameter and calls
// this function after one second asynchronously.
function executeActionDelayed (action) {
window.setTimeout(function () {
action();
}, 1000);
}
// Now call the above function with an anonymous function as the parameter.
executeActionDelayed(function () {
console.log("Execute action delayed called.");
});
console.log("End, or is it?");
As you can see in this example, the "End, or is it?" was actually logged to the console before "Execute action delayed called.". This is
because window.setTimeout is asynchronous. The function I created executeActionDelayed is using a callback!
This is similar to what you're doing with jQuery's get function:
// This function is being passed in as a parameter and will be
// called only when the response has been received.
$.get('xml/stockists/'+country+'.xml', function(d){
// stuff removed.
});
So what does this mean for you?
You have to call your function that uses the data within the success callback that you pass in:
function getAddresses(country){
$.get('xml/stockists/'+country+'.xml', function(d){
alert("file Opened");
$(d).find('stockist').each(function(){
$stockist = $(this);
stockistName.push($stockist.attr("name"));
stockistAddress.push($stockist.attr("address"));
stockistLat.push($stockist.attr("lat"));
stockistLng.push($stockist.attr("lng"));
});
placeMarkerArray();
});
}
The $.get method needs to download a file which can take some time. This is why you give it a callback function as a second parameter, it will be called later, when the download is finished.
Meanwhile, your JS keeps executing in the order you wrote it. Which is why after the get call, which starts downloading the file, the placeMarkerArray function is executed.
You should just move the placeMarkerArray() call inside the $.get callback.
Also, you should improve your code indentation, it will make things much clearer.

Javascript callback function and parameters [duplicate]

This question already has answers here:
Pass an extra argument to a callback function
(5 answers)
Closed 6 years ago.
I want to something similar to this:
function AjaxService()
{
this.Remove = function (id, call_back)
{
myWebService.Remove(id, CallBack)
}
function CallBack(res) {
call_back(res);
}
}
so my calling program will be like this:
var xx = new AjaxService();
xx.Remove(1,success);
function success(res)
{
}
Also if I want to add more parameters to success function how will I achieve it.
Say if I have success function like this:
var xx = new AjaxService();
//how to call back success function with these parameters
//xx.Remove(1,success(22,33));
function success(res,val1, val2)
{
}
Help will be appreciated.
Use a closure and a function factory:
function generateSuccess (var1,var2) {
return function (res) {
// use res, var1 and var2 in here
}
}
xx.Remove(1,generateSuccess(val1,val2));
What you're passing here is not the generateSuccess function but the anonymous function returned by generateSuccess that looks like the callback expected by Remove. val1 and val2 are passed into generateSuccess and captured by a closure in the returned anonymous function.
To be more clear, this is what's happening:
function generateSuccess (var1,var2) {
return function (res) {
// use res, var1 and var2 in here
}
}
var success = generateSuccess(val1,val2);
xx.Remove(1,success);
Or if you prefer to do it inline:
xx.Remove(1,(function(var1,var2) {
return function (res) {
// this is your success function
}
})(val1,val2));
not as readable but saves you from naming the factory function. If you're not doing this in a loop then Xinus's solution would also be fine and simpler than my inline version. But be aware that in a loop you need the double closure mechanism to disconnect the variable passed into the callback function from the variable in the current scope.
You can pass it as anonymous function pointer
xx.Remove(1,function(){
//function call will go here
success(res,val1, val2);
});
one way to do this:
function AjaxService {
var args_to_cb = [];
this.Remove = function (id, call_back, args_to_callback_as_array) {
if( args_to_callback_as_array!=undefined )
args_to_cb = args_to_callback_as_array;
else
args_to_cb = [];
myWebService.Remove(id, CallBack)
}
function CallBack(res) {
setTimeout( function(){ call_back(res, args_to_cb); }, 0 );
}
}
So you can use it like this:
var service = new AjaxService();
service.Remove(1,success, [22,33]);
function success(res,val1, val2)
{
alert("result = "+res);
alert("values are "+val1+" and "+val2);
}
I usually have the callback execute using a setTimeout. This way, your callback will execute when it gets the time to do so. Your code will continue to execute meanwhile, e.g:
var service = new AjaxService();
service.remove(1, function(){ alert('done'); }); // alert#1
alert('called service.remove'); // alert#2
Your callback will execute after alert#2.
Of course, in case of your application, it will happen so automatically since the ajax callback itself is asynchronous. So in your application, you had better not do this.
Cheers!
jrh

Categories