I am working with dojo in the ESRI Web App Builder and have come across a situation where I need to run an AJAX call and still access a variable from the base class. Below is my code with comments in it to explain exactly where it is successful and exactly where it is failing:
define(['dojo/_base/declare', 'jimu/BaseWidget', 'dojo/request', "esri/layers/WMSLayer", "esri/config", "dojo/domReady!"], function (declare, BaseWidget, request, WMSLayer, esriConfig) {
return declare([BaseWidget], {
baseClass: 'jimu-widget-mywidget',
// This function is called by a button press (Normally the WMSLayer variable would be set by user input)
addWms: function () {
var wmsLayer = new WMSLayer("http://sampleserver1.arcgisonline.com/ArcGIS/services/Specialty/ESRI_StatesCitiesRivers_USA/MapServer/WMSServer", {
format: "png",
visibleLayers: [2]
});
this.map.addLayer(wmsLayer); // this.map is inherited from BaseWidget as far as I can see. This adds a wms to my map without error
request("request.html").then(function(data){
var wmsLayer = new WMSLayer("http://sampleserver1.arcgisonline.com/ArcGIS/services/Specialty/ESRI_StatesCitiesRivers_USA/MapServer/WMSServer", {
format: "png",
visibleLayers: [2]
});
this.map.addLayer(wmsLayer); // This is now in another context....I get the error HERE.
// At this point map is not defined because this anonymous function is running
// in a different context. ( at least I think that's what is happening )
}, function(err){
// Hopefully there are no typos in my example XD
});
}
});
});
My question is --> How do I access the "map" variable from withing the callback function of "request"?
I want to be able to run this.map.addLayers from my call to the GetCapabilities of a WMS service. The request would normally call it and I get all the way to the end of my code until I can't access the "map" variable anymore as I know it.
Dojo type answer is preferred but plain old javaScript is also fine. Please avoid JQuery answers.
Resources are:
ESRI JavaScript library
Dojo
ESRI Web App Builder
The problem you're running into is the classic problem of execution context being lost when an asynchronous callback is invoked (and thus this no longer means what you want). There are generally two ways to resolve this.
One way is to create a variable in the outer scope that references what this is, so that the inner function can access it:
var self = this;
request('request.html').then(function (data) {
// ...
self.map.addLayer(wmsLayer);
});
The other way is to use context binding, either via Function#bind (ES5) or dojo/_base/lang.hitch:
// Function#bind:
request('request.html').then(function (data) {
// ...
this.map.addLayer(wmsLayer);
}.bind(this));
// lang.hitch:
request('request.html').then(lang.hitch(this, function (data) {
// ...
this.map.addLayer(wmsLayer);
}));
It's also common with the binding approach to break out the asynchronous handler function to another internal instance method:
_addRequestedLayer: function () {
// ...
this.map.addLayer(wmsLayer);
},
addWms: function () {
// ...
// ES5:
request('request.html').then(this._addRequestedLayer.bind(this));
// ...or lang.hitch, with late binding:
request('request.html').then(lang.hitch(this, '_addRequestedLayer'));
There is also a tutorial on lang.hitch.
In your request callback, this does not refer to your class anymore. A simple way to have access to the map in the callback is assigning the reference of the map in a variable on addWms function scope and then using it in your callback:
addWms: function() {
var map = this.map;
// Your code here
request('request.html').then(function (data) {
// Your code here
map.addLayer(wmsLayer); // Note that you're using map instead of this.map
}
}
You can also use Dojo hitch function, where you can pass a function and the context that it should be applied. I suggest that you take a look at this link http://dojotoolkit.org/reference-guide/1.10/dojo/_base/lang.html#hitch.
Related
I'm trying to use the NodeJS module "pcsc-lite" to communicate with a card reader. If you want to take a look at the module : https://github.com/santigimeno/node-pcsclite.
I'm looking for a way to send a sequence of data to my reader using my own method. Because, the module is event-based. So I have to declare two listeners (one in the other) to be able to call the send method.
For example :
module.on("reader", function(reader){
//...
reader.on("status", function(status){
//...
reader.connect({ share_mode : this.SCARD_SHARE_SHARED },function(err, protocol) {
//This is the method I want to be able to call "when I need it"
reader.transmit(...);
});
});
});
I would like to call the transmit method like this for example :
function send(...){
reader.transmit(...);
}
I think there is a way to do it, but I seem to be a little bit hooked to my C/Java programming habits.
Thanks in advance.
If your reader will be a singleton, you can declare it outside the callback, and then assign the variable when you're ready. Without knowing more, here's a simple example:
let reader; // we prepare a variable that's outside of scope of it all.
// your `send` function
function send(params) {
let stuff = doStuffWithParams(params);
reader.transmit(stuff, callback);
}
// we take out init stuff too
function initialize() {
// we know reader variable is already initialized.
reader.on('status', function() {
reader.connect({
share_mode : this.SCARD_SHARE_SHARED
},function(err, protocol) {
// send.
send();
// or even better, emit some event or call some callback from here, to let somebody outside this module know you're ready, then they can call your `send` method.
});
});
}
// now your module init section
let pcsc = require('pcsclite')();
pcsc.on('reader', function(r) {
// assign it to our global reader
reader = r;
initialize();
});
Note: don't call your variables module, it's refering to the file being currently executed and you can get unexpected behavior.
I am trying to set a variable value at an object (class) level from deep inside a few levels of nested anonymous javascript method calls. How do I do this?
Here is some code to explain what I am trying to do. Disclaimer: I am not that comfortable with the concept of closures in javascript, so I might be going along a wrong path here. Any suggestion about a succinct way to achieve what I want to do would be greatly appreciated.
// FileUtils object.
var FileUtils = function () {
// Member variables.
this.ConfRootDir = null;
};
// Method to get a file entry.
// successCallback has to be a method with a FileEntry object.
FileUtils.prototype.getFileEntry = function (fileName, successCallback) {
if (this.ConfRootDir == null) {
var thisObj = this;
// Request the root filesystem
// [** 1st callback, using anon method]
window.requestFileSystem(LocalFileSystem.PERSISTENT, 0,
function (fileSystem) {
// Get the root directory of the file system.
var rootDir = fileSystem.root;
// Get the ConferenceToGo directory, or create if required.
// [** 2nd callback, using anon method]
rootDir.getDirectory("ConferenceToGo", { create: true, exclusive: false },
function (confDir) {
// Set values for future use
// [** Definitely wrong scoping. The class level variable
// can't be accessed using 'this'. What to do? **]
this.ConfRootDir = confDir;
// Now try getting the handle for the list file.
// [** 3rd callback, using anon method. Irrelevant at this point.]
this.ConfRootDir.getFile(fileName, { create: false },
successCallback, // Success callback [getFile]
function (error) {
logError("Unable to retrieve file: ", true, true, error);
}); // Failure callback [getFile]
}, // Success callback [getDirectory]
function (error) { logError("Unable to create new directory: ", true, true, error); }); // Failure callback [getDirectory]
}, // Success callback [requestFileSystem]
function (error) { logError("Problem reading file system: ", true, true, error); }
);
}
}
I know that the scoping (by using 'this') is all wrong in the above piece of code, but not sure how to get it right. I have seen a few answers about binding to context (like this one), but I am using anonymous methods so that makes it harder. Note: Although I show only one method in the prototype of FileUtils here, there a few more.
Those who are aware can probably recognize that I am using methods from the cordova (PhoneGap) library for cross-platform mobile dev in HTML5 and JS, but that is not really much relevant here.
… function() { function() { function() { …
// Set values for future use
// [** Definitely wrong scoping. The class level variable
// can't be accessed using 'this'. What to do? **]
this.ConfRootDir = confDir;
You already have preparated the answer: thisObj.ConfRootDir. The thisObj variable is available in the scope of the nested function, and still points to the this keyword of the outer getFileEntry function, i.e. to the FileUtils instance.
UPDATE: The back-end service was powered by an ASP.Net AJAX Web Service proxy.
1) The main page has two global objects, one for the back end connections (Svc) and another for handling the DOM (Main). It also dynamically loads documents into an iframe.
2) These iframes need to access services provided by Svc, and also supply a callback function.
3) The problem - passing a function created in the iframe to the parent frame, it's treated as an object not a function and cannot be invoked.
Can anyone provide a better solution than what I've got currently in the iframe:
var Main = parent.Main,
Svc = parent.Svc;
Svc.method(data, Main.createCallback(
function(response) {}
));
and in the parent frame:
Main.createCallback = function(func) {
return function() {
func.apply(func, arguments);
}
}
if you override the iFrame's function from the main, the main scope will then be used.
The inverse problem can be seen here, in your case, you just override the frame's function itself i.e:
document.getElementById('yourFrameID').contentWindow.targetFunctionInFrame = targetFunctionInMain;
Bonus: if you can modify the iFrame's code, I would suggest to:
In the frame:
make a placeholder function callbackParent() {}
add a call to this function into your iframe code, so that you just have to override the callbackParent from your main.
In the main:
make the function which should be invoked function doStuff() {}
override the function as described above document.getElementById('yourFrameID').contentWindow.callBackParent = doStuff;
I use iframes to modularize my app too.They are a kind of includes embedding all CSS, HTML and JS for a module.
My first attempts were by returning a function too, but then I found it quite hard for sharing scopes.
Now I make directly a reference to the main parent object in the iframe.
eg:
var Svc = parent.Svc, JSON = parent.JSON, $ = parent.$;
Svc.module1 = {
method1:function(arg){
...
},
...
}
The global var JSON and jQuery references are here to have them available inside the methods.
My guest is that Svc.method is making some checks to see if the callback has some criteria before calling it. This criteria might be that the callback function must created by the same framework (here it's ASP.Net). You have to find what that criteria is. if "Main.createCallback" works, it's because it's meeting that criteria.
Sorry but your all wrong... add this....
const event = new CustomEvent('MSGSent', { detail: "fff variable" });
Call it like this....use a global variable for detail... like an array []
window.dispatchEvent(event);
Now after the iframe loads add this code and you get an Object back in the main page....
iframe.onload = function() {
try {
iframe.contentWindow.addEventListener('MSGSent',function(e){
alert(e.detail);
});
} catch (error) {
}
};
The problem is ASP.Net AJAX Web Service proxies, which don't appear to support calling the web service from an iframe with an inline callback function.
I am relatively new to javascript so please be patient if what i am asking is completely stupid!
I am trying to make a simple module. Inside the module i want to have a config object that holds settings for the module. I am also using jquery. The jquery selectors work only when in a function directly in the main object/module.
I understand that javascript has functional scope so I am suprised that I cannot use the jquery selectors anywhere inside the module.
EDIT:
I want to be able to directly set all of my configs inside the configs object using jquery selectors. This way i keep all the messy stuff inside one place and can then access configs.whatever throughout the rest of the module. At the moment jquery selectors do not work inside the configs module.
var OB = function() {
var configs = {
'mode' : 'test',
'numOfSelects' : $('.mySelect').find('select').length, // This doesnt work
}
var getMode = function() {
return configs.mode;
}
function init() {
alert(configs.numOfSelects); // This alerts 0 until the following line
alert($('.mySelect').find('select').length); // This correctly alerts 2
};
var handlers = {
successHandler : function() {
alert("Success");
},
errorHandler : function() {
alert("error");
}
}
return {
init : init,
getMode : getMode
}
}( );
$(document).ready(function(){
OB.init();
});
It isn't that jQuery isn't in scope — that's that the code isn't executing when you think it is. The variable config is defined when that anonymous function (var OB = function() {}()) is executed. The DOM isn't ready yet, so that DOM traversal doesn't find anything. When you do the DOM traversal in init(), that isn't executed until it's explicitly called inside the $(document).ready() handler, at which point that DOM is set up. That's the difference you're seeing.
OB() needs to be called after the DOM has completely loaded. Hence the answer by Marcelo, which calls OB() in the ready() method.
EDIT: It's funny that my original answer below was incorrect because I didn't notice two little parentheses at the end of the definition of OB, and it turns out that these are the culprit. You define and then immediately invoke OB, which is before the DOM has been fully loaded. Remove those parentheses and make the change I suggest below.
Calling OB() returns an object with init and getMode, but you haven't called OB(), you've only referred to OB. Try this instead:
$(document).ready(function(){
OB().init();
});
Also, I assume you want to later refer to getMode. In particular, you will to get the copy of getMode that has access to the same local scope that your init() call had access to. To achieve this, you will need to store the result of calling OB() for later use:
var ob;
$(document).ready(function(){
ob = OB();
ob.init();
});
function some_other_function() {
... ob.getMode() ...;
}
I'm having a really rough time wrapping my head around prototypes in JavaScript.
Previously I had trouble calling something like this:
o = new MyClass();
setTimeout(o.method, 500);
and I was told I could fix it by using:
setTimeout(function() { o.method(); }, 500);
And this works. I'm now having a different problem, and I thought I could solve it the same way, by just dropping in an anonymous function. My new problem is this:
MyClass.prototype.open = function() {
$.ajax({
/*...*/
success: this.some_callback,
});
}
MyClass.prototype.some_callback(data) {
console.log("received data! " + data);
this.open();
}
I'm finding that within the body of MyClass.prototype.some_callback the this keyword doesn't refer to the instance of MyClass which the method was called on, but rather what appears to be the jQuery ajax request (it's an object that contains an xhr object and all the parameters of my ajax call, among other things).
I have tried doing this:
$.ajax({
/* ... */
success: function() { this.some_callback(); },
});
but I get the error:
Uncaught TypeError: Object #<an Object> has no method 'handle_response'
I'm not sure how to properly do this. I'm new to JavaScript and the concept of prototypes-that-sometimes-sort-of-behave-like-classes-but-usually-don't is really confusing me.
So what is the right way to do this? Am I trying to force JavaScript into a paradigm which it doesn't belong?
Am I trying to force JavaScript into a paradigm which it doesn't belong?
When you're talking about Classes yes.
So what is the right way to do this?
First off, you should learn how what kind of values the this keyword can contain.
Simple function call
myFunc(); - this will refer to the global object (aka window) [1]
Function call as a property of an object (aka method)
obj.method(); - this will refer to obj
Function call along wit the new operator
new MyFunc(); - this will refer to the new instance being created
Now let's see how it applies to your case:
MyClass.prototype.open = function() {
$.ajax({ // <-- an object literal starts here
//...
success: this.some_callback, // <- this will refer to that object
}); // <- object ends here
}
If you want to call some_callback method of the current instance you should save the reference to that instance (to a simple variable).
MyClass.prototype.open = function() {
var self = this; // <- save reference to the current instance of MyClass
$.ajax({
//...
success: function () {
self.some_callback(); // <- use the saved reference
} // to access instance.some_callback
});
}
[1] please note that in the new version (ES 5 Str.) Case 1 will cause this to be the value undefined
[2] There is yet another case where you use call or apply to invoke a function with a given this
Building on #gblazex's response, I use the following variation for methods that serve as both the origin and target of callbacks:
className.prototype.methodName = function(_callback, ...) {
var self = (this.hasOwnProperty('instance_name'))?this.instance_name:this;
if (_callback === true) {
// code to be executed on callback
} else {
// code to set up callback
}
};
on the initial call, "this" refers to the object instance. On the callback, "this" refers to your root document, requiring you to refer to the instance property (instance_name) of the root document.