BdR Posted April 28, 2015 Share Posted April 28, 2015 Hi, I'm trying to create a small javascript app which loads a JSON url and displays some html and divs based on the JSON data. It's got to be as small as possible so without using a frameworks like Phaser and jQuery. The javascript scope of variables is a bit tricky, and I'm having some trouble getting it to work properly. My question is, how can create the MyGame class with an imageShapes image and then reference that imageShapes in the onreadystatechange method of XMLHttpRequest? Here is my code:// namespacevar MyGame = MyGame || { gamedata: [], test123: 'This is a test value 123'};// game objectMyGame.start = function (jsonfilename) { // initialise image this.imageShapes = new Image(); // add gamedata and populate by loading json this.gamedata = []; this.test123 = 'This is a test value 123'; this.loadJSON( jsonfilename, this.onJSONsuccess, // <- function to call on successfully loaded JSON this.onJSONerror );}MyGame.start.prototype = { // load a JSON file loadJSON: function(path, success, error) { console.log('TESTING loadJSON : test123='+this.imageBackground); var xhr = new XMLHttpRequest(); xhr.onreadystatechange = function() { if (xhr.readyState === XMLHttpRequest.DONE) { if ((xhr.status === 200) || (xhr.status === 0)) { // status 0 = local file testing if (success) success(JSON.parse(xhr.responseText)); // <- how to reference back to "MyGame" ? } else { if (error) error(xhr); } } }; xhr.open("GET", path, true); xhr.send(); }, // handle load json events onJSONerror: function(xhr) { console.log('MyGame.js - onJSONerror error loading json file'); console.error(xhr); }, onJSONsuccess: function(data) { // load data from JSON this.gamedata = data; console.log('TESTING -- data.imgshapes='+data.imgshapes+' test123='+MyGame.test123+' imageShapes='+this.imageShapes); // this.imageShapes is also undefined MyGame.imageShapes.src = 'img/mybackground.png'; // <- problem is here, imageShapes is undefined..? }};var test = new MyGame.start('js/mydatafile.json');When I try the code above, it fails in onJSONsuccess. The imageShapes cannot reference and it is undefined, even though I defined it earlier in the MyGame class. This is the error: Uncaught TypeError: Cannot set property 'src' of undefined test123.js:58 Quote Link to comment Share on other sites More sharing options...
druphoria Posted April 29, 2015 Share Posted April 29, 2015 Your call to MyGame.imageShapes.src is failing because you never set imageShapes on the MyGame object. The only things on MyGame are "gamedata", "test123", and "start", which you'll see if you do console.log(MyGame). The only place you're actually setting imageShapes is in the MyGame.start function. You're invoking that function "as a constructor" here:var test = new MyGame.start('js/mydatafile.json');, which is creating the imageShapes property on the test object. The reason it creates it on test is because when you invoke a function with the "new" keyword, several things happen:1) A new object is created2) The function is run with "this" set to be the new object (in your case, this will result in several properties such as imageShapes being set on this new object)3) The new object is returned (in this case, it is returned and assigned to the "test" variable). When you pass your onJSONsuccess function into your loadJSON function, it is being invoked like this: success(JSON.parse(xhr.responseText)); // <- how to reference back to "MyGame" ?When you invoke a function like this, without using .call or .apply or .bind or without invoking it as a "method" of an object, the "this" object is set to be the window object. That's why you're seeing "this.imageShapes" be undefined. Quote Link to comment Share on other sites More sharing options...
druphoria Posted April 29, 2015 Share Posted April 29, 2015 Removed Quote Link to comment Share on other sites More sharing options...
BdR Posted April 30, 2015 Author Share Posted April 30, 2015 Thanks for the explanation and I've read some chapters of the "You Don't Know JS" that was very helpful, thanks for the link. Unfortunately it's still not working, here is my updated code:// namespacevar MyGame = MyGame || { gamedata: [], test123: 'This is a test value 123', imageShapes: null // define imageShapes here!};// game objectMyGame.start = function (jsonfilename) { // initialise image this.imageShapes = new Image(); // add gamedata and populate by loading json this.gamedata = []; this.test123 = 'This is a test value 123'; this.loadJSON( jsonfilename, this.onJSONsuccess, this.onJSONerror );}MyGame.start.prototype = { // load a JSON file loadJSON: function(path, success, error) { debugger; console.log('TESTING loadJSON : test123='+this.imageBackground); var xhr = new XMLHttpRequest(); xhr.onreadystatechange = function() { if (xhr.readyState === XMLHttpRequest.DONE) { if ((xhr.status === 200) || (xhr.status === 0)) { // status 0 = local file testing if (success) success(JSON.parse(xhr.responseText)); } else { if (error) error(xhr); } } }; xhr.open("GET", path, true); xhr.send(); }, // handle load json events onJSONerror: function(xhr) { console.log('MyGame.js - onJSONerror error loading json file'); console.error(xhr); }, onJSONsuccess: function(data) { // load data from JSON this.gamedata = data; console.log('TESTING -- data.imgshapes='+data.imgshapes+' test123='+MyGame.test123+' imageShapes='+MyGame.imageShapes); MyGame.imageShapes.src = 'img/mybackground.png'; }};The 'success' and 'error' functions still need to be passed into the loadJSON-function I think. Else there is no way to reference the onJSONsuccess method because the onreadystatechange-function is invoked by xhr (=XMLHttpRequest). So then 'this' is set to XMLHttpRequest and then you can't reference the MyGame.start object. So in the above code the onJSONsuccess is called, but at that point 'this' is set to Window(?) and I again cannot reference MyGame.start or imageShapes. Man, this is really confusing. Quote Link to comment Share on other sites More sharing options...
druphoria Posted May 1, 2015 Share Posted May 1, 2015 "this" is set to window because you're invoking the function directly. Here is a breakdown of how "this" works:1. If you call a function directly, like this:fun();, then "this" is set to be window.2. If you call a function as a method of an object, like this:var obj = { fun: function() {// do stuff}};obj.fun();, then "this" will be obj.3. You can also use Function.prototype.apply, Function.prototype.call, or Function.prototype.bind to force the value of "this". In your case, you're doing number 1 because you're calling success directly. That's why "this" is the window object. And yeah, actually the way I outlined it earlier won't work (I missed the fact that you were invoking the function from the callback for onreadystatechange). Try doing this: loadJSON: function(path) { var xhrCallback = function() { if (xhr.readyState === XMLHttpRequest.DONE) { if ((xhr.status === 200) || (xhr.status === 0)) { // status 0 = local file testing this.onJSONsuccess(JSON.parse(xhr.responseText)); } else { this.onJSONerror(xhr); } } }; var xhr = new XMLHttpRequest(); xhr.onreadystatechange = xhrCallback.bind(this); xhr.open("GET", path, true); xhr.send(); } Then in onJSONSuccess, get imageShapes from "this" instead of from MyGame:onJSONsuccess: function(data) { // load data from JSON this.gamedata = data; console.log('TESTING -- data.imgshapes='+data.imgshapes+' test123='+MyGame.test123+' imageShapes='+MyGame.imageShapes); this.imageShapes.src = 'img/mybackground.png'; }I made a little JSFiddle just to make sure that the context would get set correctly using this method. Open up your console and click "Run" in the JSfiddle and you'll see that "this" on onJSONsuccess is being set properly to your "MyGame.start" object. This is happening because I'm using Function.prototype.bind to force "this" to be the MyGame.function object. Quote Link to comment Share on other sites More sharing options...
BdR Posted May 2, 2015 Author Share Posted May 2, 2015 And yeah, actually the way I outlined it earlier won't work (I missed the fact that you were invoking the function from the callback for onreadystatechange). Try doing this: Excellent! Creating the separate callback function first, and then binding it to this worked. I think I'm starting to get the hang of this now, thanks Btw you removed the link to the "You don't know JS" online books in your previous post. I found that link very helpful, so to anyone else reading you can check it out here:https://github.com/getify/You-Dont-Know-JS Quote Link to comment Share on other sites More sharing options...
Recommended Posts
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.