I'm currently building a simple application of example for Google Map API GeoCoding and stumbled upon a problem with javascript.
The geocodeRequest method should assign its result value to the variable this.tempResult.
However the varriable is null when I try to print it in Listener.
The output to console is:
Listener: null
geoCodeRequest: Object
The order how output is printed seems to imply that the code in Listener run ahead before the geoCodeRequest method manage to assign the this.tempResult variable.
Is there a solution for this?
$JSKK.Class.create
(
{
$namespace :'application',
$name :'GeoCoder'
}
)
(
{
},
{
service :null,
main :null,
geoCodeInput: null,
geoCodeButton: null,
reverseGeoCodeButton: null,
reverseGeoCodeActive: null,
callback :null,
reverseCallback:null,
tempResult: null,
init: function(main, callback, reverseCallback)
{
this.main = main;
this.callback = callback;
this.reverseCallback = reverseCallback;
this.service = new google.maps.Geocoder();
this.geoCodeInput = $('#toolPannel div[data-resource=locator] input[data-action=input]');
this.geoCodeButton = $('#toolPannel div[data-resource=locator] input[data-action=geocode]');
this.reverseGeoCodeButton = $('#toolPannel div[data-resource=locator] input[data-action=reversegeocode]');
this.reverseGeoCodeActive = false;
this.createListener();
},
geoCodeRequest: function(request)
{
this.service.geocode
(
request,
function (result,status)
{
//console.debug(arguments);
if (status== google.maps.GeocoderStatus.OK)
{
this.tempResult = result[0];
console.debug(this.tempResult);
}
else
{
alert ('GeoCoder request failed!');
}
}.bind(this)
);
},
createListener: function()
{
this.geoCodeButton.click(function()
{
this.geoCodeRequest
(
{
address: this.geoCodeInput.val()
}
);
this.callback(this.tempResult);
}.bind(this) //Bind here
);
this.reverseGeoCodeButton.click(function()
{
if (!this.reverseGeoCodeActive)
{
this.main.map.setOptions({draggableCursor:'crosshair'});
this.reverseGeoCodeActive=true;
}
else if(this.reverseGeoCodeActive)
{
this.main.map.setOptions({draggableCursor:'hand'});
this.reverseGeoCodeActive=false;
}
}.bind(this)
);
google.maps.event.addListener
(
this.main.map,
'click',
function (event)
{
if (this.reverseGeoCodeActive)
{
this.geoCodeRequest
(
{
location: event.latLng
}
);
console.debug(this.tempResult);
this.reverseCallback(this.tempResult);
}
}.bind(this)
);
}
}
);
The problem is this code:
geoCodeRequest: function( request ) {
this.service.geocode( request, function( result, status ) {
if (status == google.maps.GeocoderStatus.OK) {
this.tempResult = result[0];
console.debug( this.tempResult );
} else {
alert( 'GeoCoder request failed!' );
}
}.bind(this) );
},
this.geoCodeButton.click( function() {
this.geoCodeRequest({
address: this.geoCodeInput.val()
});
this.callback( this.tempResult );
}.bind(this) );
(Sorry, but I took the liberty of reformatting so I could follow the logic better. Feel free to convert back to your own style.)
You are trying to call this.callback() before the geocoded result comes back from the server. That won't work. You need to handle the geocoding result in the callback that the geocoder uses.
You're already providing the geocoder a callback in the geoCodeRequest() method, so what you can do is add a callback to your click handler and call that from the callback in geoCodeRequest(). You could do that like this:
geoCodeRequest: function( request ) {
this.service.geocode( request, function( result, status ) {
if (status == google.maps.GeocoderStatus.OK) {
request.success( result[0] );
} else {
request.failure( status );
}
}.bind(this) );
},
this.geoCodeButton.click( function() {
this.geoCodeRequest({
address: this.geoCodeInput.val(),
success: function( result ) {
console.debug( result );
this.callback( result );
},
failure: function( status ) {
alert( 'GeoCoder request failed!' );
}
});
}.bind(this) );
Note that I also added a failure callback to handle the error condition.
The reverseGeoCodeButton.click() handler has the same problem and can be fixed with the same solution.
This is close to your original approach, but I have to wonder if the code could be simplified. Do you need these multiple levels of code? In any case, wherever you're dealing with an asynchronous result like what the geocoder gives you, you do have to handle that inside the appropriate callback instead of after the geocoder (or any asynchronous function) returns.
Related
I'm having this weird issue where when I get the result of a HTML geolocation call, I cant bind it to Vue data, but I can console.log it successfully.
Vue method:
initGeolocation: function() {
if( navigator.geolocation )
{
// Call getCurrentPosition with success and failure callbacks
navigator.geolocation.getCurrentPosition( success, fail );
}
else
{
return;
}
function success(position)
{
console.log(position.coords.latitude); //works
this.lat = position.coords.latitude; //does not work
}
function fail()
{
console.log('fail')
}
},
mounted() {
this.lat = this.initGeolocation(); // does not work
console.log(this.initGeolocation()) // returns undefined
},
Data:
lat: '',
long: '',
Any help would be very much appreciated.
The word this refers to the scope of the function. When you nest another function inside, the word this now refers to the new/ smaller scope so this.lat is no longer defined. So we capture the out this in vm and use it inside functions.
methods: {
initGeolocation: function() {
var vm = this;
if( navigator.geolocation)
{
// Call getCurrentPosition with success and failure callbacks
navigator.geolocation.getCurrentPosition( success, fail );
}
else
{
return;
}
function success(position)
{
vm.lat = position.coords.latitude; //should work now!!
}
function fail()
{
console.log('fail')
}
}
},
mounted() {
this.initGeolocation();
},
In your mounted you assign this.lat with the return of your initGeolocation() method. However this method does not return any data if it succeeds. Instead you write your result into this.lat which then will be overridden again by the void result of your method. So make sure your method initGeolocation either returns your geolocation data or you change your mounted method to call the method without assigning the return value to this.lat.
Also it seems like you just added the initGeolocation method to your component. Look into the methods property of vue components where this would belong.
So try this instead:
mounted() {
this.initGeolocation();
console.log(this.initGeolocation());
},
methods: {
initGeolocation: function() {
if( navigator.geolocation)
{
// Call getCurrentPosition with success and failure callbacks
navigator.geolocation.getCurrentPosition( success, fail );
}
else
{
return;
}
function success(position)
{
this.lat = position.coords.latitude; //does not work
}
function fail()
{
console.log('fail')
}
}
}
I searched for a long time and although there are many questions with similar keywords, none of them share the same intricacies, so let me explain my problem:
I have a function (getProjectData), that has to be called (it is a system wide thing) to get all data from a particular admin section of a cms. My problem comes from a conditional check, where if A one object is blank, where if B, it needs to contain the result of more than 1 ajax calls. The whole data object gets retuned in this function. Code so far:
function getProjectData() {
var code = "some id";
var frozen = true;
var obj = {
code: code,
frozen: true,
localData: "",
};
// here is the point where the object gets returned with a black localData
// or splits to get a few ajax calls
if ( !obj.frozen ) return obj;
else {
getLocalData ( code ).then( function ( data ) {
// ideally, here i would make the parent return obj, but this time with added localData data
obj.localData = data;
parent.return obj;
} );
}
}
function getLocalData ( code ) {
var promise = new Promise ( function ( resolve, reject ) {
var apiReq = "apiUrl";
var chartsReq = "anotherApiUrl";
var res;
$.when(
// api response
$.ajax({
url: apiReq + code,
type: "get",
success: function ( data ) {
return data;
},
error: function ( error ) {
return error;
}
}),
// another ajax call..
$.ajax({ ... }),
// another ajax call..
$.ajax({ ... })
).then(
function ( a, b, c ) {
data = {
response: a
...... for b and c
};
resolve ( data );
},
function ( error ) {
reject ( error );
}
);
} );
return promise.then(
function (data) {
return data;
},
function ( error ) {
console.log ( error );
}
);
}
My problem is that since (and i tried with other ways as well..) all the returns are in a function within other functions, there is no way to return the Obj object after the localData has been added.
I might have over-engineered it but i tried many different ways and since the getProjectData is called by a save button and values need to be returned rather than say, appended to the DOM, i am not sure what to do!
I know that in asynchronous requests, the function has to return before the result is available, where we would usually use callbacks, but a callback will not be able to return something. I could use synchronous requests but i know they are going to be deprecated..
Any ideas?
The correct way to call functions is listed below.
Sincerely I don't understand what you mean saying:
// ideally, here i would make the parent return obj, but this time with added localData data
Using the following way in the parent you should just call:
getProjectData.then(function(obj){
//... do whatever
});
JS:
function getProjectData() {
var code = "some id";
var frozen = true;
var obj = {
code: code,
frozen: true,
localData: "",
};
// here is the point where the object gets returned with a black localData
// or splits to get a few ajax calls
return new Promise(function(resolve, reject) {
if ( !obj.frozen )
resolve(obj);
else {
getLocalData ( code ).then( function ( data ) {
// ideally, here i would make the parent return obj, but this time with added localData data
obj.localData = data;
resolve(obj);
});
});
}
}
function getLocalData ( code ) {
var promise = new Promise ( function ( resolve, reject ) {
var apiReq = "apiUrl";
var chartsReq = "anotherApiUrl";
var res;
$.when(
// api response
$.ajax({
url: apiReq + code,
type: "get",
success: function ( data ) {
return data;
},
error: function ( error ) {
return error;
}
}),
// another ajax call..
$.ajax({ ... }),
// another ajax call..
$.ajax({ ... })
).then(
function ( a, b, c ) {
data = {
response: a
...... for b and c
};
resolve ( data );
},
function ( error ) {
reject ( error );
}
);
} );
return promise;
}
I got this response from ajax
{
"laborCostIndex":0.0,
"laborDailyWage":0.0,
"laborHourlyWage":0.0,
"laborMonthlyWage":0.0,
"laborLeave":0.0,
"laborBonus":0.0,
"laborSSS":0.0,
"laborPagIbig":0.0,
"laborPhilHealth":0.0,
"laborEMP":0.0,
"laborTotalMonthlyRate":110.0,
"laborTotalDailyRate":4.230769230769231,
"laborTotalHourlyRate":0.5288461538461539
}
I'm trying to access the element inside through this:
response.laborCostIndex and response['laborCostIndex'] but seems doesn't work for me.
The ajax is from the xeditable here is the code:
UPDATE: posted the whole ajax
$('.laborCostIndex').editable({
pk: '1',
name: 'laborCostIndex',
url: '../BTool/edit_laborCostIndex.do',
validate: function( value ) {
if($.trim( value ) == '') {
return 'This field is required';
} else if( isNaN( value ) ) {
return 'Only accepts numbers';
}
},
params: function(params) {
var basicDailyWage = $(this).closest('td').next('td').find('a').text();
var pagIbig = $(this).closest('tr').find(':nth-child(11)').find('a').text();
var emp = $(this).closest('tr').find(':nth-child(13)').find('a').text();
var datas = new Array(basicDailyWage, pagIbig, emp);
params.pk = datas;
return params;
},
success: function(response, newValue) {
console.log( response );
console.log( response.laborCostIndex );
console.log( response['laborCostIndex'] );
}
});
Both results to undefined, I don't know why.
Try this in your success function
var obj = JSON.parse(response);
console.log( obj.laborCostIndex);
I would like to run some specific code around put() and add() for Dojo stores.
The problem I am having is that for JSON REST stores, in JsonRest.js add() is just a function that calls put():
add: function(object, options){
options = options || {};
options.overwrite = false;
return this.put(object, options);
},
So, if I use aspect.around() with add(), my code ends up being executed twice IF I apply my code to stores created with a store that implements add() as a stub to put().
Please note that I realise that most stores will do that. I just want my solution to be guaranteed to work with any store, whether there is method nesting or not.
Dojo's own Observable.js has the same problem. This is how they deal with it:
function whenFinished(method, action){
var original = store[method];
if(original){
store[method] = function(value){
if(inMethod){
// if one method calls another (like add() calling put()) we don't want two events
return original.apply(this, arguments);
}
inMethod = true;
try{
var results = original.apply(this, arguments);
Deferred.when(results, function(results){
action((typeof results == "object" && results) || value);
});
return results;
}finally{
inMethod = false;
}
};
}
}
// monitor for updates by listening to these methods
whenFinished("put", function(object){
store.notify(object, store.getIdentity(object));
});
whenFinished("add", function(object){
store.notify(object);
});
whenFinished("remove", function(id){
store.notify(undefined, id);
});
My question is: is there a simple, "short" way to change my existing code so that it checks if it's within a method, and avoid running the code twice?
I gave it a go, but I ended up with clanky, hacky code. I am sure I am missing something...
Here is my existing code:
topic.subscribe( 'hotplate/hotDojoStores/newStore', function( storeName, store ){
aspect.around( store, 'put', function( put ){
return function( object, options ){
return when( put.call( store, object, options ) ).then( function( r ) {
var eventName;
var identity = store.idProperty;
eventName = object[ identity ] ? 'storeRecordUpdate' : 'storeRecordCreate';
topic.publish( eventName, null, { type: eventName, storeName: storeName, objectId: r[ identity ], object: object }, false );
} );
}
});
aspect.around( store, 'add', function( add ){
return function( object, options ){
return when( add.call( store, object, options ) ).then( function( r ) {
var identity = store.idProperty;
topic.publish('storeRecordCreate', null, { storeName: storeName, storeTarget: storeTarget, objectId: r[identity], object: object }, false } );
});
}
});
});
This is my attempt...
What I don't really "get" about my attempt is whether it's 100% safe or not.
If store.add() is called twice in a row, is is ever possible that inMethod is set to true by the first call, and that the second add() call then finds it already set to true because the first one hasn't managed to set it to false yet?
This would only really be possible if nextTick() is called in between the two calls I assume?
Or am I just completely confused by it all? (Which is very possible...)
topic.subscribe( 'hotplate/hotDojoStores/newStore', function( storeName, store ){
var inMethod;
aspect.around( store, 'put', function( put ){
return function( object, options ){
if( inMethod ){
return when( put.call( store, object, options ) );
} else {
inMethod = true;
try {
return when( put.call( store, object, options ) ).then( function( r ) {
var eventName;
var identity = store.idProperty;
eventName = object[ identity ] ? 'storeRecordUpdate' : 'storeRecordCreate';
topic.publish( eventName, null, { type: eventName, storeName: storeName, objectId: r[ identity ], object: object }, false );
});
} finally {
inMethod = false;
}
}
}
});
aspect.around( store, 'add', function( add ){
return function( object, options ){
if( inMethod ){
return when( add.call( store, object, options ) );
} else {
inMethod = true;
try {
return when( add.call( store, object, options ) ).then( function( r ) {
var identity = store.idProperty;
topic.publish('storeRecordCreate', null, { type: 'storeRecordCreate', storeName: storeName, objectId: r[identity], object: object }, false );
});
} finally {
inMethod = false;
}
}
}
});
aspect.around( store, 'remove', function( remove ){
return function( objectId, options ){
return when( remove.call( store, objectId, options ) ).then( function( r ) {
topic.publish('storeRecordRemove', null, { type: 'storeRecordRemove', storeName: storeName, objectId: objectId }, false );
});
};
});
});
I've got the following.
var lookupInit = function () {
http.get('api/employmenttype', null, false)
.done(function (response) {
console.log('loaded: employmenttype');
vm.lookups.allEmploymentTypes(response);
});
http.get('api/actionlist', null, false)
.done(function (response) {
console.log('loaded: actionlist');
vm.lookups.allActionListOptions(response);
});
http.get('api/company', null, false)
.done(function (response) {
console.log('loaded: company');
vm.lookups.allCompanies(response);
});
//... x 5 more
return true;
};
// somewhere else
if (lookupInit(id)) {
vm.userInfo.BusinessUnitID('0');
vm.userInfo.BuildingCode('0');
if (id === undefined) {
console.log('api/adimport: latest');
http.json('api/adimport', { by: "latest" }, false).done(viewInit);
}
else if (id !== undefined) {
console.log('api/adimport: transaction');
http.json('api/adimport', { by: "transaction", TransactionId: id }, false).done(viewInit);
}
} else {
console.log('User info init failed!');
}
The following "http.get('api/employmenttype', null, false)" means i set async to false.
I'm aware that this is probably inefficient. And i'd like to have all the calls load simultaneously.
The only problem is if i don't have them set to async false, the second part of my code might execute before the dropdowns are populated.
I've tried a couple of attempts with Jquery Deferreds, but they have resulted in what i can only describe as an abortion.
The only thing i'm looking to achieve is that the lookup calls finish before the adimport/second part of my code, in any order.... But having each call wait for the one before it to finish EG: async, seems like the only solution I'm capable of implementing decently ATM.
Would this be an appropriate place for deferred function, and could anyone point me into a direction where i could figure out how to implement it correctly, as I've never done this before?
You can use $.when to combine multiple promises to one that resolves when all of them have been fulfilled. If I got you correctly, you want
function lookupInit() {
return $.when(
http.get('api/employmenttype').done(function (response) {
console.log('loaded: employmenttype');
vm.lookups.allEmploymentTypes(response);
}),
http.get('api/actionlist').done(function (response) {
console.log('loaded: actionlist');
vm.lookups.allActionListOptions(response);
}),
http.get('api/company').done(function (response) {
console.log('loaded: company');
vm.lookups.allCompanies(response);
}),
// … some more
);
}
Then somewhere else
lookupInit(id).then(function(/* all responses if you needed them */) {
vm.userInfo.BusinessUnitID('0');
vm.userInfo.BuildingCode('0');
if (id === undefined) {
console.log('api/adimport: latest');
return http.json('api/adimport', {by:"latest"})
} else {
console.log('api/adimport: transaction');
return http.json('api/adimport', {by:"transaction", TransactionId:id});
}
}, function(err) {
console.log('User info init failed!');
}).done(viewInit);
In the Jquery API I've found this about resolving multiple deferreds:
$.when($.ajax("/page1.php"), $.ajax("/page2.php")).done(function(a1, a2){
/* a1 and a2 are arguments resolved for the
page1 and page2 ajax requests, respectively.
each argument is an array with the following
structure: [ data, statusText, jqXHR ] */
var data = a1[0] + a2[0]; /* a1[0] = "Whip", a2[0] = " It" */
if ( /Whip It/.test(data) ) {
alert("We got what we came for!");
}
});
Using this with your code:
var defer = $.when(
$.get('api/employmenttype'),
$.get('api/actionlist'),
$.get('api/company'),
// ... 5 more
);
defer.done(function (arg1, arg2, arg3 /*, ... 5 more*/) {
vm.lookups.allEmploymentTypes(arg1[0]);
vm.lookups.allEmploymentTypes(arg2[0]);
vm.lookups.allEmploymentTypes(arg3[0]);
// .. 5 more
vm.userInfo.BusinessUnitID('0');
vm.userInfo.BuildingCode('0');
if (id === undefined) {
console.log('api/adimport: latest');
http.json('api/adimport', { by: "latest" }, false).done(viewInit);
} else if (id !== undefined) {
console.log('api/adimport: transaction');
http.json('api/adimport', { by: "transaction", TransactionId: id }, false).done(viewInit);
}
});
You can use the defer of the $.when() inside an other $.when(), so if the json calls are not dependant on the first calls you can add them in a an onther defer.