typescript class cannot find "this" variable? - javascript

I'm using babylonjs library and created a "Building" class with typescript. Using typescript for the whole thing BTW. I create this new "Building" from my main game.ts "Game" class and when trying to access a member of "Building" I get "undefined" variable errors. However this only happens within another class method but seems work correctly in the constructor. I'm assuming it has something to do with the "this" scoping in javascript/typescript. I have tried modifying the function by doing:
Create = ...(...)=> {
...
I have tried creating the variable via:
private rect: = () => Rectangle
but this still does not work
Is this really an issue with "this" scoping because nothing seems to be working.
Below I marked exactly where this variable works and where this doesnt work.
class Building {
private rect : Rectangle
private buildingMesh:string[]
private buildingId:string
constructor(rect: Rectangle, id:string) {
this.rect = rect
console.log("TL in b const: " + this.rect.topLeft.x) // <--- This works here
this.buildingId = id
}
Create(scene:BABYLON.Scene) {
BABYLON.SceneLoader.ImportMesh(this.buildingId, "models/","tree.babylon", scene, function (newMeshes) {
var idx = 0
console.log("TL in b: " + this.rect.topLeft.x) // <--- this gives me undefined
var wall =newMeshes[0].createInstance(this.buildingId + idx)
wall.position.x = this.rect.topLeft.x
wall.position.y = this.rect.topLeft.y
this.buildingMesh.push(this.buildingId + idx)
idx++
});
}
}

I guess that you are almost there. Arrow function ( => ) syntax is what we need, but even on the BABYLON.SceneLoader.ImportMesh call:
BABYLON.SceneLoader
.ImportMesh(this.buildingId, "models/","tree.babylon", scene,
function (newMeshes) {
...
// here we do not have this kept by TS for us
});
we should use
BABYLON.SceneLoader
.ImportMesh(this.buildingId, "models/","tree.babylon", scene,
(newMeshes) => {
...
// here the magic would happen again
// and compiler will keep this to be what we expect
});

Related

javascript : problem with function.bind(object) - "this" stays global object

I have a problem with the binding of functions in Javascript.
Be sure that I read all StackOverflow's answers I could find
(like this one),
and followed the instructions and examples of
Mozilla's Developpers guides
here is the relevant part of my code :
class Collection extends Array {
constructor (...args) {
super(...args)
}
each (callback) {
this.forEach(element => {
callback.bind(element)(element)
// bind the function THEN call it with element as argument
// but I also tried :
// callback.bind(element)()
// callback.call(element, element)
// let bound = callback.bind(element); bound()
})
}
}
//the tests :
let el1 = {x:1, y:"somevars"}
let el2 = {x:42, y:"another"}
let col = new Collection()
col.push(el1)
col.push(el2)
// the test
col.each(element => console.log(Object.keys(this)))
// and I get ['console', 'global', 'process' ...] all the global variables
// instead of ['x','y'] which is what I want
I really don't understant why it is'nt working...
for context, it is to solve an interesting
kata on Codewars,
not a matter of life and death.
Ok so as pointed by #Teemu, arrow functions can't be bound ...
but with that insight, I could look for a way to bypass this and found
another StackOverflow's post
that gives a trick :
(copy-pasted from the post)
function arrowBind(context, fn) {
let arrowFn;
(function() {
arrowFn = eval(fn.toString());
arrowFn();
}).call(context);
}
arrowBind(obj, () => {console.log(this)});
this works just fine, the new this is the context...
But doesn't solve the puzzle in my case ( 'having is not defined') I need to look further

Why am I getting "potentially invalid reference access to a class field via this in a nested function" error

In vanilla JS, my code would work fine. For this case, I'd like to componentize my Wall class which's supposed to display the image in the browser that the user has uploaded. Again, this works normally in vanilla JS but not JSX.
I'm getting a potentially invalid reference access to a class field via this in a nested function on the document.querySelector("#file-input").addEventListener("change", this.previewImages); line which I think is causing the issue.
What am I doing wrong and how can I fix it?
import React, {Component} from 'react';
class Wall extends Component {
constructor(props) {
super(props);
this.previewImages = this.previewImages.bind(this);
}
previewImages() {
let preview = document.createElement("div");
if (this.files) {
[].forEach().call(this.files, readAndPreview());
}
function readAndPreview() {
if (!/\.(jpe?g|png|gif)$/i.test(file.name)) {
return alert(file.name + " is not an image");
}
let reader = new FileReader();
reader.addEventListener("load", () => {
let image = new Image();
image.height = 100;
image.title = file.name;
image.src = this.result;
let date = Date.now();
let d = new Date(parseInt(date, 10));
let ds = d.toString("MM/dd/yy HH:mm:ss");
console.log(ds);
let initialCountOfLikes = 0;
let zeroLikes = document.createElement("h1");
let zeroLikesTextNode = zeroLikes.createTextNode(initialCountOfLikes + " likes");
zeroLikes.appendChild(zeroLikesTextNode);
preview.appendChild(image); // makes image appear
preview.appendChild(zeroLikes); // makes like count appear
image.ondblclick = function() {
if (initialCountOfLikes === 0) {
console.log("Inside if block");
initialCountOfLikes++;
console.log("initialCountOfLikes++ => " + initialCountOfLikes);
} else if (initialCountOfLikes === 1) {
console.log("inside second else if block");
initialCountOfLikes--;
console.log("initialCountOfLikes-- => " + initialCountOfLikes);
}
zeroLikesTextNode.nodeValue = initialCountOfLikes + " likes";
};
});
reader.readAsDataURL(file);
document.querySelector("#file-input").addEventListener("change", this.previewImages);
}
}
render() {
return (
<div id="file-input-wrapper">
<input type="file" />
<label htmlFor="file-input" id={"LblBrowse"} />
</div>
);
}
}
export default Wall;
The warning is telling you that using this in JavaScript frequently has confusing implications, specifically when used inside a function nested inside another function. this stops referring to the class, and instead refers to the scope of your nested function.
In your case, this probably is a legitimate problem (I think) because you have your class, Wall, which has a method previewImages() and a property files. Within that function, you have instantiated a new function, readAndPreview(), inside which you specify this.previewImages as a function callback to the addEventListener function.
They're saying you're potentially using this.previewImages incorrectly, because you're writing functions in traditional JavaScript syntax, function foo() { ... }, where this keeps being redefined in each child function call. In your case, I believe that this is referring to the context of readAndPreview(), and hence cannot access the method this.previewImages() since this doesn't refer to your parent class, Wall.
People used to do things like, make a var that = this; on the parent class, and you'd know that that always meant the parent class.
But now, with ES6 lambda functions using the "fat arrow" syntax () => { } you can access this and know it's referring to the parent scope.
I believe you can refactor your class to change previewImages() { into previewImages = () => { and know that this will refer to the class. You'll have to do the same with function readAndPreview() {. Change it to const readAndPreview = () => {. If you're setting it to a variable, though, I think you'll have to move it above the place you call it, though. e.g. above
if (this.files) {
[].forEach().call(this.files, readAndPreview());
}
I faced this error in Angular 8.
I used the Arrow function instead of regular functions to solve.
In your case.
readAndPreview = () => { ... }
This might solve your problem.
Using of arrow function may help you. Arrow functions don't have their own bindings to this, arguments or super.

Access an instance from a function outside of the object constructor

I have a problem I can't understand after a lot of attempts to solve it.
To help you understand, there are 2 classes (Game and Board), and a third file with the jQuery keypress controls. Game is about the logic of the game, and Board about the display.
Here is a part of the code I hope sufficient to understand.
// GAME CLASS
function Game(width, height) {
this.width = width;
this.height = height;
this.forbiddenPosition = [];
this.chartBoard = this.resetBoard();
this.generateGame();
}
Game.prototype.generateGame = function () {
this.player1 = new Player("Joueur 1", 100, dagger);
this.player2 = new Player("Joueur 2", 100, dagger);
const playerArray = [this.player1, this.player2];
}
Game.prototype.getPlayer1 = function () {
return this.player1;
};
Game.prototype.getPlayer2 = function () {
return this.player2;
};
Game.prototype.switchTurn = function (player1, player2) {
console.log(player1);
console.log(player2);
};
// BOARD CLASS
const ctx = $('#board').get(0).getContext('2d');
function Board (width, height) {
this.width = width;
this.height = height;
this.game = new Game(this.width, this.height);
this.displayInfoPlayers(this.game.getPlayer1(), this.game.getPlayer2());
}
Board.prototype.displayInfoPlayers = function (player1, player2) {
$('.canvas-side__left').css('visibility', 'visible');
$('.canvas-side__right').css('visibility', 'visible');
$('.canvas-side__left').addClass('animated slideInLeft');
$('.canvas-side__right').addClass('animated slideInRight');
$(".canvas-side__left").html("<h2 class='canvas-side--title'>" + player1.name + "</h2><p class='canvas-side--health'>" + player1.health + "</p><p class='canvas-side--health'>" + player1.weapon.name + "</p>");
$(".canvas-side__right").html("<h2 class='canvas-side--title'>" + player2.name + "</h2><p class='canvas-side--health'>" + player2.health + "</p><p class='canvas-side--health'>" + player2.weapon.name + "</p>");
};
// CONTROL
$(document).on('keypress', function (e) {
if (e.which == 13) {
Game.prototype.switchTurn(Game.prototype.getPlayer1(), Game.prototype.getPlayer2());
e.stopPropagation();
}
});
Board class is linked to Game class and so uses this. The control using jQuery code are in a third file and not into a class.
When I press Enter, I get undefined for player1 and 2. I tried different ways to call the getter functions and nothing works. I also tried to put the controls inside the Game file and still nothing.
I get either undefined or getPlayer1() is not a function.
I am looking for a way to call these getter functions from everywhere so I can use player1 and 2 which I need to move on the board.
There are several issues there.
The keypress event handler is using Game.prototype, not an instance of Game. You want to be using an instance of Game you've created and stored somewhere. Game.prototype doesn't have the player1 and player2 properties. They're added to instances of Game by the Game constructor. Nothing ever adds them to Game.prototype (which is correct, they shouldn't be on the prototype).
There's no need for getPlayer1, etc. You can directly access player1 and player2. (It's possible to make player1 and player2 private and only provide accessors for them, but it's a bit complicated at the moment and probably not something you want to take on yet.)
Within Game methods, you need to consistently use this.player1 and this.player2, don't pass the players around.
It seems odd for Board to create an instance of Game. It seems like it should be the other way around.
I suggest stepping back from this task and trying something simpler first (like creating a class, an instance of the class, and using that instance in an event handler), then incrementally adding complexity and making sure at each stage you're clear on what's happening. As you go, you may have more specific questions, which you can post on SO (after thorough research, etc.).
You can do something like this and it should work. Essentially, you prototype the function you're trying to access which is not declared until after the constructor.
class Test {
constructor() {
this.five = Test.prototype.getFive();
}
getFive() {
return 5;
}
}
let test = new Test();
console.log(test.five); // Returns 5

can't get function's global execution context variable in runtime

i guess this is a pure vanilla's javascript issue.
i needed to override a function showDatepicker , i did it this way :
const Base = (Handsontable.editors.DateEditor as any).prototype as any;
const DateEditorHelper = Base.extend();
DateEditorHelper.prototype.showDatepicker = function (event: any[]) {
Base.showDatepicker.apply(this, event)
this.$datePicker.config(this.getDatePickerConfig());
var offset = this.TD.getBoundingClientRect();
var dateFormat = this.cellProperties.dateFormat || this.defaultDateFormat;
var datePickerConfig = this.$datePicker.config();
var dateStr = void 0;
var isMouseDown = this.instance.view.isMouseDown();
var isMeta = event ? (0, _unicode.isMetaKey)(event.keyCode) : false;
this.datePickerStyle.top = offset.top >= $(window).height() - 224 ? window.pageYOffset + offset.top - 224 + (0, _element.outerHeight)(this.TD) + 'px' : window.pageYOffset + offset.top + (0, _element.outerHeight)(this.TD) + 'px';
this.datePickerStyle.left = window.pageXOffset + offset.left + 'px';
this.$datePicker._onInputFocus = function () { };
datePickerConfig.format = dateFormat;
if (this.originalValue) {
dateStr = this.originalValue;
if ((0, _moment2.default)(dateStr, dateFormat, true).isValid()) {
this.$datePicker.setMoment((0, _moment2.default)(dateStr, dateFormat), true);
}
// workaround for date/time cells - pikaday resets the cell value to 12:00 AM by default, this will overwrite the value.
if (this.getValue() !== this.originalValue) {
this.setValue(this.originalValue);
}
if (!isMeta && !isMouseDown) {
this.setValue('');
}
} else if (this.cellProperties.defaultDate) {
dateStr = this.cellProperties.defaultDate;
datePickerConfig.defaultDate = dateStr;
if ((0, _moment2.default)(dateStr, dateFormat, true).isValid()) {
this.$datePicker.setMoment((0, _moment2.default)(dateStr, dateFormat), true);
}
if (!isMeta && !isMouseDown) {
this.setValue('');
}
} else {
// if a default date is not defined, set a soft-default-date: display the current day and month in the
// datepicker, but don't fill the editor input
this.$datePicker.gotoToday();
}
this.datePickerStyle.display = 'block';
this.$datePicker.show();
}
It's actually the original function's code in which i made some small modification.
i thought the function's inner code variables will be evaluated in runtime (i'm not talking about the inner function scope variables as its arguments or variables declared in it but about the global variables to the execution context).
Before the function get executed there are no errors at all (when the browser reads the function , it doesn't complain about that some variables are undefined) but when it runs , i got this error :
Uncaught ReferenceError: _unicode is not defined
at eval (eval at DateEditorHelper.showDatepicker (module.js?v=64fd5db96274:40281), <anonymous>:1:1)
at e.DateEditorHelper.showDatepicker (module.js?v=64fd5db96274:40281)
The overriden function is running in the same context as where the original function should runs , i don't know why a global variable is undefined.
The original function is a handsontable built-in editor (datePicker). The overriding idea i got it from this thread and this
You say that you copied the code from the original function and made minor changes. The problems seems to be that you're using a variable named _unicode. Are you defining that variable?
When replacing a method's code with the 'original' code modified, you must made sure that any other variable/function referenced by the old code is also copied.
You must take into account that the original method was defined in another context. Probably, the author didn't mean for this class to be derived, or to have its methods overriden.
The advantage of modules (or IIFEs) is that you can define a public API while encapsulating the private parts of the implementation. Of course, this means that overriding methods or functions is much more difficult.
In this case, the variable _unicode is clearly intended to be part of the private implementation. The fact that its name follows the convention of starting with an underscore is almost proof of that. It probably is defined in the same module as DatePickerHelper, or imported from another, but in any case I'm almost sure it is not exported, so you cannot have access to it and your code fails.
For your override to work, you must either change the code to avoid using that _unicode variable, or define it yourself the same way it is done in the original code. Of course, depending of how it is defined you may end up having to 'copy' lots of that code, but that's an issue that any JavaScript programmer who has ever tried to override a method from an external library has suffered.
Let's see an example of this 'private implementation':
(function() {
// this is a function I want to keep private.
function giveMeHello() {
return 'hello, ';
}
// this is the class I want to share
class Greeting {
greet(name) {
console.log(giveMeHello() + name);
}
}
window.Greeting = Greeting;
})();
const greet1 = new Greeting();
greet1.greet('Oscar'); // outputs 'hello, Oscar'.
// As a consumer of Greeting, I don't like the way if greets people. I want the name to be in uppercase
// implementation is practically identical to original code
Greeting.prototype.greet = function() {
console.log(giveMeHello() + name.toUpperCase());
};
// Let's use it!
greet1.greet('John'); // Oh! Error! giveMeHello is not defined!
As you see, in this example I doing the same you are. But my implementation relies, just as the original one, on the giveMeHello function. But giveMeHello is no a global or public function. I haven't got access to it from the point in which I am overriding the function. Therefore, when I execute the method, it fails.
If now I just copy the giveMeHello function before overriding the method, it will work.
Do you get it now? If you don't, I suggest you to read more about scopes in JavaScript, something that is beyond the purpose of StackOverflow.

Creating Static ( Like ) Variables - Best Practice

Every time I need a static variable, I end up tacking it on as a property to the object that uses it and needs it to persist.
Particularly, this index here(MT.MAOrb.startR.index) I need to be static or hold it's value until the function is called again by a callback.
Is using this form the best way to do this?
MT.MAOrb.startR.index
/**
** MAOrb
*/
MT.MAOrb =
{
pre : function() {
var o_p = {
model : 'MAOrb'
};
return o_p;
},
post : function( o_p ) {
MT.MAOrb.startR( o_p );
},
startR: function( o_p ){
var sky = document.getElementById( 'Ab1' );
if( MT.MAOrb.startR.index === undefined ) {
var size = Object.size( o_p );
console.log( 'size' + size );
var index1 = MT.MAOrb.random( 0, size - 1 );
console.log( 'index1' + index1 );
MT.MAOrb.startR.index = index1;
MT.MAOrb.startR.o_p = o_p;
}else{
MT.MAOrb.startR.index++;
}
var image_element = MT.MAOrb.makeElement( MT.MAOrb.startR.o_p[ MT.MAOrb.startR.index ] );
sky.appendChild( image_element );
MT.MAOrb.moveLinear( image_element );
},// ....more code here
};
If you are trying to emulate a public static property, then that's a totally A-OK way to do it.
JavaScript is not a classical object oriented language. It is prototypical.
One ramification is that there really isn't a concept of static in the language.
The way you're doing it is totally fine, as long as you don't mind that another object can directly read and modify the property.
Javascript has no concept of static vs. non-static variables: everything is just a property of an object. As such, there is no right or wrong way of doing static variables, only right or wrong ways of doing static-like variables.
That being said, adding the variable as a property of a fixed (module-like) object, as you're doing, is pretty much your best bet (ie. best practice). Unless you're using a library like Backbone.js that is, which actually does add support for static variables to its "models" (ie. it's class system).
I'd say that it's actually a rather strange way to do it. Javascript provides function level scope, and you can use that to your advantage by using an immediately-invoked-function-expression (IIFE):
myObject = {
count: (function () { // this function is invoked immediately
var staticCounter = 0;
return function () { // this is the actual 'count' function
return ++staticCounter;
};
}())
};
myObject.count(); // 1
myObject.count(); // 2
One reason that this could be considered a better approach is that it completely hides the static variable. If you were to do myObject.count.staticCounter, some other code might be reading or writing to that variable. Obviously you wouldn't want that to happen, but if you do this, you are completely guaranteed of that variable's scope, which leads to easier debugging and refactoring in the future.

Categories

Resources