I have 2 js files, 1 is to store the location data in an object and the other is the event handler for the click events and such.
This works:
var Geology = {
coords: {},
error: '',
setPosition: function (position) {
Geology.coords = position.coords;
// DEBUG
for (var prop in Geology.coords)
console.log(prop + ': ' + Geology.coords[prop]);
},
setError: function (error) {
Geology.error = error;
}
};
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(Geology.setPosition,Geology.setError,{timeout:10000});
}
But i want to be able to update the location data if necessary by a click or timer. Any time i try to do that it doesn't assign the vars to Geology like it does initially.
like this:
jQuery(document).ready(function($){
$('.toggle').click(function(){
//- get geo location if available
if (typeof Geology === 'object') {
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(Geology.setPosition,Geology.setError,{timeout:10000});
}
}
});
});
I don't get errors in the console but it just fails and does not run the callback. However, it does run the getCurrentPosition method because I've tested to see if it makes it through the conditionals.
I think you need to set the maximum_age parameter, otherwise the results will be cached forever :
navigator.geolocation.getCurrentPosition(
geo_success,
geo_error,
{ maximumAge:60000 } // set to 1min (in ms)
);
You also have two other parameters : enableHighAccuracy and timeout
More info: https://developer.mozilla.org/en-US/docs/Using_geolocation
EDIT : I just found this, it's probably related to your issue navigator.geolocation.getCurrentPosition sometimes works sometimes doesn't
Related
I am working on a pothole map, and am on the stage where I load pothole data from the server and put it on the map as markers. Since data retrieval and the APIs that my app relies on (Roads, Geolocation, etc.) are asynchronous, my code ended up refactored to run asynchronously. I refactored the code blocks that added the markers to the map to this:
/* put all potholes on the map
* Parameters:
* • callback : the function to call next
*/
function addPotholeMarkers(callback)
{
var DEBUG = false;
// guarantee that callback is function
if ((callback) && (typeof(callback) !== 'function')) throw new TypeError('callback is something, but not a function. Thrown from addPotholeMarkers().');
// add all the markers for them to the map
async.waterfall([
function(cb) {
async.eachOf(potholeAddresses, function(value, key) {
async.eachOf(value, function (v, k) {
addPotholeMarker(v, false);
})
})
if (cb && typeof cb === 'function') cb(null);
}, function(cb) {
async.eachOf(potholeCoordinates, function(value, key) {
async.eachOf(value, function(v, k) {
async.setImmediate(function() { addPotholeMarker(v); }); // This came from
})
})
}], function(err, results) {
console.log('trying to center map');
reCenterMap();
console.log('Map recentered');
if (callback) {
callback(err);
}
});
}
and addPotholeMarker() looks something like:
/* code was initially obtained from https://developers.google.com/maps/documentation/roads/inspector */
/* Adds marker to map.
* Parameters :
* • potholeData : a PotholeData (or PotholeDataFromCoords) object
* • snappedToRoad: boolean
* Returns :
* • the marker that was added to the map, or null if arguments invalid
*/
function addPotholeMarker(potholeData, snappedToRoad) {
// make sure potholeState is either falsy or contains iconURL string
if ((!potholeData.potholeState) || ((potholeData.potholeState) && (potholeData.potholeState.iconURL === undefined))) throw new Error('invalid potholeData');
// let's make sure to snap this to road if it isn't already...
var coords = new GPSCoordinates(potholeData.lat, potholeData.lng);
if (!snappedToRoad)
{
var potholeMarker = 'a garbage return value';
getRoadCoordinates(coords).done(function(response) {
var coords = response.snappedPoints[0].location;
potholeData.lat = coords.latitude;
potholeData.lng = coords.longitude;
return (potholeMarker = addPotholeMarker(potholeData, true));
/* potholeMarker = addPotholeMarker(potholeData, true);
return potholeMarker;*/
});
return;
//return potholeMarker;
}
var marker = new google.maps.Marker({
position: coords,
title: coords.toString(),
map: map,
opacity: 0.5,
icon: ((potholeData.potholeState.iconURL !== undefined) ? potholeData.potholeState.iconURL : PURPLE_MARKER)
});
// make marker have effect when mouseout,mouseover
marker.addListener('mouseover', function(mouseEvent) {
marker.setOpacity(1.0);
});
marker.addListener('mouseout', function(mouseEvent) {
marker.setOpacity(0.5);
});
var infoWindow = createInfoWindow(potholeData);
// save infoWindow for later reference
infoWindows[statesMap.get(getPotholeStateFor(potholeData.potholeState))].push(infoWindow);
// on click of marker, show infoWindow
marker.addListener('click', function(mouseEvent) {
infoWindow.open(map, marker);
});
// add this to potholeMarkers
potholeMarkers[statesMap.get(getPotholeStateFor(potholeData.potholeState))].push(marker);
return marker;
}
This app is hosted on Google Apps Script (you'll need Google account to run this), and uses the client-side async library
This code, when ran successfully, is supposed to re-center the map at the position-average of all the markers. reCenterMap() works as it should, so I omitted it in attempt at MVCE.
When I ran the code
During any tick of the asynchronous loop, the members of the potholeCoordinates object (which is an Object<Array<PotholeData> >) appear empty. How to fix this?
After showing a friend this problem, contemplating his advice on this, and staying up last night for a few hours to work on it myself, I made the following changes:
not related to this problem, but
I changed the index statesMap.get(getPotholeStateFor(potholeData.potholeState)) to statesMap.get(getPotholeStateFor(potholeData)). Turns out that I gave getPotholeStateFor() the wrong object, which made it return the wrong state.
I changed the signature of addPotholeMarker(potholeData, snappedToRoad) to addPotholeMarker(potholeData, snappedToRoad, callback); /* because apparently I forgot that, with async functions, callbacks must be passed to the lowest level functions and invoked there, with signature callback(err, results) or something similar */
Inside addPotholeMarker() I made sure to use callback, but in a modular way:
if (callback) return callback(null, potholeMarker);
return potholeMarker;
/* I applied this change to the if (!snappedToRoad), but that if-statement is still broken: it will return before it finishes its task of appending to array, and return callback(null, potholeMarker) will cause the callback to be invoked twice. I may end up having to refactor this whole function, especially to append to potholeMarkers (which, btw, is global) after this function (it will be the callback to this) */
The innermost async loop in addPotholeMarkers() got changed from :
async.eachOf(value, function(v, k) {
async.setImmediate(function() { addPotholeMarker(v); });
})
to
async.eachSeries(value, function(pothole, fn) {
addPotholeMarker(pothole,
true,
//pothole.isSnappedToRoad(),
fn);
})
NOTE pothole.isSnappedToRoad() is commented out because it returns false, and addPotholeMarker() doesn't work right in the main call stack with the second parameter false. Also, it should return true, but it isn't because this type of error happened in function prior to addPotholeMarkers(); /* I'm going to fix that next! */
I'm working on a little web application which uses GPS. It uses Javascript to accomplish this. What I'm doing:
Defining an global array "positionCoords" with 2 empty indexes
(latitude / longitude).
On document ready, I am requesting latitude / longitude with the function "getGeoLocation()".
After the request, I am doing an simple console.log of the array. The array indexes are still empty.
Executing console.log(positionCoords) in the webbrowser console, gives me the correct values.
Could this has something to do with asynchronously? I'm quite new to this feature, any help would be appreciated!
// global variables
var positionCoords = {};
positionCoords['latitude'] = null;
positionCoords['longitude'] = null;
/*
on document ready, excecute
*/
$(document).ready(function() {
getGeolocation();
console.log(positionCoords);
});
/*
check if geolocation is supported and allowed, get coordinates
*/
function getGeolocation() {
if (navigator.geolocation) {
// geolocation is available
navigator.geolocation.getCurrentPosition(
function(position) {
// get coordinates
positionCoords['latitude'] = position.coords.latitude;
positionCoords['longitude'] = position.coords.longitude;
return true;
},
function(error){
handleGeolocationErrors(error);
return false;
}
);
} else {
// geolocation is not available
console.log("Browser does not support geolocation services.");
return false;
}
}
DEMO with Google Map
Since it is ASYNC call, you need to pass function-callback to getGeolocation, then , you will call it like the follwong :
ON Calling :
$(document).ready(function() {
getGeolocation(function(coords){
console.log(coords);
//====The resof your code which dependent on `coords` SHALL be here
//......
},function(err){console.log(err)});
});
To be able to call it like the above , you need to modify the implementation of getGeolocation by passing it two parameters :
Function Callback onSuccess.
Function Callback onError
Implementation :
/*
check if geolocation is supported and allowed, get coordinates
*/
function getGeolocation(fnsuccess,fnerror) {
if (navigator.geolocation) {
// geolocation is available
navigator.geolocation.getCurrentPosition(
function(position) {
if(typeof fnsuccess==='function'){
fnsuccess.call(null,position.coords); //--Call CallBack On Success
}
},
function(error){
if(typeof fnerror ==='function'){
fnerror.call(null,error); //--Call CallBack On Error
}
}
);
} else {
// geolocation is not available
console.log("Browser does not support geolocation services.");
return false;
}
}
DEMO :
the DEMO will ask you to share your location , if you accept , check the console of your browser , you will get Coords as you expect
DEMO with Google Map
I can return a value if I send a sync message:
// frame script
var chromeBtnText = sendSyncMessage("getChromeToolbarButtonText");
if (chromeBtnText == 'blah') {
alert('tool is blah');
}
// chrome script
messageManager.addMessageListener("getChromeToolbarButtonText", listener);
function listener(message) {
return document.getElementById('myChromeToolbarButton').label.value;
}
How do I achieve this with a callback with sendAsyncMessage?
I was hoping to do something like:
// frame script
function myCallback(val) {
var chromeBtnText = val;
if (chromeBtnText == 'blah') {
alert('tool is blah');
}
}
var chromeBtnText = sendAsyncMessage("getChromeToolbarButtonText", null, myCallback);
There is no callback for replies. In fact, there is no reply at all. The return value from the chrome message listener is simply ignored for async messages.
To do fully async communication, you'd have to send another message containing the reply.
Frame script
addMessageListener("getChromeToolbarButtonTextReply", function(message) {
alert(message.data.btnText);
});
sendAsyncMessage("getChromeToolbarButtonText");
Chrome
messageManager.addMessageListener("getChromeToolbarButtonText", function(message) {
var btnText = document.getElementById('myChromeToolbarButton').label.value;
// Only send message to the frame script/message manager
// that actually asked for it.
message.target.messageManager.sendAsyncMessage(
"getChromeToolbarButtonTextReply",
{btnText: btnText}
);
});
PS: All messages share a namespace. So to avoid conflicts when another piece of code wants to use the same name getChromeToolbarButtonText, you better choose a more unique name in the first place, like prefixing your messages with your add-on name my-unique-addoon:getChromeToolbarButtonText or something like that. ;)
I was also hoping to do something similar:
messageManager.sendAsyncMessage("my-addon-framescript-message", null, myCallback);
I'm going the other direction so the myCallback would be in chrome but it's exactly the same principle.
I'd used similar approaches to #Noitidart and #nmaier before but in this new case I wanted to bind to some local data so myCallback can behave differently based on the application state at the time the first message was sent rather than at the time the callback is executed, all while allowing for the possibility of multiple message round-trips being in progress concurrently.
Chrome:
let someLocalState = { "hello": "world" };
let callbackName = "my-addon-somethingUnique"; // based on current state or maybe generate a UUID
let myCallback = function(message) {
messageManager.removeMessageListener(callbackName, myCallback);
//message.data.foo == "bar"
//someLocalState.hello == "world"
}.bind(this); // .bind(this) is optional but useful if the local state is attached to the current object
messageManager.addMessageListener(callbackName, myCallback);
messageManager.sendAsyncMessage("my-addon-framescript-message", { callbackName: callbackName } );
Framescript:
let messageHandler = function(message) {
let responseData = { foo: "bar" };
sendAsyncMessage(message.data.callbackName, responseData);
};
addMessageListener("my-addon-framescript-message", messageHandler);
There's a real-world example here: https://github.com/luckyrat/KeeFox/commit/c50f99033d2d07068140438816f8cc5e5e290da9
It should be possible for Firefox to be improved to encapsulate this functionality in the built-in messageManager one day but in the mean-time this approach works well and with a surprisingly small amount of boiler-plate code.
in this snippet below. i add the callback before sendAsyncMessage('my-addon-id#jetpack:getChromeToolbarbuttonText'... as i know it will send back. Then I remove it after callback executes. I know I don't have to but just to kind of make it act like real callback, just to kind of show people, maybe it helps someone understand.
Frame:
/////// frame script
function CALLBACK_getChromeToolbarButtonText(val) {
removeMessageListner('my-addon-id#jetpack:getChromeToolbarButtonTextCallbackMessage', CALLBACK_getChromeToolbarButtonText); //remove the callback
var chromeBtnText = val;
if (chromeBtnText == 'blah') {
alert('tool is blah');
}
}
addMessageListener('my-addon-id#jetpack:getChromeToolbarButtonTextCallbackMessage', CALLBACK_getChromeToolbarButtonText); //add the callback
var chromeBtnText = sendAsyncMessage("my-addon-id#jetpack:getChromeToolbarButtonText", null);
Chrome:
////// chrome script
messageManager.addMessageListener("my-addon-id#jetpack:getChromeToolbarButtonText", listener);
function listener() {
var val = document.getElementById('myChromeToolbarButton').label.value;
sendAsyncMessage('my-addon-id#jetpack:getChromeToolbarButtonTextCallbackMessage',val);
}
how can I get latitude and longitude outside created object?
I've made new object of Ext.util.Geolocation, updated it, and now I'm trying to get Latitude and Longitude values outside the object, but it shows me 'null'.
Code:
var geo = Ext.create('Ext.util.Geolocation', {
autoUpdate: false,
listeners: {
locationupdate: function(geo) {
//alert('New latitude: ' + geo.getLatitude());
},
locationerror: function(geo, bTimeout, bPermissionDenied, bLocationUnavailable, message) {
if(bTimeout){
alert('Timeout occurred.');
} else {
alert('Error occurred.');
}
}
}
});
geo.updateLocation();
//geo.fireEvent('locationupdate');
alert(geo.getLatitude()); // it shows null
Thanks in advance.
It's probably because it takes some time to get your location.
You should try to get the latitude and the longitude within the locationupdate callback function.
If you want to access it outside, just make sure that geo exists
if (geo) { alert(geo.getLatitude()); }
Hope this helps
This is just a timing issue - obtaining the location is not synchronous, therefore you can not access it outside the locationupdate callback function, at least until it has been initialized.
You should do whatever you need to do inside that callback (e.g. call another function passing in the latitude if it needs)...
...
listeners: {
locationupdate: function(geo) {
yourCallbackFunction(geo.getLatitude());
},
...
If you really need to use it outside that callback, then in the worst case you can do something like:
var latitude; //will store latitude when ready..
var geo = Ext.create('Ext.util.Geolocation', {
autoUpdate: false,
listeners: {
locationupdate: function(geo) {
latitude = geo.getLatitude();
}
}
});
geo.updateLocation();
//waits until latitude exists then does something..
var untilLatThere = setInterval(function(){
if(latitude) {
alert(latitude); //alerts a valid value..
clearInterval(untilLatThere);
}
}, 100);
I have a function that calls the geolocator and i don't know how to test this function. I've tried spying on the geolocator and returning fake data but with no success, the original function is still used and so i would have to wait and i couldn't use mock data.
// this doesn't work
var navigator_spy = spyOn( navigator.geolocation, 'getCurrentPosition' ).andReturn( {
coords : {
latitude : 63,
longitude : 143
}
} );
How can I do this?
When you call the geolocation code, it looks like this:
navigator.geolocation.getCurrentPosition(onSuccess, onError);
This means that you're calling it and passing it functions:
function onSuccess(position) {
// do something with the coordinates returned
var myLat = position.coords.latitude;
var myLon = position.coords.longitude;
}
function onError(error) {
// do something when an error occurs
}
So, if you wanted to spy on it using jasmine returning a value, you'd want call the success function using the first argument of the original call like this:
spyOn(navigator.geolocation,"getCurrentPosition").andCallFake(function() {
var position = { coords: { latitude: 32, longitude: -96 } };
arguments[0](position);
});
If you wanted to make it look like an error was returned, you'd want to call the error function using the second argument of the original call like this:
spyOn(navigator.geolocation,"getCurrentPosition").andCallFake(function() {
arguments[1](error);
});
Edit to show full example:
This is the function you are using Jasmine to test:
function GetZipcodeFromGeolocation(onSuccess, onError) {
navigator.geolocation.getCurrentPosition(function(position) {
// do something with the position info like call
// an web service with an ajax call to get data
var zipcode = CallWebServiceWithPosition(position);
onSuccess(zipcode);
}, function(error) {
onError(error);
});
}
This would be in your spec file:
describe("Get Zipcode From Geolocation", function() {
it("should execute the onSuccess function with valid data", function() {
var jasmineSuccess = jasmine.createSpy();
var jasmineError = jasmine.createSpy();
spyOn(navigator.geolocation,"getCurrentPosition").andCallFake(function() {
var position = { coords: { latitude: 32.8569, longitude: -96.9628 } };
arguments[0](position);
});
GetZipcodeFromGeolocation(jasmineSuccess, jasmineError);
waitsFor(jasmineSuccess.callCount > 0);
runs(function() {
expect(jasmineSuccess).wasCalledWith('75038');
});
});
});
At this point, when you run the spec, it will tell you that your web service gave you the proper zip code for the latitude and longitude you supplied if your web service works properly.
Ah wait, maybe you HAVE to create the spy within your beforeEach block because Jasmine restores spies automatically after each test case. if you did something like:
var navigator_spy = spyOn( navigator.geolocation, 'getCurrentPosition' )
it("should stub the navigator", function() {
// your test code
});
the spy is already restored when you want to test it. Use this instead:
beforeEach(function() {
this.navigatorSpy = spyOn( navigator.geolocation, 'getCurrentPosition' )
});
it("should work now since the spy is created in beforeEach", function() {
// test code
});