I know what the problem is but not sure what's the best option to solve this issue. I have got a callback and I'm not able to access this from it. I don't want to have any variable outside the scope to refer this. Can I pass this as a parameter?
var myModule = Module.create({
init: function() {
ws.subscribe('/topic/notifications', this._wsNotifications, headers);
},
refresh: function() {
},
_wsNotifications: function ( message ) {
this.refresh(); //Error: 'this' is undefined because it's a callback
}
});
One way you can solve this is using function.bind at the source When you specify the callback do
ws.subscribe('/topic/notifications', this._wsNotifications.bind(this), headers);
or cache the this to a variable.
var myModule = Module.create({
self : this,
init: function() {
ws.subscribe('/topic/notifications', this._wsNotifications, headers);
},
refresh: function() {
},
_wsNotifications: function ( message ) {
self.refresh(); //Error: 'this' is undefined because it's a callback
}
});
Give this a try.
var myModule = Module.create({
var self = this;
init: function() {
ws.subscribe('/topic/notifications', this._wsNotifications, headers);
},
refresh: function() {
},
_wsNotifications: function ( message ) {
self.refresh(); //Error: 'this' is undefined because it's a callback
}
});
return interactions;
});
note the creation and use of the self variable instead of the this variable. Using this method will preserve this, even when it would normally change scope.
You can make use of ECMAscript's bind function Function.prototype.bind.
init: function() {
ws.subscribe('/topic/notifications', this._wsNotifications.bind(this), headers);
},
Now, this within _wsNotifications will refer to the object you bound it to.
Related
I have a code like that:
{
// other functions...
myFunction: function( item, options, callback ) {
var self = this;
// Both item and options variables are accessible here.
try {
var content = this.getContent( item );
} catch(ex) {
// Exception handling here....
}
editor.saveContent( content, function() {
// OPTIONS IS UNDEFINED HERE!
// ITEM IS UNDEFINED HERE!
// Callback, instead, is accessible!
if ( callback ) { callback() };
});
}
}
Problem is that inside the saveContent callback, I can access the callback variable, while any attempt in accessing the item, options and content vars are unsuccessful! Why is that?
You should supply the variable you are interrested in to the callback function like this:
editor.saveContent( content, function() {
if ( callback ) { callback(item, options) };
});
Via javascript closure, the item and options variable are available in the saveContent function.
Created fiddle to demonstrate (see console log).
The code you posted should work fine. But if that is really not working then try writing your code like this:
{
// other functions...
myFunction: function( item, options, callback ) {
var self = this;
// Store the reference in "this"
self.item = item;
self.options = options;
self.callback = callback;
// Both item and options variables are accessible here.
try {
self.content = this.getContent( item );
} catch(ex) {
// Exception handling here....
}
editor.saveContent( self.content, function() {
// Use them here
console.log(this.item, this.options, this.content);
// Callback, instead, is accessible!
if ( this.callback ) { this.callback() };
}.bind(self));
}
}
You have written var content inside the try-catch block. So it anyways can't be accessible outside the try-catch block.
editor.saveContent.call(this,content, function(){
//can access any variable defined in its parent scope
});
I am trying to implement an asynchronous request with d3.js inside self-executing anonymous function.
Rather than saying d3.json(url, callback), I use d3.json(url) to first create the request object, then register a "load" event listener with xhr.on before starting the request with xhr.get.
Here is an example, in my object I have a render function that does render charts. I would like to call this render function once my data are loaded from an API.
The call is made properly but inside my render function i am not able to call my parseData function. It seems that I have lost the this context.
The console throw this exception : Uncaught TypeError: this.parseData is not a function
Does I make something wrong ? Thanks
(function() {
var Ploter = {
render: function(jsondata) {
this.parseData(jsondata); // where the error is raised
// draw the line chart
// ....
},
parseData: function(data) {
console.log(data);
},
init: function() {
this.bindEvents();
},
bindEvents: function() {
var self = this;
d3.json(this.DATAURL)
.on("load", this.render)
.on("error", function(error) { console.log("failure!", error); })
.get();
}
Ploter.init();
})();
Instead of passing the function directly, you can wrap it in another function, creating a closure. The inner function will be able to reference the value you stored in self, which refers to the original Ploter you're looking for:
bindEvents: function() {
var self = this;
d3.json(this.DATAURL)
.on("load", function(data) { self.render(data); })
Here is my example object to demonstrate the issue.
Dog = Backbone.Model.extend({
initialize: function () {
},
Speak: function (sayThis) {
console.log(sayThis);
},
CallInternalSpeak: function () {
this.Speak("arf! from internal function.");
},
CallSpeakFromClosure: function () {
this.Speak("arf! fron outside closure.");
var callClosure = function () { // think of this closure like calling jquery .ajax and trying to call .Speak in your success: closure
console.log("we get inside here fine");
this.Speak("say hi fron inside closure."); // THIS DOES NOT WORK
}
callClosure();
}
});
var rover = new Dog;
rover.Speak("arf! from externally called function");
rover.CallInternalSpeak();
rover.CallSpeakFromClosure();
Since you are in Backbone, you can always use Underscore's bind function as well. After you define callClosure, you can wrap it with a proper binding:
callClosure = _.bind(callClosure, this);
The old "self" trick... make a reference to this, call it self, and reference it in the function.
CallSpeakFromClosure: function () {
this.Speak("arf! fron outside closure.");
var self = this;
var callClosure = function () {
console.log("we get inside here fine");
self.Speak("say hi fron inside closure."); // THIS DOES NOT WORK
}
callClosure();
}
This would be better explained in code:
var FileResourceManager = {
LoadRequiredFiles: function (config) {
config = config || {};
this.OnLoading = config.onLoadingCallback;
this.OnComplete = config.onCompleteCallback;
//this works fine here.
if (this.OnLoading) {
this.OnLoading();
}
Modernizr.load([{
load: 'somefile.js',
complete: function () {
//Error in this callback here.
if (this.OnComplete) {
this.OnComplete();
}
}
});
}
};
FileResourceManager.LoadRequiredFiles({
onLoadingCallback: function () {
alert('started');
},
onCompleteCallback: function () {
alert('complete');
}
});
As you can see, in the callback for Modernizr.load's complete event, I want to call the method of the parent/outer object. But this actually became the Modernizr object. How can I access the properties of the outer object inside an event?
I've seen this done in the backbone.js project, by using some form of binding. I'm not sure if I need to write something like this.
var self = this;
Modernizr.load([{
load: 'somefile.js',
complete: function () {
//Error in this callback here.
if (self.OnComplete) {
self.OnComplete();
}
}
});
Modernizr.load([{
load: 'somefile.js',
complete: (function () {
//Error in this callback here.
if (this.OnComplete) {
this.OnComplete();
}).bind(this)
}
});
The this object redefined in the scope of the Modernizr function. It is customary to define a variable called that outside the scope and use it to refer to the outer this.
Alternatively, you could just use the config object, like this:
complete: function () {
if (config.OnComplete) {
config.OnComplete();
}
}
var Test = (function() {
return {
useSub: function () {
this.Sub.sayHi();
},
init: function () {
$(document).ready(this.useSub);
}
};
})();
Test.Sub = (function () {
return {
sayHi: function () {
alert('hi');
}
};
})();
Test.useSub(); // works
Test.init(); // explodes
Above I am trying to create a Test namespace and add an object Sub to it. I was doing fine until I tried using the object in jQuery. The error is "Uncaught TypeError: Cannot call method 'sayHi' of undefined". If there is a better way to do this, I am open to it.
Edit:
Obviously this was demo code. In my real application the solution that I went with because I think it is the most clear is this one:
var Namespace (function () {
return {
init: function () {
$(document).ready(function() {
Namespace.onReady();
}
},
onReady: function() {
alert('Now I am back in the Namespace scope. Proceed as planned');
}
};
})();
Edit2: All jQuery callbacks seem to require they are used in this manner or else the scoping is screwed up.
I think it is a scope problem. If you do
$(document).ready(this.useSub);
then this.useSub will be executed in the window scope (so inside the function, this refers to the window object) and there doesn't exist a Sub attribute.
Try:
init: function () {
var obj = this;
$(function(){obj.useSub()});
}
For some reason it does not work using $(document).ready(function(){obj.useSub()}); but it works with the $() shortcut.
Here is one way
var Test = {
useSub : function () {
Test.Sub.sayHi();
},
init: function () {
$(document).ready(Test.useSub);
},
Sub: {
sayHi: function () {
alert('hi');
}
}
};
in this line:
$(document).ready(this.useSub);
you're passing a reference to a function and the scope is lost- when the function runs, this no longer means Test.