I'm using a js lib, which will create a global variable "AV" used everywhere in a WebApp.
But I want to create a "sandbox"(not actually a strict sandbox because of no secure concern) to use multiple different "AV"s in one WebApp.
I wrote a wrap for browser below and it works.
var AVContexts = {
App1: null,
App2: null
}
var ContextLoader = function (appId, appKey) {
this.AV = null;
this.runInThis = function (script) {
eval(this.script);
this.AV.initialize(appId, appKey);
}
this.loadContext = function (appId, appKey) {
$.ajax({
url: 'js/av.js',
dataType: "text",
context: this
}).done(function (data) {
this.script = data;
this.runInThis.call(this);
}).fail(function () {
console.log('failed');
});
}
this.loadContext(appId, appKey);
}
AVContexts.App1 = new ContextLoader(
"[appid]",
"[appkey]"
);
AVContexts.App2 = new ContextLoader(
"[appid]",
"[appkey]"
);
// Do something
var TestObject = AVContexts.App1.AV.Object.extend("TestObject");
var testObject = new TestObject();
testObject.save({foo: "bar"}, {
success: function (object) {
alert("AVOS Cloud works!");
}
});
But when I move it to nodejs. An error occurred at
this.runInThis.call(this);
ERROR: Cannot call method 'call' of undefined
Any idea?
You should cache the "this", like this:
var me = this;
before you use $.ajax. and then,
$.ajax().done(function() {
me.runInThis.call();
});
If you want to know the reason,
run
console.log(this);
before
this.runInThis.call(this);
you will know the reason.
Related
I'm implementing a web map client built on top of OpenLayers3 which should be able to connect to multiple WMS servers, ask for WMS Capabilities and show layers advertised by servers.
var MyMapClient = function(params) {
this.wms_sources_ = params.wms_sources;
this.wms_capabilities_ = [];
}
MyMapClient.prototype.parse_capabilities = function(index) {
var capabilities = this.wms_capabilities_[index];
// do something with capabilities
}
MyMapClient.prototype.load_wms_capabilities = function() {
var parser = new ol.format.WMSCapabilities();
jQuery.each(this.wms_sources_, (function (index, wms_source) {
console.log("Parsing " + wms_source.capabilities_url);
jQuery.when(jQuery.ajax({
url: wms_source.capabilities_url,
type: "GET",
crossDomain: true,
})).then((function (response, status, jqXHR) {
var result = parser.read(response);
console.log("Parsed Capabilities, version " + result.version);
this.wms_capabilities_[index] = result;
return index;
}).bind(this)).then(this.parse_capabilities.bind(this));
}).bind(this));
};
The code above works fine but I have to bind(this) every time I want to call a function which needs access to "private" variables of MyMapClient's instance. Isn't there a better way to access instance internals consistently, without sacrificing readability?
I would say to use the best of both worlds, that is, a local variable holding the correct scope, and calls to bind() where needed:
MyMapClient.prototype.load_wms_capabilities = function() {
var parser = new ol.format.WMSCapabilities(),
_this = this;
jQuery.each(this.wms_sources_, function (index, wms_source) {
console.log("Parsing " + wms_source.capabilities_url);
jQuery.when(jQuery.ajax({
url: wms_source.capabilities_url,
type: "GET",
crossDomain: true,
})).then(function (response, status, jqXHR) {
var result = parser.read(response);
console.log("Parsed Capabilities, version " + result.version);
_this.wms_capabilities_[index] = result;
return index;
}).then(
function() { return _this.parse_capabilities(); }
// or else
// _this.parse_capabilities.bind(_this)
// pick the one you like more
);
});
};
You can "hard bind" a method like this:
function Foo() {
this.bar = this.bar.bind(this);
}
Foo.prototype.bar = function() {
return console.log(this.baz);
};
Incidentally, that's what CoffeeScript compiles to when doing this:
class Foo
bar: =>
console.log #baz
The => operator causes this preservation of context.
This is my object definition:
function DrilledLayer(sourceLayerName, sourceTableName, targetLayerName, targetFieldName, operators, baseLayer=false) {
this.sourceLayerName = sourceLayerName;
this.sourceTableName = sourceTableName;
this.targetLayerName = targetLayerName;
this.targetFieldName = targetFieldName;
this.operators = operators;
this.baseLayer = baseLayer;
this.targetLayerId;
this.drilledLayerId;
this.selectedCode;
this.redraw = false;
this.getTargetLayerId(); //this function must initialize this.targetLayerId
}
DrilledLayer.prototype.getTargetLayerId = function(){
$.soap({
url: 'https://url/version_4.8/services/MapService',
method: 'getLayersIdByName',
appendMethodToURL: false,
data : {
mapInstanceKey: mapKey,
layerName: this.targetLayerName,
},
error: function(){
alert("error getLayersIdByName");
},
success: function(soapResponse){
layerId = soapResponse.toJSON().Body.getLayersIdByNameResponse.getLayersIdByNameReturn.getLayersIdByNameReturn.text;
this.targetLayerId = layerId;
}
});
}
This is how I create the object:
drillCs = new DrilledLayer("Drilled CS", "Cs_Franco_General.TAB", "RA_General", "Code_RA", "=")
If I look into drillCs object there is no targetLayerId property defined, but I know the soap request were made successfuly. Why?
this in JavaScript is mostly set by how a function is called. this during the success callback won't be the same as this during your call to your getTargetLayerId function, you have to remember it.
In this case, the easiest way is probably with a variable:
DrilledLayer.prototype.getTargetLayerId = function(){
var layer = this; // <=== Set it
$.soap({
url: 'https://url/version_4.8/services/MapService',
method: 'getLayersIdByName',
appendMethodToURL: false,
data : {
mapInstanceKey: mapKey,
layerName: this.targetLayerName,
},
error: function(){
alert("error getLayersIdByName");
},
success: function(soapResponse){
layerId = soapResponse.toJSON().Body.getLayersIdByNameResponse.getLayersIdByNameReturn.getLayersIdByNameReturn.text;
layer.targetLayerId = layerId; // <=== Use it
}
});
}
More (on my blog):
You must remember this
Separately, of course, you won't see the properly until the async callback fires (which will be some time after the new call returns), but you seem to be comfortable with the async aspect of this.
I am writing a class in JavaScript for the first time and I am having some trouble writing new data to a class variable. I've been trying all sorts for hours but nothing seems to work!
function ClassName(productId) {
//create variables
this.productId = productId;
this.shop = [];
this.product = [];
//method that calls for response. On success will return {"status" : "success", "shop" : "someshop.com"}
this.auth = function() {
$.ajax({
url: "http://website.com/api/auth/",
dataType: "jsonp",
success: function(data) {
authCallback(data); //use callback to handle response
},
error: function() {
console.log("bad auth");
}
});
}
var authCallback = function(r) {
//using console.log(r) output the response OK
this.shop = r; //this runs with no errors
}
}
Now, as yo can see in the authCallback method I'm setting this.shop = r; but then if i refer back to this variable its still at its default value of [] .
var class = new ClassName(1);
class.auth();
console.log(class.shop); //this outputs []
I've also tried this in the Javascript console writing each line after each stage had been completed(waited for a response from class.auth() and output from authCallback() before then calling console.log(class.shop);
So, what am I doing wrong? Why isn't the variable updating to its new value?
When you just write:
authCallback(data);
then within authCallback you will have the wrong value of this, it'll either be null or the global object (depending on whether you're in strict mode or not).
Use:
success: authCallback.bind(this)
to ensure that this inside the callback actually represents your object.
You should also note that you cannot access this.shop until after the callback has completed. A more idiomatic implementation using modern jQuery techniques would be this:
this.auth = function() {
return $.ajax({
url: "http://website.com/api/auth/",
dataType: "jsonp"
}).done(this.authCallback.bind(this)).fail(function() {
console.log("bad auth");
});
};
this.authCallback = function(r) {
this.shop = r;
return this;
}
followed by:
var clazz = new ClassName(1);
clazz.auth().then(function(c) {
console.log(c.shop);
});
The answer must be obvious but I don't see it
here is my javascript class :
var Authentification = function() {
this.jeton = "",
this.componentAvailable = false,
Authentification.ACCESS_MASTER = "http://localhost:1923",
isComponentAvailable = function() {
var alea = 100000*(Math.random());
$.ajax({
url: Authentification.ACCESS_MASTER + "/testcomposant?" + alea,
type: "POST",
success: function(data) {
echo(data);
},
error: function(message, status, errorThrown) {
alert(status);
alert(errorThrown);
}
});
return true;
};
};
then I instanciate
var auth = new Authentification();
alert(Authentification.ACCESS_MASTER);
alert(auth.componentAvailable);
alert(auth.isComponentAvailable());
I can reach everything but the last method, it says in firebug :
auth.isComponentAvailable is not a function
.. but it is..
isComponentAvailable isn't attached to (ie is not a property of) your object, it is just enclosed by your function; which makes it private.
You could prefix it with this to make it pulbic
this.isComponentAvailable = function() {
isComponentAvailable is actually attached to the window object.
isComponentAvailable is a private function. You need to make it public by adding it to this like so:
var Authentification = function() {
this.jeton = "",
this.componentAvailable = false,
Authentification.ACCESS_MASTER = "http://localhost:1923";
this.isComponentAvailable = function() {
...
};
};
Another way to look at it is:
var Authentification = function() {
// class data
// ...
};
Authentification.prototype = { // json object containing methods
isComponentAvailable: function(){
// returns a value
}
};
var auth = new Authentification();
alert(auth.isComponentAvailable());
Im trying to develop a class in JavaScript I can use to access a load of data that is gathered by an AJAX request easily. The only problem is I need to make the members of the class accessible only once the AJAX call is complete. Ideally what I would like to end up is something where by I can call this in a script:
courses.getCourse('xyz').complete = function () {
// do something with the code
}
And this will only fire after the AJAX call has been complete and the data structures in the "class" are ready to be used. Ideally I dont want to have to create a .complete member for every function in the class
Here is the "class" I am trying to make so far:
var model_courses = (function() {
var cls = function () {
var _storage = {}; // Used for storing course related info
_storage.courses = {}; // Used for accessing courses directly
_storage.references = new Array(); // Stores all available course IDs
var _ready = 0;
$.ajax({
type: "GET",
url: "data/courses.xml",
dataType: "xml",
success: function(xml) {
$(xml).find("course").each(function() {
_storage.courses[$(this).attr('id')] = {
title : $(this).find('title').text(),
description : $(this).find('description').text(),
points : $(this).find('points').text()
}
_storage.references.push($(this).attr('id'))
})
}
})
console.log(_storage.courses)
}
cls.prototype = {
getCourse: function (courseID) {
console.log(cls._storage)
},
getCourses: function () {
return _storage.courses
},
getReferences: function (),
return _storage.references
}
}
return cls
})()
At the moment getCourse will be fired before the AJAX request is complete and obviously it will have no data to access.
Any ideas will be greatly appreciated, im stuck on this one!
jQuery already handles this for you using deferred objects, unless i'm misunderstanding what you are looking for.
var courses = {
getCourse: function (id) {
return $.ajax({url:"getCourse.php",data:{id:id});
}
};
courses.getCourse("history").done(function(data){
console.log(data);
});
I know this isn't exactly what you are looking for, I'm hoping it's enough to push you in the right direction. Deferred objects are awesome.
The following changes allow you to make the AJAX request just once and you can call your function like
courses.getCourse('xyz', function(course){
// Use course here
});
Here are the changes
var model_courses = (function() {
// This is what gets returned by the $.ajax call
var xhr;
var _storage = {}; // Used for storing course related info
_storage.courses = {}; // Used for accessing courses directly
_storage.references = []; // Stores all available course IDs
var cls = function () {
xhr = $.ajax({
type: "GET",
url: "data/courses.xml",
dataType: "xml",
success: function(xml) {
$(xml).find("course").each(function() {
_storage.courses[$(this).attr('id')] = {
title : $(this).find('title').text(),
description : $(this).find('description').text(),
points : $(this).find('points').text()
}
_storage.references.push($(this).attr('id'))
});
}
});
}
cls.prototype = {
// Made changes here, you'd have to make the same
// changes to getCourses and getReferences
getCourse: function (courseID, callback) {
if (xhr.readyState == 4) {
callback(_storage.courses[courseID]);
}
else {
xhr.done(function(){
callback(_storage.courses[courseID]);
})
}
},
getCourses: function () {
return _storage.courses
},
getReferences: function (),
return _storage.references
}
}
return cls
})()
As a side note, your module pattern will not work very well if you need to instantiate two of these model_courses objects, since the storage objects are all shared in your self calling function's closure. You usually don't mix the module pattern with prototypes (returning a constructor from a module), unless you really know what you are doing, that is, the shared closure variables work as static properties of your class.
This is what I would do if I were you (since you really want private variables)
function ModelCourses() {
var storage = {
courses: {},
references: []
};
var xhr = $.ajax({
type: "GET",
url: "data/courses.xml",
dataType: "xml",
success: function(xml) {
$(xml).find("course").each(function() {
storage.courses[$(this).attr('id')] = {
title : $(this).find('title').text(),
description : $(this).find('description').text(),
points : $(this).find('points').text()
}
storage.references.push($(this).attr('id'))
})
}
});
this.getCourse = function(courseId, callback) {
function getCourse() {
callback(storage.courses[courseID])
}
if (xhr.readyState == 4) {
getCourse();
}
else {
xhr.done(getCourse);
}
};
}
in getStorage either add a check to see if there is any data to pilfer (preferred), or make the "actual" method private than publicize it when it has items it can access. (I would recommend the first though otherwise you'll get exceptions about calling a method that doesn't exists on an object).
You can define a function getData that would perform the ajax request and that would take the getCourse as a callback.
The getData could possibly store locally the result of the Ajax call and test the local storage before performing the ajax call.
You could also specify a private member to allow the ajax call to be run only once.
You might want to check underscore.js for some handy tool
Here is a short example code :
cls.prototype.getData = function(callback) {
/*perform ajax call or retrieve data from cache*/
callback()
}
cls.prototype.getCourse = function(id) {
this.getData(function() {
/*do something with the data and the id you passed*/
}
}