Altering external variable from Google Maps' StreetViewService.getPanorama({},callback) method - javascript

I am running into trouble with this piece of code since I'm not sure how to fix it. I already asked this in the chats but couldn't figure out myself after some answers.
I want to get Panorama from StreetViewService using the method from the Google Maps javascript API from a StreetViewService getPanorama() method.
The method receives a literal with the coordinates and a radius, and a callback function that receives 2 parameters: data and status.
in that callback you check wether the service returns some images for street view or not, in which case you do one thing or another.
It seems the callback is executed asynchronously or the getPanorama method, executing some kind of ajax behind the scenes.
I'm pasting the code below, but first I explain my intentions. I need to return from one method I made inside a literal that is inside a self made library wether the the request has valid images for that requested coordinates or not by setting a variable to true or false and then at the end returning that value. However, even if I set the variable to true inside that callback anonymous function, when the variable returns, it always has it's initial value without it not being changed.
Here the code. Not everything, just the essential code.
Then my intention is to used the boolean returned to know if I have to switch one button active for some kind of job or not or do some things or not depending of if it returned true or false, Change some style etc as well.
I'd appreciate if you could change my code in a way it could be done. I was told about a callback solution or wrapping it into a promise. However I don't know how to do it. I used promisses in jquery but not in vanilla javascript. I'd like to see how the callback solution could be made as well with this code.
//Library not show for shortenning the example.
streetView: { //This is inside a library
valid_request: false,
event_key: null,
panorama: null,
setStreetView: function(coords, element) {
libMapa.streetView.valid_request = false; // Initialize the value again.
let sv_service = new google.maps.StreetViewService();
var latlng = coords;
sv_service.getPanorama({ // This is a method from Google Map Javascript API.
location: latlng,
radius: 50
}, function(data, status) {
if (status === google.maps.StreetViewStatus.OK) {
if (!libMapa.streetView.panorama) {
libMapa.streetView.panorama = new google.maps.StreetViewPanorama(element);
libMapa.streetView.panorama.setEnableCloseButton(true);
}
libMapa.streetView.panorama.setPano(null);
libMapa.streetView.panorama.setPano(data.location.pano);
libMapa.streetView.panorama.setVisible(true);
libMapa.streetView.valid_request = true;
} else {
alert("No images for this place");
// Otherwise variable stays as FALSE.
}
});
return libMapa.streetView.valid_request;
}
}
/****************************************/
//OUTSIDE THE LIBRARY IN INDEX.HTML
var sv_valid = libMapa.streetView.setStreetView(coords, div_mapa);
//sv_valid still shows false even if it should return true.
if (sv_valid) {
// "pressed" is to control a button as if it was a switch by activatinc and deactivating it.
pressed = false; // It always ends up being false even when it should return true.
element.style.cursor = "default";
libMapa.mapa.unByKey(libMapa.streetView.event_key);
}

Use a callback to do some action or modify some variable after the AJAX call to getPanorama() (and by proxy, setStreetView()) is complete.
Modify setStreetView() to accept a callback parameter and pass it along to getPanorama().
streetView {
// streetView variables etc.
setStreetView: function(coords, element, callback) {
// get ready for the AJAX call etc.
sv_service.getPanorama({
// parameter object
}, function(data, status){
// The getPanorama() callback.
// Do stuff with the data/status here,
// then call the callback function that you passed in to setStreetView.
// You can send data along (e.g. the result of the AJAX call) to the callback.
callback(someResult);
});
}
}
// somewhere else, where you're calling streetView.setStreetView()
var someVar = "foo";
streetView.setStreetView(someCoords, someElement, function(eventualResult){
someVar = eventualResult;
});
Here's a small example: https://jsfiddle.net/_jered/pgftxgf3/
I also highly suggest you do some research on AJAX, asynchronous JavaScript, and callbacks.

Related

mxgraph infinite loops on apply

I am extending mxgraph delete control example to add delete like controls to nodes which are generated dynamically in my graph. The source code for the example is available here
The problem is in this part of the code -
// Overridden to add an additional control to the state at creation time
mxCellRendererCreateControl = mxCellRenderer.prototype.createControl;
mxCellRenderer.prototype.createControl = function(state)
{
mxCellRendererCreateControl.apply(this, arguments);
var graph = state.view.graph;
if (graph.getModel().isVertex(state.cell))
{
if (state.deleteControl == null)
mxCellRendererCreateControl.apply inside the overridden call back of createControl seems to work as intended (calls the original function before creating additional controls) with the initial state of the graph on load. But, once I add nodes dynamically to the graph and the callback is invoked by mxgraph's validate/redraw, the control goes into an infinite loop, where 'apply' function basically keeps calling itself (i.e, the callback).
I am a bit clueless because when I debug, the context(this) looks fine, but I can't figure out why instead of invoking the prototype method, it just keeps invoking the overridden function in a loop. What am I doing wrong?
It looks like you are not cloning your original function the right way, please try the following :
Function.prototype.clone = function() {
var that = this;
return function theClone() {
return that.apply(this, arguments);
};
};
Add that new method somewhere in your main code so it will available in the whole application, now you can change your code to :
// Overridden to add an additional control to the state at creation time
let mxCellRendererCreateControl = mxCellRenderer.prototype.createControl.clone();
mxCellRenderer.prototype.createControl = function(state) {
mxCellRendererCreateControl(state);
var graph = state.view.graph;
if (graph.getModel().isVertex(state.cell)) {
if (state.deleteControl == null) {
// ...
}
}
// ...
};
This should work if I understood your problem correctly, if it does not, please change the old function call back to the apply. Otherwise let me know if something different happened after the Function prototype change.
It seems that your overriding code is being called multiple times (adding a simple console.log before your overriding code should be enough to test this)
Try to ensure that the code that overrides the function only gets called once, or validate whether the prototype function is the original or yours.
Here is an example of how you can check if the function is yours or not
if (!mxCellRenderer.prototype.createControl.isOverridenByMe) {
let mxCellRendererCreateControl = mxCellRenderer.prototype.createControl;
mxCellRenderer.prototype.createControl = function(state) { /* ... */ };
mxCellRenderer.prototype.createControl.isOverridenByMe = true;
}
There are other ways, like using a global variable to check if you have overriden the method or not.
If this doesn't fix your issue, please post more about the rest of your code (how is this code being loaded/called would help a lot)

JavaScript callback firing out of sequence

I thought I'd finally figured out JavaScript callbacks as I found a use for one, but it is firing out of sequence.
In a Google Maps application I have a function which checks if the map is zoomed to the maximum level before allowing the user to store the (new) location of a marker.
The check function is:
function checkForMaxAccuracy(nextOperation) {
// Check at full zoom and alert if not
maxZoomService.getMaxZoomAtLatLng( newLocationMarker.getPosition(), function(response) {
var levelToZoomTo;
if (response.status != google.maps.MaxZoomStatus.OK) {
alert("Please ensure you are at maximum zoom to ensure most accurate placement possible.");
// continue after this alert as Mao could be fully zoomed but zoom service not reporting correctly
} else {
//alert("The maximum zoom at this location is: " + response.zoom);
if (response.zoom > map.getZoom()) {
alert("You must be zoomed in to the maximum to ensure most accurate placement possible before saving.\n\n Click [ Zoom here ] in theInfo Window to zoom to the maximum.");
// return after this error as Mao is definitely not fully zoomed
return;
}
// Call the update function to do the saving to the database
nextOperation();
}
});
}
Where nextOperation is the callback function.
This is called by two different functions, the first, and simplest (because it's not fully written yet) works perfectly:
function saveNewLocation() {
checkForMaxAccuracy(function () {
alert("save new");
});
}
If not fully zoomed I get the warning message displayed by the check function. If the map IS fully zoomed I get the alert 'save new' message displayed which is a place holder for the code to do the database update.
However, the same check function is also called from a more complete function. This other function is called when the user is updating the location of an existing marker.
This second function actually sets the onclick property of an HTML span object - effectively enabling and disabling the save control depending on what is going on.
In this function I am setting the onclick as follows:
document.getElementById('savePosition'+locationId).onclick = (state)?function(){checkForMaxAccuracy(doUpdate(locationId,"position"));}:null;
In this case, when I click the corresponding span element the doUpdate() function gets fired before the checkForMaxAccuracy() function.
(Apologies for the use of ternary operator.. I know if makes things a little difficult to read, but I'm leaving as is in my code in case I've got the bracketing or syntax slightly wrong here and that's what's causing my problems.)
My guess is I'm getting something fundamentally wrong in my 'understanding' of callback functions.
Can anyone explain what I'm doing wrong?
-FM
checkForMaxAccuracy(
doUpdate(locationId,"position")
);
will pass the returned value of doUpdate as an argument of checkForMaxAccuracy. In other words, you're not passing the callback function itself but its return value.
What you could do is:
checkForMaxAccuracy(
function() {
doUpdate(locationId,"position")
}
);

Extracting values from USGS real time water service

There must be something simple I am missing, but alas, I do not know what I do not know. Below is the code I have thus far for trying to get current streamflow conditions from the USGS.
// create site object
function Site(siteCode) {
this.timeSeriesList = [];
this.siteCode = siteCode;
this.downloadData = downloadData;
this.getCfs = getCfs;
// create reference to the local object for use inside the jquery ajax function below
var self = this;
// create timeSeries object
function TimeSeries(siteCode, variableCode) {
this.variableCode = variableCode;
this.observations = [];
}
// create observation object
function TimeSeriesObservation(stage, timeDate) {
this.stage = stage;
this.timeDate = timeDate;
}
// include the capability to download data automatically
function downloadData() {
// construct the url to get data
// TODO: include the capability to change the date range, currently one week (P1W)
var url = "http://waterservices.usgs.gov/nwis/iv/?format=json&sites=" + this.siteCode + "&period=P1W&parameterCd=00060,00065"
// use jquery getJSON to download the data
$.getJSON(url, function (data) {
// timeSeries is a two item list, one for cfs and the other for feet
// iterate these and create an object for each
$(data.value.timeSeries).each(function () {
// create a timeSeries object
var thisTimeSeries = new TimeSeries(
self.siteCode,
// get the variable code, 65 for ft and 60 for cfs
this.variable.variableCode[0].value
);
// for every observation of the type at this site
$(this.values[0].value).each(function () {
// add the observation to the list
thisTimeSeries.observations.push(new TimeSeriesObservation(
// observation stage or level
this.value,
// observation time
this.dateTime
));
});
// add the timeSeries instance to the object list
self.timeSeriesList.push(thisTimeSeries);
});
});
}
// return serialized array of cfs stage values
function getCfs() {
// iterate timeseries objects
$(self.timeSeriesList).each(function () {
// if the variable code is 00060 - cfs
if (this.variableCode === '00060') {
// return serialized array of stages
return JSON.stringify(this.observations);
}
});
}
}
When I simply access the object directly using the command line, I can access individual observations using:
> var watauga = new Site('03479000')
> watauga.downloadData()
> watauga.timeSeriesList[0].observations[0]
I can even access all the reported values with the timestamps using:
> JSON.stringify(watauga.timeSeriesList[0].observations)
Now I am trying to wrap this logic into the getCfs function, with little success. What am I missing?
I don't see anything in the code above that enforces the data being downloaded. Maybe in whatever execution path you're using to call getCfs() you have a wait or a loop that checks for the download to complete prior to calling getCfs(), but if you're simply calling
site.downloadData();
site.getCfs()
you're almost certainly not finished loading when you call site.getCfs().
You'd need to do invoke a callback from within your success handler to notify the caller that the data is downloaded. For example, change the signature of Site.downloadData to
function downloadData(downloadCallback) {
// ...
Add a call to the downloadCallback after you're finished processing the data:
// After the `each` that populates 'thisTimeSeries', but before you exit
// the 'success' handler
if (typeof downloadCallback === 'function') {
downloadCallback();
}
And then your invocation would be something like:
var watauga = new Site('03479000');
var downloadCallback = function() {
watauga.timeSeriesList[0].observations[0];
};
watauga.downloadData(downloadCallback);
That way, you're guaranteed that the data is finished processing before you attempt to access it.
If you're getting an undefined in some other part of your code, of course, then there may be something else wrong. Throw a debugger on it and step through the execution. Just bear in mind that interactive debugging has many of the same problems as interactively calling the script; the script has time to complete its download in the background before you start inspecting the variables, which makes it look like everything's hunky dory, when in fact a non-interactive execution would have different timing.
The real issue, I discovered through just starting over from scratch on this function, is something wrong with my implementation of jQuery.().each(). My second stab at the issue, I successfully used a standard for in loop. Here is the working code.
function getCfs() {
for (var index in this.timeSeriesList) {
if (this.timeSeriesList[index].variableCode === '00060'){
return JSON.stringify(this.timeSeriesList[index].observations);
}
}
}
Also, some of the stuff you are talking about #Palpatim, I definitely will have to look into. Thank you for pointing out these considerations. This looks like a good time to further investigate these promises things.

jQuery/javascript basic logic question

I am using a jQuery method $.getJSON to update data in some cascading drop down lists, in particular, a default value if there is nothing returned for the drop down, e.g. "NONE".
I just want some clarification to how my logic should go.
var hasItems = false;
$.getJSON('ajax/test.json', function(data) {
hasItems = true;
//Remove all items
//Fill drop down with data from JSON
});
if (!hasItems)
{
//Remove all items
//Fill drop down with default value
}
But I don't think this is right. So do I enter into the function whether or not I receive data? I guess I really want to check the data object contains something - to set my boolean hasItems.
You should handle the check right inside the callback function, check the example here.
var hasItems = false;
$.getJSON('ajax/test.json', function(data) {
hasItems = true;
//Remove all items
//Fill drop down with data from JSON
if (!hasItems)
{
//Remove all items
//Fill drop down with default value
}
});
You want to do all checking of returned data inside the callback, otherwise that condition will be called before the callback has been called, resulting in it always being the initial value assigned.
You're dealing with asynchrony, so you need to think of the code you're writing as a timeline:
+ Some code
+ Fire getJSON call
|
| server working
|
+ getJSON call returns and function runs
The code inside the function happens later than the code outside it.
Generally:
// Setup any data you need before the call
$.getJSON(..., function(r) { //or $.ajax() etc
// Handle the response from the server
});
// Code here happens before the getJSON call returns - technically you could also
// put your setup code here, although it would be weird, and probably upset other
// coders.

How do i execute a callback in a jQuery Plugin code

This may sound really like a newbie .. But i used the jQuery Boilerplate on this page - http://stefangabos.ro/jquery/jquery-plugin-boilerplate-revisited/ and created a plugin. Everything works fine, except now i want to add a callback. I want to execute this -
$.Alerter({'message':'this is a test','onSuccess':function(data) { alert(data); } });
The onSuccess is a callback function which is added to the defaults.
My question is – how do i send the output to the onSuccess. I want it to return back a TRUE or FALSE value after certain steps have been executed in the init()
Something like this:
plugin.result = null;
plugin.init = function() {
// do stuff
...
// save _result in public variable result
plugin.result = _result;
}
If you are writing this plugin for dom operations, you could also use it like plugin.data('result',_result);
Since I don't know anything else I can't give further insight.
Hope this will help you.

Categories

Resources