unable to get value that is set in a callback - javascript

I'm having an issue getting a value that is set in a callback. I initially make a call to get Quest data, then call game.state.setNPCs after the quest data has returned.
I want to get the NPC object after it has been set, but the get is returning an empty array even though setNPCs() seems to be setting the array.
You can see below, after the callback to set the result.npcs, I log out getNPCs(), and it is an Empty Array.
Even more weird, I call game.state.getNPCs() from within the GameState object after the value has been set, but it is still an empty array.
EDIT: I find if I pass in game.state.getNPCs as a callback into the initial callback setNPCs(), like so:
callback(result.npcs, game.state.getNPCs);
Then this works... But I don't want to have to pass in another callback. See below.
Initial call with game.state.setNPCs callback:
Utilities.game.quest.getQuestData({ id : stat.quest_id }, game.state.setNPCs);
Call to getQuestData:
getQuestData : function (params, setNPCcallback) {
API.Quest.getQuestData(params).done(function (result) {
if (game.state) {
game.state.setQuest(result); //Object received successfully
setNPCcallback(result.npcs, game.state.getNPCs);
console.log('NPCs', game.state.getNPCs()); //Empty array
}
});
},
GameState object:
var GameState = function(args) {
this.npcs = [];
...
};
GameState.prototype = {
constructor : GameState,
getNPCs : function () {
console.log(this.npcs); //Empty array
return this.npcs;
},
setNPCs : function (npcsArray, getNPCcallback) {
this.npcs = npcsArray;
console.log(this.npcs); //Contains Object
console.log(game.state.getNPCs()); //Empty array
console.log(getNPCcallback()); //Contains Object
},

I made a small demo to test the issue.
var Person = function () {
this.name = "someone";
}
Person.prototype.walk = function () {
console.log(this)
};
var p = new Person;
function exec(callback) {
callback(); //context is Window
callback.call(p); //context is Person {name: "someone"}
}
p.walk(); //context is Person {name: "someone"}
exec(p.walk);
When you invoke the callback from getQuestData, the context is not GameState instance. Invoking the callback with the correct context using call or apply methods, or using a callback which is bound to correct context using bind method should fix the issue.
Else you can pass the GameState instance itself and invoke gameState.callbackMethod()
which should look like the following according to previous example
function exec(instance) {
instance.walk(); //context is Person {name: "someone"}
}
exec(p);

Related

Call a method after a callback and an event

I have a module with four functions that call one after the other. I am trying to follow the Revealing Module Pattern. One of the functions is public, the remaining are private. It goes like this:
publicMethod is called from another module
queryNames is called from publicMethod
execute(parameters, callback?, errback?) is called from queryNames
addNamesList is called as the callback? argument of execute
Several dijit/form/CheckBox's are created and the method querySegments is triggered onChange
querySegments needs to call a method of an object created in publicMethod.
The problem is in step 6, I can't reach the object created in step 1.
I have tried to use dojo hitch to define the callback? argument in step 3, but I can't get it to work. I tried putting this in its first argument, but even then I can't reach the required scope to call addNamesList.
Here is some code to demonstrate this issue.
define([
'dojo/dom',
'dijit/form/CheckBox',
'esri/layers/ArcGISDynamicMapServiceLayer',
'esri/tasks/query',
'esri/tasks/QueryTask',
'dojo/_base/lang'
],
function (
dom,
CheckBox,
ArcGISDynamicMapServiceLayer,
Query, QueryTask,
lang
) {
// ***************
// private methods
// ***************
// fetch names and call addNamesList to put the list in place
var queryNames = function (map, mapLayer) {
// new QueryTask(url, options?)
var queryTask = new QueryTask("url")
var query = new Query()
// execute(parameters, callback?, errback?)
// this callback passes an argument called featureSet
queryTask.execute(query, lang.hitch(map, "addNamesList", mapLayer), function(error) {console.log(error)})
}
// callback function of queryNames
var addNamesList = function (mapLayer, featureSet) {
console.log('addOplist')
var namesCount = featureSet.features.length
for (var i = 0; i <namesCount; i++) {
// work
var cbox = new CheckBox({
id: "cbox_" + i,
value: featureSet.features[i].attributes["someID"],
checked: false,
onChange: function (evt) {
querySegments(this.value, mapLayer)
}
})
cbox.placeAt("someDiv" + i, "first")
}
}
// triggered by the checkbox event
var querySegments = function (name, mapLayer) {
// build the query
var queryStatement = "someID = " + name
var layerDefinitions = [queryStatement]
// call a method of mapLayer
mapLayer.setLayerDefinitions(layerDefinitions)
}
// **************
// public methods
// **************
var publicMethod = function (map) {
var mapLayer = new ArcGISDynamicMapServiceLayer('restURL')
map.addLayer(mapServiceLayer)
queryNames(map, mapLayer)
return mapLayer
}
return {
publicMethod: publicMethod
}
}
)
You can see a more detailed explanation and a working example on this other (and more broad) question that I have put on Code Review.
I am new to JavaScript and I guess I still have a lot of issues with scoping, closures and callbacks.
I will deeply appreciate any input, including how to improve this question.
Edit
With this current implementation (with dojo hitch), no error is thrown. The method addNamesList is not called (nor errback, which I also don't understand why). I think this is because addNamesList is not on map's (hitch first argument) namespace. I tried to put this instead, but it makes no difference.
Before I decided to use hitch, the code looked like this:
var queryNames = function (map, mapLayer) {
...
queryTask.execute(query, addNamesList)
}
var addNamesList = function (featureSet) {
...
...
...
querySegments(this.value, mapLayer)
}
but then I couldn't reach mapLayer inside the method triggered by the check box event. It would throw Uncaught ReferenceError: mapLayer is not defined. That is why I tried to use hitch.
Javascript is asynchronous, so pretty much data coming from db, http requests or whatever is returned via callbacks. Here's what happens in your code:
public method calls queryNames
queryNames call addNamesList of map asynchronously and returns nothing
public method takes back control, meanwhile some stuff is going on with the addNamesList
mapLayer is returned untouched while some stuff is still going on in the background
So, to avoid this, you should return data from public method via callback, so you pass callback as the second parameter to the public method, then to the querySegments. Then, in the success callback of query, when you finally get the result ready, you do:
callback(mapLayer);
So, everything you should do is to pass this callback as deep as needed to the place where you have your mapLayer ready (so you've done with it everything you wanted), and then do a callback(mapLayer);.
This and this would probably explain better.
Best regards, Alexander

Setting "this" to the instance, in a callback set during the creation of a prototype function

I have this code:
var createAllAreSelectedClickedHandler = function(selectablesArrayGetter) {
return function() {
var array = selectablesArrayGetter();
var desiredState = array.every(function(selectable) { return selectable.selected; }) ? false : true;
array.forEach(function(selectable) {
selectable.selected = desiredState;
});
};
};
Followed by this one:
function PromoViewModel() { this.registrations = [...] }
PromoViewModel.prototype.allEventsSelectedClickedHandler = createAllAreSelectedClickedHandler(function() { return this.registrations; }));
I can't manage to set the correct value of this. The "this" value when the function is created points to Window so I can't do .bind(this). I've tried doing .bind(PromoViewModel.prototype) but it lacks all the precious instance fields set inside the constructor.
I know I could simply set this.allEventsSelectedClickedHandler in the constructor function, but I'm trying to separate the methods creation from the variables.
The problem is the call selectablesArrayGetter(); which determines the this value for the callback.
You will need to "pass" the this value that the method (i.e. the closure you are returning) is invoked on, using call:
var array = selectablesArrayGetter.call(this);
I'd recommend defining your PromoViewModel.prototype.allEventsSelectedClickedHandler method as follows:
PromoViewModel.prototype.allEventsSelectedClickedHandler = function() {
var _array = this.registrations;
var desiredState = _array.every(function(selectable) { return selectable.selected; }) ? false : true;
_array.forEach(function(selectable) {
selectable.selected = desiredState;
});
};
the function that you're passing as callback uses this, but doesn't have the PromoViewModel context. You can ensure the method has the proper context by binding this to a variable.
function PromoViewModel()
{
var me = this;
this.registrations = [...];
this.allEventsSelectedClickedHandler = createAllAreSelectedClickedHandler(function() {
return me.registrations;
});
}
Working fiddle: http://jsfiddle.net/michaschwab/coegnL5j/9/ also has Bergi's answer in there (commented out) to show that that works just as well.
Ok here is what I did.
In the prototype definition instead of directly associating it to createAllAreSelectedClickedHandler function, I actually define a function that returns the createAllAreSelectedClickedHandler function. By doing this, I can define a variable (in this case protoScope) that maps this context when defined.
When doing that, if you put a break-point in the createAllAreSelectedClickedHandler function you will see that the selectablesArrayGetter value is correct (the acutal registrations array).
PromoViewModel.prototype.allEventsSelectedClickedHandler = function (){
var protoScope = this;
return createAllAreSelectedClickedHandler(function() {
return protoScope.registrations;
});
}

How to extract data from this object?

I am working on some legacy code for my workplace and cannot figure out how to process data from a data object that is returned. The retrieveThis function is supposed to retrieve the object data:
myObj.retrieveThis(new myObj.getThisData({num : 10, page : 1, sorting : "stampDescending"}), function () {myCallback(this);});
var myObj = function () {
var getThisData = {
// this function populates an array and returns it to the retrieveThis function
}
var theObject = {
retrieveThis: function(a, b) {
var OBJ = {};
// OBJ is populated by the data from var getThisData...I checked
setTimeout(function () {
b(OBJ);
}, 1000);
}
}
return theObject;
})();
I am having trouble getting the data ("theObject") to my callback function (or at all). I pass this to myCallback(), where myCallback is:
function myCallback(obj) {
alert(Object.keys(obj));
}
The alert box shows a list of keys including document, jQuery, and myObj. It looks like the data from OBJ is populated from the array allTheData, but I can't seem to pass this back (as return theObject;) to process it. Where am I going wrong here?
Note - I cannot modify this legacy code. I need to process "theObject".
You pass wrong callback function
inside you call it with parameters, so you should define it with parameters
myObj.retrieveThis(
new myObj.getThisData({num : 10, page : 1, sorting : "stampDescending"}),
myCallback);
in this case in first param to myCallback passed OBJ object

Calling a Dynamically set Function Object Property from within the Object

From within my webpage I am creating an object and trying to call a dynamically set function from within it. The dynamic function however, isn't being executed.
Here is a subset of the Object:
var LightBoxLogin = {
DialogBox: null,
SuccessFunction: null,
..........
Login: function(){
console.log(LightBoxLogin.SuccessFunction) // Displays "TestSubmit()"
LightBoxLogin.SuccessFunction(); // does nothing, should alert the page
}
}
LightBoxLogin.SuccessFunction is set with:
function SuperLightbox(functOnSuccess)
{
LightBoxLogin.SuccessFunction = functOnSuccess;
if(IsLightboxNeeded())
{
LightBoxLogin.Login();
}
else{
alert("Not needed");
}
}
And called like:
function TestSubmitHandler ()
{
SuperLightbox(TestSubmit);
}
function TestSubmit ()
{
alert('TEST SUBMIT ALL CAPS');
}
Let me know if im missing anything.
I just need to execute the function passed as a parameter initially.
Instead of the line
SuperLightBox(TestSubmit);
Use the function name as a String instead:
SuperLightBox("TestSubmit");
This means in the Login:function() this line:
LightBoxLogin.SuccessFunction();
Will be replaced with:
window[LightBoxLogin.SuccessFunction]();
This yields the results I was looking for, but beware; it only works if the desired function is accessible globally in the page.

How can I store functions within the HTML5 history states

So I'm using the HTML5 history management for adding the ability to navigate back and forward within a website with AJAX loaded subcontent.
Now I would like to store javascript functions within the state object, to callback at the state popping. More or less like the following code:
$(window).bind("popstate", function(event) {
var state = event.originalEvent.state;
if (!state) {
return;
}
state.callback(state.argument);
}
function beforeLoad() {
var resourceId = "xyz";
var func;
if (case1) {
func = switchPageToMode1;
} else { // case 2
func = swithPageToMode2;
}
func(resourceId); // run the function
window.history.pushState({ callback: func, resourceId: resourceId }, "newTitle", "newURL"); // and push it to history
}
function switchPageToMode1(resourceId) {
alterPageLayoutSomeWay();
loadResource(resourceId);
}
function swithPageToMode2(resourceId) {
alterPageLayoutSomeOtherWay();
loadResource(resourceId);
}
function loadResource(resourceId) {
...
}
All right. So what I'm trying to do is storing a reference to a javascript function. But when pushing the state (the actual window.history.pushState call) the browser files a complaint, namely Error: "DATA_CLONE_ERR: DOM Exception 25"
Anybody knows what I'm doing wrong? Is it at all possible to store function calls within the state?
No, it's not possible, not directly anyway. According to MDC the "state object," i.e. the first argument to pushState, "can be anything that can be serialized." Unfortunately, you can't serialize a function. The WHATWG spec says basically the same thing but in many more words, the gist of which is that functions are explicitly disallowed in the state object.
The solution would be to store either a string you can eval or the name of the function in the state object, e.g.:
$(window).bind("popstate", function(event) {
var state = event.originalEvent.state;
if ( !state ) { return; }
window[ state.callback ]( state.argument ); // <-- look here
}
function beforeLoad() {
var resourceId = "xyz",
func
;
if ( case1 ) {
func = "switchPageToMode1"; // <-- string, not function
} else {
// case 2
func = "swithPageToMode2";
}
window[ func ]( resourceId ); // <-- same here
window.history.pushState(
{ callback : func,
argument : resourceId
},
"newTitle", "newURL"
);
}
Of course that's assuming switchPageToMode1 and -2 are in the global context (i.e. window), which isn't the best practice. If not they'll have to be accessible somehow from the global context, e.g. [window.]MyAppGlobal.switchPageToMode1, in which case you would call MyAppGlobal[ func ]( argument ).
I came up with a slightly different solution.
I added two variables to the window variable
window.history.uniqueStateId = 0;
window.history.data = {}.
Each time I perform a pushstate, all I do is push a unique id for the first parameter
var data = { /* non-serializable data */ };
window.history.pushState({stateId : uniqueStateId}, '', url);
window.history.data[uniqueStateId] = data;
On the popstate event, I then just grab the id from the state object and look it up from the data object.
Here is what I do:
Each HTML page contains one or more components that can create new History entries.
Each component implements three methods:
getId() which returns its unique DOM id.
getState() that returns the component's state:
{
id: getId(),
state: componentSpecificState
}
setState(state) that updates the component's state using the aforementioned value.
On page load, I initialize a mapping from component id to the component like so:
this.idToComponent[this.loginForm.getId()] = this.loginForm;
this.idToComponent[this.signupForm.getId()] = this.signupForm;
Components save their state before creating new History entries:
history.replaceState(this.getState(), title, href);
When the popstate event is fired I invoke:
var component = this.idToComponent[history.state.id];
component.setState(history.state);
To summarize: instead of serializing a function() we serialize the component id and fire its setState() function. This approach survives page loads.

Categories