`this` is undefined when calling method from another context - javascript

This is my first time creating OOP for JS. I followed some tutorials but I can't wrap my head around this issue. I know the problem, but i dont know the solution
function NewApp(name){
this.name = name;
this.currentPage = 1;
this.customObjectWithMethods = //init with options and so on
}
NewApp.prototype.logic = function(){
// Note 1.
var app = this
//Note 3.
this.customObjectWithMethods.method{
if(app.currentpage < 3)
// Note 2.
app.navigate(app.logic)
}
}
NewApp.prototype.navigate = function(sender){
var app = this;
this.customObjectWithMethods.method{
app.currentpage++;
this.method(function() {
return app.currentPage === 2;
}, sender(), this.terminate);
}
}
Note 1: I need to create a reference because after that, this doesn't
work anymore to refer to the current object.
Note 2: After the check I want to do some logic in another method and repeat the current function, but when the function runs again it breaks on the method (this.customObjectWithMethods) because this doesn't exists.
Note 3: This is where it breaks because "this" works the first time not the second time.
It gets very complicated like this with the this-keyword, which makes me think that my design may be flawed.
Is there any solution for this problem, or should I refactor it ?

Surely it will become complicated, thiskeyword doesn't always refer to the main object but to the scope where it is used, take a look at Scope and this in JavaScript for further information.
This is your way to go, make a variable that contains your constructor and add these two methods to this variable, after that you can call your functions:
var newApp = function newApp(name){
this.name = name;
this.currentPage = 1;
//Make a reference to your object here
var THIS = this;
this.logic = function(){
var sender = this;
THIS.customObjectWithMethods.method = function(){
if(THIS.currentpage < 3)
THIS.navigate(sender);
}
}
this.navigate = function(sender){
this.customObjectWithMethods.method = function(){
THIS.currentpage++;
this.method(function() {
return THIS.currentPage === 2;
}, sender(), this.terminate);
}
}
}
And this is how to use your constructor and its methods:
var app = newApp("Test");
//Call the first method
app.customObjectWithMethods();
//Thenn call the second one
app.logic();

Some syntax errors & style issues - here is a short correction
var myFunction = function(){
//code here
};
var mySecondFunction = function(){
//code here
};
function NewApp(name){
this.name = name;
this.currentPage = 1;
this.customObjectWithMethods = function(){}; //empty function so calling doesnt resolve in error
}
NewApp.prototype.logic = function(){
this.customObjectWithMethods.method = mySecondFunction.bind(this);
}
NewApp.prototype.navigate = function(sender){
this.customObjectWithMethods.method = myFunction.bind(this);
}
I have moved the 2 functions outside of the constructor Function so they dont get recreated every time you call the constructor functions.
with _.bind(this) the "this"-reference gets passed into the scope of your functions (i think this is more pretty than creating another var).
with
var reff = new NewApp('namename');
you can get started calling your functions now:
ref.logic();
maybe this approach works for you?

Related

How do I wrap a javascript function with dynamic arguments?

I'd like to wrap some dynamically created javascript functions, similar to Daniel's accepted answer here:
How do I store javascript functions in a queue for them to be executed eventually
// Function wrapping code.
// fn - reference to function.
// context - what you want "this" to be.
// params - array of parameters to pass to function.
var wrapFunction = function(fn, context, params) {
return function() {
fn.apply(context, params);
};
}
The difference is I'd like the argument values to be dynamic at time of execution - is it possible to pass a reference to a variable in the arguments, which could be updated after it is wrapped?
Here's what I'd like to do:
// I have a function to be wrapped
var sayStuff = function(a,b) {
console.log(a);
console.log(b);
}
// Variables I'd like to pass
var randomNumberA = 0;
var randomNumberB = 0;
// Wrap the function
var fun = wrapFunction(sayStuff, this, [*reference randomNumberA*,*reference randomNumberB*]);
// variables get changed
randomNumberA = Math.random()*100;
randomNumberB = Math.random()*100;
// Execute the function using current values of randomNumberA & randomNumberB
fun();
If possible I'd like to do this without changing sayStuff, I have a lot of existing functions like this I'm hoping to wrap, which also get used outside of the wrapping, so ideally I'd like to not replace the arguments with an object.
Hope that makes sense, Thanks!
If the function and the variable will be created in the same scope you can just use that:
var randomNumber = 0;
var fun = function(){ alert(randomNumber); }
randomNumber = 10;
// Now this will alert 10, because when fun is executed
// JS looks in his scope to find what randomNumber is.
fun();
This happens because functions in javascript works as Closures, they carry their environment with them. See: https://en.wikipedia.org/wiki/Closure_(computer_programming)
So if randomNumber will be changed out of the scope where you bind that function, you need to use an object, this is because in javascript we don't have "pointers" or references to pass by. One way is using a object.
function giveMeAFunction(){
var params = { randomNumber: 0 }
var fun = function(){ alert(scope.randomNumber); }
return {fun: fun, scope: scope};
}
var paramsAndFun = giveMeAFunction()
// Now you can change the variables in the scope and call the function
paramsAndFun.params.randomNumber = 10;
paramsAndFun.fun(); // Will alert 10
// Now if you replace the entire params object it will not work
// This is because you will replacing it with a new object while
// The one that is referenced in the scope where fun was created is
// the old one.
paramsAndFun.params = { randomNumber: 15 };
paramsAndFun.fun(); // will still alert 10
Now let's get to binding part of the problem.
There is already Function.prototype.bind function to help you with that.
For example:
var sayStuff = function(opts) {
alert(otions.randomNumber);
}
var options = { randomNumber: 0 };
var fun = sayStuff.bind(this, options);
options.randomNumber = 10;
fun(); // Will print 10
There is a lot going on here. Sorry if I made everything confuse.
If the dynamic arguments are defined in the context argument, a solution can be based passing the name of the variables and then, at execution time, calculate its current value:
var wrapFunction = function(fn, context) {
var xArg = arguments;
return function() {
var argsArray = [];
for (var i = 2; i < xArg.length; i++) {
argsArray.push(context[xArg[i]]);
}
fn.apply(context, argsArray);
};
}
var sayStuff = function() {
for (var i = 0; i < arguments.length; i++) {
console.log('sayStuff func: ' + arguments[i]);
}
}
var randomNumber1 = 0;
var randomNumber2 = 0;
var fun = wrapFunction(sayStuff, this, 'randomNumber1', 'randomNumber2');
randomNumber1 = Math.random()*100;
randomNumber2 = Math.random()*100;
console.log('randomNumber1: ' + randomNumber1);
console.log('randomNumber2: ' + randomNumber2);
fun();

How to access an instance variable within a Promise callback?

Let's say I have a basic dumb javascript class :
var FunctionX = function(configs) {
this.funcConfigs = configs;
}
FunctionX.prototype.getData = function() {
return $.get('/url');
}
FunctionX.prototype.show = function(promise) {
console.log(this.funcConfigs); // <-- this here is the promise itself, I'm looking to get the instance's configs
}
FunctionX.prototype.setup = function() {
this.GetData().then(show);
}
var f = new FunctionX({ "a": "b" });
f.setup();
Now I'm trying here in the show function to access the instance variable "funcConfig". "This" is the promise, and "funcConfigs" directly returns undefined.
I tried to resolve this issue with a .resolveWith(this) but it does not solve this issue.
How can I access the instances variables in this scope context?
In agreement with user2864740, the issue is most likely caused because this is not what you expect it to be when show is invoked as a callback. To make this work properly, you need to capture the proper this in a closure (e.g. var that = this;), and invoke it explicitly.
In other words...
FunctionX.prototype.setup = function() {
var that = this;
this.getData().then(function () {
that.show();
});
}
EDIT: For a slightly cleaner syntax (using underscore.js):
FunctionX.prototype.setup = function() {
var that = this;
this.getData().then(_.bind(this.show, this));
}

"this" in JavaScript. reference to an object inside a factory

I wrote some classes in javascript and i wrote a few FunctionFactories for them. But I think that i have done some things wrong.
I renamed some things of my code, that you can understand it better.
So the first class is the "root"-class. this class has children, which i add later.
function templateRoot(){
this.id = "root";
this.parent = null;
this.children = [];
this.editable = true; // bla
this.render = function(){
$.each(this.children,function(i,obj){
this.children[i].render();
var baseButtons = this.getBaseButtons();
$('#'+this.id).append(baseButtons);
});
};
this.addBase = addBaseFactory(this);
};
The attribute "addBase" gets a function which is delivered by addBaseFactory...
function addBaseFactory(that){
return function(){
var newBase = new base(that.children.length, that.id);
that.children.push(newBase);
};
}
...and the base class which is used to generate a object in "addBase" looks like this:
function base(count, parent){
this.id = parent+"_base"+count;
this.parent = parent;
this.children = [];
this.remove = function (){
$('#'+this.id).remove();
};
this.render = baseRenderFactory(this);
this.addChild = addChildFactory(this);
this.getBaseButtons = function(){
var addAttributeButton = new $("<button>+ Attribute</button>").button();
var addTextButton = new $("<button>+ Text</button>").button();
return [addAttributeButton, addTextButton];
};
}
The problem now is. When i debug the code and set a breakpoint within the "render" function of the root-object. Then i can see, that "this" is not the root but the "base" object. And i cannot figure out why it is like that because the "root" object is the owner of this function, and my base has an own render function which is not called directly there.
So even the "this" in the line
$.each(this.children,function(i,obj){
Refers to the "base" object. But the "this" is inside the "root" object...
Hope you can help me :-)
EDIT:
The code to let it run:
var test = new templateRoot();
test.addBase();
test.render();
EDIT 2:
"that" in "addBaseFactory" refers to the correct "base" object.
I found your explanation pretty confusing, so I may have misinterpreted what you're trying to do, but I think you expect this within your nested functions to the same object as the this in the outer templateRoot() function. That's not how this works in JavaScript. Nested functions don't inherit the same this as the containing function - each function has its own this object that is set depending on how the function is called.
Here's one possible solution, which uses the fact that nested functions can see variables from their containing function(s):
function templateRoot(){
var self = this; // save a reference to this for use in nested functions
this.id = "root";
this.parent = null;
this.children = [];
this.editable = true; // bla
this.render = function(){
$.each(self.children,function(i,obj){
self.children[i].render();
var baseButtons = this.getBaseButtons();
$('#'+self.id).append(baseButtons);
});
};
this.addBase = addBaseFactory(this);
};
A detailed explanation about how this works in JS can be found at MDN.
Wouldn't this render its childerens children, since jquery would send each child as this?
this.render = function(){
$.each(this.children,function(i,obj){
this.children[i].render();
var baseButtons = this.getBaseButtons();
$('#'+this.id).append(baseButtons);
});
};
Btw in what scope is addBaseFactory called? Because I think the "this" in the base, will refer to that scope.

JS and methods in class emulation

I am new to this so I'll try to be as detailed as possible.
I am creating a JS class using functions.
function Bandstar(p, id)
{
var numberOfSides, Xcenter, Ycenter;
var canvas;
var circlesInt;
var linesInt;
var lines;
var linesInt2;
var linesMidToEdge;
.....
When it comes to methods, I just declare them like this:
this.IAset = function(a)
{
for (var i =0; i<= this.numberOfSides-1;i++)
{
if (parseInt(a[i]) == a[i])
this.circles[i].value = a[i];
else
return false;
}
this.IArepaint();
return true
}
and that's pretty much it.
The problem now is that, when I am creating a specific method:
this.moveFunc = function(e)
{
var p = e.target;
this.IAmove(p);
};
with the method IAmove being declared as follows:
this.IAmove = function(p)
{
var m = (p.oTop - this.Ycenter ) / (p.oLeft - this.Xcenter);
var linea = m * ( p.left - p.oLeft) + p.oTop;
var ejeX = p.left;
...
}
The compiler just keeps throwing this error:
Uncaught TypeError: Object function (e) { var p = e.target; this.IAmove(p); } has no method 'IAmove'
So, the thing is that I am declaring moveFunc as a function but then when I try to get to use this property, it doesn't actually use the instance of the class above it, (Bandstar), but of itself (and obviously moveFunc doesn't have a method called IAmove, the class on which it is being created is)...
I thought this was just like it worked, but I must not be getting the concept of heritage and class morphology in JS right.
How can I access a method in a class from another method inside that same class? If I just write IAmove(p) it will just say the method IAmove() is undefined (because it isn't, it's part of the Bandstar namespace.
I know it must be something stupid that I'm not seeing. Any suggestions?
The context inside your function may change based on your call. So this could not be the original object.
You should add a private var like:
function Bandstar(p, id)
{
var numberOfSides, Xcenter, Ycenter;
var canvas;
var that = this; // add this line
and use it when you try to call function (or get properties) of original object
this.moveFunc = function(e)
{
var p = e.target;
that.IAmove(p); // change this line
};
for be sure to point to original object.
EDIT: According with comments you can read more infos in Stuart's answer
You probably wants to add these methods to your Bandstar prototype.
function Bandstar(p, id)
{
// ...
}
Bandstar.prototype.IAset = function(a)
{
// ...
}
Bandstar.prototype.moveFunc = function(e)
{
var p = e.target;
this.IAmove(p);
};
This way your methods keep the Bandstar context, and this will remain Bandstar instance reference.
Have a look at Object.prototype reference
Assuming all of those methods are defined in the constructor the syntax is correct. The following fiddle illustrates it working, the code is based on a simplified version of your code...
function Test() {
this.move = function(p) {
document.write("Moved to: " + p);
};
this.another = function(e) {
this.move(e.target);
};
}
var test = new Test();
test.another({target: "The moon"});
The cause of the issues is likely to be one of two three things:
How the method moveFunc is called
When the method moveFunc is called
How the method IAmove is called (it may not be the moveFunc function that is causing that error).
The this keyword will reference the scope of the function which is typically the owner of the method...
var bandstar = new Bandstar(p, id);
bandstar.moveFunc(e); // this should be bandstar
The only reasons that might not be the case is if you are explicitly binding the moveFunc function to another object or a more common situation is that the function is being called without being attached to the owner...
var bandstar = new Bandstar(p, id);
var moveFunc = bandstar.moveFunc(e);
moveFunc(e); // this will now be window
The this keyword will default to window if the owner is detached from it. You can bind the function using.
var bandstar = new Bandstar(p, id);
var moveFunc = bandstar.moveFunc(e);
moveFunc.bind(bandstar);
moveFunc(e); // this will now be bandstar again
In the second issue both methods must be defined in the constructor before the moveFunc function can be called
this.moveFunc = function(e) { ... }
this.moveFunc(e); // Will throw an error that IAmove does not exist in the object
this.IAmove = function(p) { ... }
You can log the this object using console.log(this); to find out what it is.
It looks as though from your error that you are trying to call IAmove on the moveFunc function. For this error to be caused you would need to have called moveFunc with itself as the owner or the bound object... Which is not a particularly likely.
I have a hunch that the error may not actually be related to the this.IAmove(p); call in the moveFunc as assumed. Post the stack trace and we can use that to find where the error occurred.

Dynamically firing a named-spaced method via JavaScript

I have multiple external JavaScripts that are namespaced based on the section of the site. I am trying to dynamically fire methods, but am unable to get the methods to fire. Can anyone tell me what the problem is?
If I add this, the method fires:
Namespace.Something.init()
But when I try to do it like this, nothing happens (note: namespace equals Namespace.Something and functionname equals init):
namespace[functionname]();
Unless you want to use eval which I am sure you don't the following works.
This assumes that all your methods are the same level deep i.e namespace.somename.somemethod
var Namespace = {
Something: {
init: function() {
console.log('init called');
}
}
};
Namespace.Something.init();
var namespace = "Namespace";
var section = "Something";
var method = "init";
this[namespace][section][method]();
as Namespace is part of the global scope you can access it from this[namespace]
I asked the same question a few weeks ago, though I think I phrased it slightly differently. See this.
Basically, you need to parse the string functionname one piece at a time.
By the way, using the walk_path code from that answer, here's a general purpose function I wrote to run a function from a string including arguments.
// run an arbitrary function from a string. Will attempt to parse the args from parenthesis, if none found, will
// use additional arguments passed to this function.
utils.runFunction = function (funcdef) {
var argPos = funcdef.indexOf('(');
var endArgPos = -1;
var args = undefined;
var func = funcdef;
if (argPos > 0) {
endArgPos = funcdef.indexOf(')', argPos);
if (endArgPos > 0) {
args = funcdef.substring(argPos + 1, endArgPos).split(',');
func = funcdef.substring(0, argPos - 1);
}
} else {
args = Array.prototype.slice.call(arguments, 1);
}
var func = walk_path(window, func);
return !args ? func() : func.apply(null, args);
};
var methodName = 'Namespace.Something.init';
var methodParts = methodName.split('.');
var method = this;
for (var i=0; i < methodParts.length; i++) {
method = method[methodParts[i]];
};
method(the arguments you want);

Categories

Resources