1

I have a question about geolocation and the javascript variable scope. Ive been toying around with it a bit but I'm a php guy, js is not my best language. Im making a google map and I want to set the users coords as the map center and set a zoom. If the request fails/is denied then I'm hitting a web service to get their approximate coords from the IP. I would like to get the position/zoom back from the getLocation() function, but I can't get the information out of navigator.geolocation.getCurrentPosition()

I've tried returning the object from within the error and position functions but it looks like its not set up for that, I can't get anything back but an error code. I thought I might be able to set the var and then overwrite it but I can't get it to return anything but undefined, I still don't 100% understand scope and hoisting enough to find the solution.

the geolocFail() works as expected, returning the object correctly. Since its called from the error function within navigator.geolocation.getCurrentPosition - I can't get the information out of there.

      function getLocation() {

       var mapLocation;

        if (navigator.geolocation) {
              var location_timeout = setTimeout("geolocFail()", 10000);
              navigator.geolocation.getCurrentPosition(
                function(position) {
                    clearTimeout(location_timeout);
                    mapLocation = {position: position.coords.latitude + "," + position.coords.longitude,  zoom:10};
                }, function(error) {
                    clearTimeout(location_timeout);
                        switch(error.code) {
                            case error.PERMISSION_DENIED:
                                alertDiv.innerHTML="<p class='text-danger'>User denied the request for Geolocation.</p>";
                                break;
                            case error.POSITION_UNAVAILABLE:
                                alertDiv.innerHTML="<p class='text-danger'>User Location information is unavailable.</p>";
                                break;
                            case error.TIMEOUT:
                                alertDiv.innerHTML="<p class='text-danger'>The request to get user location timed out.</p>";
                                break;
                            case error.UNKNOWN_ERROR:
                                alertDiv.innerHTML="<p class='text-danger'>An unknown error occurred.</p>";
                                break;
                        }
                    mapLocation =  geolocFail(); 
                });
        } 
        else {
            // Fallback for no geolocation
            alertDiv.innerHTML="<p class='text-danger'>Geolocation is not supported by this browser.</p>";
                    mapLocation =  geolocFail();
        }

      }



  function geolocFail(){
        //fallback to webservice
        var xmlhttp;
        if (window.XMLHttpRequest) {
            // code for IE7+, Firefox, Chrome, Opera, Safari
            xmlhttp = new XMLHttpRequest();
        } else {
            // code for IE6, IE5
            xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
        }
        xmlhttp.onreadystatechange = function(){
          if (xmlhttp.readyState==4 && xmlhttp.status==200){
              if (typeof callback == "function") {
                      // apply() sets the meaning of "this" in the callback
                      callback.apply(xmlhttp);
                    }
          }
        }
        xmlhttp.open("GET","/map/determineLocation?t=" + Math.random(),true);
        xmlhttp.send(); 
        if(this.responseText){
           var mapLocation = {position: this.responseText,  zoom:10}
        }
        else{
          var mapLocation = {position: "40.0000,-100.0000",  zoom:4};
        }
        return mapLocation;
      }
Severian
  • 427
  • 3
  • 18

1 Answers1

3

Your specific problem

Your callbacks are asynchronous. Although they are defined in one place, you don't when they will actually be triggered by the remote service.

The best thing to do would be to wrap your response code up in a function which you then call within the callback to handle the response and design this so that it isn't reliant on any sort of procedural flow.

Use a function to grab and respond to the location data when it returns rather than trying to assign it to a variable which you then use try to use immediately after the call to navigator.geolocation.getCurrentPosition.

This (simplified) code works:

function respond_to_received_data(position) {
  console.log(position);
}
function respond_to_error_data(error) {
  console.log(error);
} 

mapLocation = navigator.geolocation.getCurrentPosition(
  function(position) {
    respond_to_received_data(position);
  },
  function(error) {
    respond_to_received_data(error);
  }
);

fiddle here

Please see the following community wiki resource for more on asynchronicity:

However, FYI regarding scope and hoisting:

JavaScript Scope

JavaScript has two scopes, global scope and function scope. Any variable declared (with the var keyword) outside of a function has global scope, i.e. it will available anywhere within you webpage. Any variable declared within a function (again, with the var keyword) will only be available to that function and in order to be used outside of that function has to be returned,either directly, or as part of a returned function expression.

This explains the very common pattern in Javascript to wrap code in an immediately called and expendable anonymous function:

(function() { /* code in controlled scope */ }());

Todd Motto will help you learn more.

Hoisting in Javascript

Within any given scope javascript first gathers the variables which are used within the scope and defines them first, either with a value, or if one cannot be deduced at this point with the undefined value. For more on hoisting, see the Mozilla Dev Network description.

An example to help visualise the above

A combination of these two behaviours can be illustrated with a for loop in the middle of a scope. The increment variable from the for loop will be accessible as undefined at the start of the scope and will still be available at the end of the scope, set to the last value it had during loop execution.

// globally defined
var glob = 'hi';
(function() {
  var j = 4;
  console.log(j); // => 4
  console.log(glob); // => 'hi'
  console.log(inLoop); // => undefined

  // NB if this was set to anything not used in the scope then execution would be halted by an 'undefined' error in the code
  console.log(i); // => undefined
  for (var i = 0; i < 10; i ++) 
  { 
    var inLoop = 'xyz';
  }

  console.log(i); // => 10
  console.log(inLoop); // => 10
}());
console.log(glob); // => 'hi'

fiddle here

PS the new version of JS has the let keyword that defines a variable for what would be known as 'block scope' or like PHP :-)

Community
  • 1
  • 1
Toni Leigh
  • 4,830
  • 3
  • 22
  • 36
  • Toni, Thanks for the time you took to answer this question, it is truly appreciated. However, even using functions like this, I cannot pass the values I retrieve from them (the coordinates) back to the main function. So If i set var x = foo within respond_to_received_data(), and then return it at the end of the function, I still need to get that var back up to the original function getLocation(). – Severian Feb 18 '15 at 21:38
  • no, but you can then use the values in some way, the main function is called and that's that, it's execution is finished before the asynchronous call is complete (it could take several seconds for example), you'll have to act on the data in some way when you get using other functions – Toni Leigh Feb 18 '15 at 21:42