I didn't succeed in accessing the variable playing from outside the object Pong:
Pong = {
// some other objects
initialize: function (runner, cfg) {
Game.loadImages(Pong.Images, function (images) {
this.cfg = cfg;
this.runner = runner;
this.width = runner.width;
this.height = runner.height;
this.images = images;
this.playing = false; // variable is defined here
this.scores = [0, 0];
this.menu = Object.construct(Pong.Menu, this);
this.court = Object.construct(Pong.Court, this);
this.leftPaddle = Object.construct(Pong.Paddle, this);
this.rightPaddle = Object.construct(Pong.Paddle, this, true);
this.ball = Object.construct(Pong.Ball, this);
this.sounds = Object.construct(Pong.Sounds, this);
this.runner.start();
} .bind(this));
},
// some more functions
isPlaying: function () { // I added this function to enable for access
return this.playing; // here playing is undefined
},
start: function (numPlayers) {
if (!this.playing) { // here playing is defined
this.scores = [0, 0];
this.playing = true;
this.leftPaddle.setAuto(numPlayers < 1, this.level(0));
this.rightPaddle.setAuto(numPlayers < 2, this.level(1));
this.ball.reset();
this.runner.hideCursor();
}
},
// more objects and functions
This is a pingpong game. The complete page is this:
http://ulrichbangert.de/div/webdesign/javascript/pong.html I cannot understand why this variable can be accessed in start and not in isPlaying.
Why is this and what code do I have to use to access this variable? To enable for debugging I added calling isPlaying in the onclick event.
This is one of the classic problems with javascript, this changing "unexpectedly".
Inside of that callback, this points to something else, not your object. One of solutions is to trap object reference in a closure.
initialize: function (runner, cfg) {
var that = this; // <= "that" won't change.
Game.loadImages(Pong.Images, function (images) {
that.cfg = cfg;
that.playing = false;
Related
I have a JavaScript function defined. Inside this function, I define variables of type jQuery elements. So, the variables refer to divs on the HTML. This function returns an object that has a single function init().
In the $(document).ready function, I call init() function.
The problem is when the script loads, the DOM is not ready and hence the variables referring to jQuery items are being set to undefined.
Later, I call the init() function inside Angular ngOnInit() to make sure things are well initialized, so nothing is happening as the variables above are undefined and they are not being re-calculated again.
Seems, when a function in JavaScript is defined, its body runs, and hence the variables are run and set to undefined as the HTML elements were not in the DOM yet.
How can I re-calculate the variables when init() runs? I cannot get my mind on this thing.
Thanks
var mQuickSidebar = function() {
var topbarAside = $('#m_quick_sidebar');
console.log('Function: ', Date.now());
var topbarAsideTabs = $('#m_quick_sidebar_tabs');
var topbarAsideClose = $('#m_quick_sidebar_close');
var topbarAsideToggle = $('#m_quick_sidebar_toggle');
var topbarAsideContent = topbarAside.find('.m-quick-sidebar__content');
var initMessages = function() {
var messenger = $('#m_quick_sidebar_tabs_messenger');
if (messenger.length === 0) {
return;
}
var messengerMessages = messenger.find('.m-messenger__messages');
var init = function() {
var height = topbarAside.outerHeight(true) -
topbarAsideTabs.outerHeight(true) -
messenger.find('.m-messenger__form').outerHeight(true) - 120;
// init messages scrollable content
messengerMessages.css('height', height);
mApp.initScroller(messengerMessages, {});
}
init();
// reinit on window resize
mUtil.addResizeHandler(init);
}
var initSettings = function() {
var settings = $('#m_quick_sidebar_tabs_settings');
if (settings.length === 0) {
return;
}
// init dropdown tabbable content
var init = function() {
var height = mUtil.getViewPort().height - topbarAsideTabs.outerHeight(true) - 60;
// init settings scrollable content
settings.css('height', height);
mApp.initScroller(settings, {});
}
init();
// reinit on window resize
mUtil.addResizeHandler(init);
}
var initLogs = function() {
// init dropdown tabbable content
var logs = $('#m_quick_sidebar_tabs_logs');
if (logs.length === 0) {
return;
}
var init = function() {
var height = mUtil.getViewPort().height - topbarAsideTabs.outerHeight(true) - 60;
// init settings scrollable content
logs.css('height', height);
mApp.initScroller(logs, {});
}
init();
// reinit on window resize
mUtil.addResizeHandler(init);
}
var initOffcanvasTabs = function() {
initMessages();
initSettings();
initLogs();
}
var initOffcanvas = function() {
topbarAside.mOffcanvas({
class: 'm-quick-sidebar',
overlay: true,
close: topbarAsideClose,
toggle: topbarAsideToggle
});
// run once on first time dropdown shown
topbarAside.mOffcanvas().one('afterShow', function() {
mApp.block(topbarAside);
setTimeout(function() {
mApp.unblock(topbarAside);
topbarAsideContent.removeClass('m--hide');
initOffcanvasTabs();
}, 1000);
});
}
return {
init: function() {
console.log('Inside Init(): ', Date.now());
console.log($('#m_quick_sidebar')); // topbarAside is undefined here!
if (topbarAside.length === 0) {
return;
}
initOffcanvas();
}
}; }();
$(document).ready(function() {
console.log('document.ready: ', Date.now());
mQuickSidebar.init();
});
The problem is that you immediately invoke your function mQuickSidebar not
Seems, when a function in JavaScript is defined, its body runs
var mQuickSidebar = function() {
var topbarAside = $('#m_quick_sidebar');
console.log('Function: ', Date.now());
var topbarAsideTabs = $('#m_quick_sidebar_tabs');
var topbarAsideClose = $('#m_quick_sidebar_close');
var topbarAsideToggle = $('#m_quick_sidebar_toggle');
var topbarAsideContent = topbarAside.find('.m-quick-sidebar__content');
...
}(); // <---- this runs the function immediately
So those var declarations are run and since the DOM is not ready those selectors don't find anything. I'm actually surprised that it does not throw a $ undefined error of some sort
This looks like a broken implementation of The Module Pattern
I re-wrote your code in the Module Pattern. The changes are small. Keep in mind that everything declared inside the IIFE as a var are private and cannot be accessed through mQuickSidebar. Only the init function of mQuickSidebar is public. I did not know what you needed to be public or private. If you need further clarity just ask.
As a Module
var mQuickSidebar = (function(mUtil, mApp, $) {
console.log('Function: ', Date.now());
var topbarAside;
var topbarAsideTabs;
var topbarAsideClose;
var topbarAsideToggle;
var topbarAsideContent;
var initMessages = function() {
var messenger = $('#m_quick_sidebar_tabs_messenger');
if (messenger.length === 0) {
return;
}
var messengerMessages = messenger.find('.m-messenger__messages');
var init = function() {
var height = topbarAside.outerHeight(true) -
topbarAsideTabs.outerHeight(true) -
messenger.find('.m-messenger__form').outerHeight(true) - 120;
// init messages scrollable content
messengerMessages.css('height', height);
mApp.initScroller(messengerMessages, {});
};
init();
// reinit on window resize
mUtil.addResizeHandler(init);
};
var initSettings = function() {
var settings = $('#m_quick_sidebar_tabs_settings');
if (settings.length === 0) {
return;
}
// init dropdown tabbable content
var init = function() {
var height = mUtil.getViewPort().height - topbarAsideTabs.outerHeight(true) - 60;
// init settings scrollable content
settings.css('height', height);
mApp.initScroller(settings, {});
};
init();
// reinit on window resize
mUtil.addResizeHandler(init);
};
var initLogs = function() {
// init dropdown tabbable content
var logs = $('#m_quick_sidebar_tabs_logs');
if (logs.length === 0) {
return;
}
var init = function() {
var height = mUtil.getViewPort().height - topbarAsideTabs.outerHeight(true) - 60;
// init settings scrollable content
logs.css('height', height);
mApp.initScroller(logs, {});
};
init();
// reinit on window resize
mUtil.addResizeHandler(init);
};
var initOffcanvasTabs = function() {
initMessages();
initSettings();
initLogs();
};
var initOffcanvas = function() {
topbarAside.mOffcanvas({
class: 'm-quick-sidebar',
overlay: true,
close: topbarAsideClose,
toggle: topbarAsideToggle
});
// run once on first time dropdown shown
topbarAside.mOffcanvas().one('afterShow', function() {
mApp.block(topbarAside);
setTimeout(function() {
mApp.unblock(topbarAside);
topbarAsideContent.removeClass('m--hide');
initOffcanvasTabs();
}, 1000);
});
};
return {
init: function() {
console.log('Inside Init(): ', Date.now());
console.log($('#m_quick_sidebar')); // topbarAside is undefined here!
topbarAside = $('#m_quick_sidebar');
topbarAsideTabs = $('#m_quick_sidebar_tabs');
topbarAsideClose = $('#m_quick_sidebar_close');
topbarAsideToggle = $('#m_quick_sidebar_toggle');
topbarAsideContent = topbarAside.find('.m-quick-sidebar__content');
if (topbarAside.length === 0) {
return;
}
initOffcanvas();
}
};
})(mUtil, mApp, $);
$(document).ready(function() {
console.log('document.ready: ', Date.now());
mQuickSidebar.init();
});
My console message:
Uncaught TypeError: Cannot read property 'x' of undefined.
However, when I log to the console the snake.head object, it gives me a valid "response"! How could it be that an object is defined but when trying to store a property of that object in a variable it doesn't work? I also tried just continuing the game without storing the x and y position in a variable and instead accessing it within the head object and guess what? Doesn't work. I even tried just accessing the game.SNAKE[game.SNAKE.length-1].(property) but it also doesn't work! However, when I try it in my snake.init function, it works fine, but I can't do this, as my init only runs once!
var game = {};
game.contextBackground = document.getElementById("bgCanvas").getContext("2d");
game.contextSnake = document.getElementById("snakeCanvas").getContext("2d");
game.contextFruit = document.getElementById("fruitCanvas").getContext("2d");
game.keys = [];
game.SNAKE = [];
game.width = document.getElementById("snakeCanvas").width;
game.height = document.getElementById("snakeCanvas").height;
game.COLUMNS = 35;
game.ROWS = 35;
game.boxSide = game.width/game.COLUMNS;
game.snake = {
snakeLength: 3,
tailx: null,
taily: null,
head: null,
headx: null,
heady: null,
direction: "right",
createSnake: function (snakeLength) {
game.SNAKE.length = this.snakeLength;
for (i=0;i<snakeLength;i++) {
game.SNAKE[i] = {
x: i ,
y: 0
};
}
},
updateSnake: function (length) {
this.head = game.SNAKE[length-1];
this.headx = this.head.x;
for (i in game.SNAKE) {
}
},
init: function() {
this.createSnake(this.snakeLength);
},
update: function() {
this.updateSnake(game.SNAKE.length);
console.log(this.head);
for (i in game.SNAKE) {
game.SNAKE[i].x ++;
}
},
render: function() {
game.contextSnake.clearRect(0,0,game.width,game.height);
for (i in game.SNAKE) {
drawBox(game.SNAKE[i].x,game.SNAKE[i].y,game.contextSnake,"green");
}
}
};
game.fruit = {
x: null,
y: null,
newFruitNecessary: true,
init: function() {
},
update: function() {
},
render: function() {
}
};
function init() {
loop();
game.snake.init();
game.fruit.init();
}
function update() {
game.snake.update();
game.fruit.update();
}
function render() {
game.fruit.render();
game.snake.render();
}
function loop() {
setTimeout(function(){
loop();
},1000/10);
update();
render();
}
function drawBox (gridx,gridy,context,color) {
if (color == null) {
color = "black";
}
context.fillStyle = color;
context.fillRect((gridx * game.boxSide), (gridy * game.boxSide), game.boxSide,game.boxSide);
}
init();
You have to init your snake (and later fruit) before you can loop; otherwise, game.SNAKE will point at an empty array, so the lines
this.head = game.SNAKE[length-1];
this.headx = this.head.x;
will fail to work properly when the first update() calls inside your loop(). game.SNAKE is empty, so you store undefined to this.head; thus, this.head.x resolves to undefined.x, which fails.
Try shifting around the lines in init:
function init() {
game.snake.init();
game.fruit.init();
loop();
}
I have a nested function that I want to call from outside.
var _Config = "";
var tourvar;
function runtour() {
if (_Config.length != 0) {
tourvar = $(function () {
var config = _Config,
autoplay = false,
showtime,
step = 0,
total_steps = config.length;
showControls();
$('#activatetour').live('click', startTour);
function startTour() {
}
function showTooltip() {
}
});
}
}
function proceed() {
tourvar.showTooltip();
}
$(document).ready(function () {
runtour();
});
I was hoping to call it by tourvar.showTooltip(); but I seem to be wrong :) How can I make showTooltip() available from outside the function?
since my previous answer was really a hot headed one, I decided to delete it and provide you with another one:
var _Config = "";
var tourvar;
// Module pattern
(function() {
// private variables
var _config, autoplay, showtime, step, total_steps;
var startTour = function() { };
var showTooltip = function() { };
// Tour object constructor
function Tour(config) {
_config = config;
autoplay = false;
step = 0;
total_steps = _config.length;
// Provide the user with the object methods
this.startTour = startTour;
this.showTooltip = showTooltip;
}
// now you create your tour
if (_Config.length != 0) {
tourvar = new Tour(_Config);
}
})();
function proceed() {
tourvar.showTooltip();
}
$(document).ready(function () {
runtour();
});
function outerFunction() {
window.mynestedfunction = function() {
}
}
mynestedfunction();
So I'm writing a game, and I've got a module that returns the keys currently being pressed via jQuery. No problems there. The problem comes when I attempt to access the keys pressed:
var Keys = require('./lib/keys')
Player.prototype.update = function () {
Keys(function (err, keydown) {
console.log(keydown, keydown['w']);
/* // To move a player up, for example:
if (keydown['w']) {
this.y += this.speed;
}
*/
});
};
And the console shows that what keys are pressed, but attempting to access one gives me an undefined instead of true.
Object undefined
s: true
w: true
x: true
__proto__: Object
Anyone have any thoughts?
Update: key module
var $ = require('./jquery')
var Keys = function (callback) {
var keydown = {};
function keyName(event) {
return String.fromCharCode(event.which).toLowerCase();
}
$(document).bind('keydown', function (event) {
keydown[keyName(event)] = true;
return false;
});
$(document).bind('keyup', function (event) {
return false;
});
callback(null, keydown);
}
module.exports = Keys;
/*************
* UPDATE *
*************/
This is the final fix:
./lib/keys.js
var $ = require('./jquery')
var Keys = function () {
this.keydown = {};
var keyName = function (event) {
return String.fromCharCode(event.which).toLowerCase();
}
var self = this;
$(document).bind('keydown', function (event) {
self.keydown[keyName(event)] = true;
return false;
});
$(document).bind('keyup', function (event) {
self.keydown[keyName(event)] = false;
return false;
});
};
Keys.prototype.getKeys = function (callback) {
callback(null, this.keydown);
}
module.exports = new Keys;
./lib/player.js
var Keys = require('./keys')
var Player = function (game, keys) {
// stuff
}
Player.prototype.update = function() {
var self = this;
Keys.getKeys(function(err, keys) {
if (keys['w']) {
self.y -= self.speed;
}
if (keys['a']) {
self.x -= self.speed;
}
if (keys['s']) {
self.y += self.speed;
}
if (keys['d']) {
self.x += self.speed;
}
});
That happens because of Keys has asynchronous processes in it.
It's just a known chrome issue that shows the object value by reference. So you see the object value a moment after you call console.log
To see it more clear open chrome webdev tools and put debugger; instead of console.log and see what's actually in keydown object. And I bet it will be just an empty object.
And I'll just leave it here: http://felix-kling.de/blog/2011/08/18/inspecting-variables-in-javascript-consoles/
That will teach me to scan code too fast. The comments are right and this code isn't pointing to the current problem.
The variable this gets reset every time you enter a new function.
Player.prototype.update = function () {
var self = this;
Keys(function (err, keydown) {
console.log(keydown, keydown['w']);
/* // To move a player up, for example:
if (keydown['w']) {
self.y += self.speed;
}
*/
});
};
I don't see any jQuery here. You need to supply more code, such as Keys source code. But I guess that you need to use http://api.jquery.com/event.which/ , for example, keydown.which === 'w'
I was playing around with making game in JS. And hit a brick wall which is inability to change a variable from an event in the main html file. Namely speaking offSetX. Why it doesn't change?
var game = new Game();
window.addEventListener("keyup", game.input);
game.start('myCanvas');
The game object looks like this:
function Game() {
this.offSetX = 0;
this.init = function (id) {
this.canvas = document.getElementById(id);
this.context = this.canvas.getContext('2d');
this.blocks = [];
this.blocks.push(new block());
};
this.logic = function () {
for (var i in this.blocks) {
this.blocks[i].update(this.offSetX);
}
};
this.draw = function () {
for (var i in this.blocks) {
this.blocks[i].draw(this.context);
}
};
this.main = function () {
this.logic();
this.draw();
console.log(this.offSetX);
};
this.input = function (key) {
if (key.keyCode == 37) {
this.offSetX--;
console.log(this.offSetX);
}
if (key.keyCode == 39) {
this.offSetX++;
console.log(this.offSetX);
}
};
this.start = function (id) {
var _this = this;
this.init(id);
this.interval = setInterval(function () {
_this.canvas.width = _this.canvas.width;
_this.main();
}, 30);
}
};
Try this:
window.addEventListener("keyup", function(key){
game.input.apply(game,[key]);
});
The problem was by window.addEventListener("keyup", game.input) line , you are adding handler for window object, that's why in input method , "this" is window object(which does not have any "offSetX" method), not the game object.