I am trying to build a little javascript class for geocoding addresses trough Google Maps API. I am learning Javascript and AJAX and I still can’t figure out how can I initialize class variables trough a callback:
// Here is the Location class, it takes an address and
// initialize a GClientGeocoder. this.coord[] is where we'll store lat/lng
function Location(address) {
this.geo = new GClientGeocoder();
this.address = address;
this.coord = [];
}
// This is the geoCode function, it geocodes object.address and
// need a callback to handle the response from Google
Location.prototype.geoCode = function(geoCallback) {
this.geo.getLocations(this.address, geoCallback);
}
// Here we go: the callback.
// I made it a member of the class so it would be able
// to handle class variable like coord[]. Obviously it don't work.
Location.prototype.geoCallback = function(result) {
this.coord[0] = result.Placemark[0].Point.coordinates[1];
this.coord[1] = result.Placemark[0].Point.coordinates[0];
window.alert("Callback lat: " + this.coord[0] + "; lon: " + this.coord[1]);
}
// Main
function initialize() {
var Place = new Location("Tokyo, Japan");
Place.geoCode(Place.geoCallback);
window.alert("Main lat: " + Place.coord[0] + " lon: " + Place.coord[1]);
}
google.setOnLoadCallback(initialize);
Thank you for helping me out!
EDIT
Thanks TJ for your reply. I read your example and your post — things are getting much clearer. But I still have an issue. Have a look:
function bind(context, func) {
return function() {
return func.apply(context, arguments);
}
}
function Location(address) {
this.geo = new GClientGeocoder();
this.address = address;
this.coord = [];
}
Location.prototype.geoCode = function(callback) {
this.geo.getLocations(this.address, callback);
}
Location.prototype.geoCallback = function(result) {
this.coord[0] = result.Placemark[0].Point.coordinates[1];
this.coord[1] = result.Placemark[0].Point.coordinates[0];
// This alert is working properly, printing the right coordinates
window.alert("I am in geoCallback() lat: " + this.coord[0] + "; lon: " + this.coord[1]);
}
function initialize() {
var Place = new Location("Tokyo, Japan");
Place.geoCode(bind(Place, Place.geoCallback));
window.alert("I am in initialize() lat: " + Place.coord[0] + "; lon: " + Place.coord[1]);
}
Why the alert in initialize() pops before the alert in geoCallback(), printing an undefined/undefined?
What you need to do is ensure that
thisis set correctly in the callback. This is sometimes called “binding”. Prototype providesFunction#bindfor this, but it’s easy enough to do if you don’t use Prototype — define a function that will do the binding for you:and then use it in your
initializecall:(Although I think I’d suggest refactoring a bit so that the caller of
geoCodedoesn’t have to provide the callback at that level.)What
bindabove does is create a closure (a function) that, when called, will turn around and call the function you gave withthisset to the context you gave, passing on any arguments that were given. (This is done above viaFunction#apply, which is a standard part of JavaScript.) You’d normally want to definebindat a fairly high level (page level or within your scoping function if you use one [which is a good idea]) to avoid having the generated functions closing over more data than necessary.Here’s a post in my anemic blog about
thisin a bit more detail.Regarding your edit: That’s actually a completely different question. By default, Ajax calls are asynchronous (which is why Google wants you to provide a callback function). So your code requests the data via
getLocations, but that request is processed asynchronously and your code continues. The very next thing your code does is display the values you don’t have yet. At some later time, the request will complete and the values will be updated, but by then your code has finished. You’d want to move the alert (more generically, move your code processing the result) into a callback.