JavaScript callback function with relative variables - javascript

I am not entirely sure how to phrase this question, but basically, I have a class, button that on its click should call the function passed to it.
button = function(...,callBack) {
//...
this._cb = callBack;
}
button.prototype.callBack = function(e) {
//...
this._cb();
}
and then somewhere else
//on canvas click
e.target.callBack(e);
(I hope this is about the right amount of background, I can give more if needed)
So the issue I am running into is when I dynamically instantiate the buttons such that their callbacks use data from an array. i.e.
for (var i = 0; i < levels.length; i++) {
buttons[buttons.length] = new button(..., function() {drawLevel(levels[i])});
}
Then when they are clicked, they run that callback code and try to find some random value for i (probably a for-loop that didn't use var) and runs that level.
My question is, how can I (without using eval) circumvent this problem.
Thanks!

I'm not 100% clear on what you're asking, but it looks like you're going to be getting the wrong value for i in the anonymous function you're creating in the loop (it will always be levels.length)
Way around this is to have a different scope for every function created, with the i in each scope being a copy of the i in the loop
buttons[buttons.length] = new button(..., (function(i){
return function() {drawLevel(levels[i])};
})(i));

Related

Qualtrics API functions not working within custom function called in EventListener

I defined a custom function in the header section that checks, alerts the user, and resets the value of a particular slider bar when it fails certain restrictions.
This function works beautifully when called on question clicks:
this.questionclick = chkVals;
I would like to also run the function when the user are exiting the text input field (as some users are using the keyboard to do the survey). I implemented an Event Listener for each sliders' text input field that runs the function when the focus is out of the text input field.
// choices is an array of choice ids
for (i = 0; i < choices.length; i++) {
var x = document.getElementById(choices[i]);
x.addEventListener("blur", chkVals, true);
};
I know that the event listener works, because the correct alerts are popping up. It is just not able to reset the values as this.setChoiceValue is not a function within the environment. I have tried setting var that = this; and calling that.setChoiceValue in the function, but it still does not work.
Any help will be greatly appreciated!
You haven't shown all your code, so I'm making some assumptions.
this is the Qualtrics question object in the addOnload function. Since chkVals is outside the addOnload function, this (or that) is undefined. So, you need to pass it in your function call (function chkVals(qobj)) then use qobj.setChoiceValue in the chkVals function. Then your function calls become:
this.questionClick = chkVals(this);
and
x.addEventListener("blur", chkVals(this), true);
#T. Gibbons 's answer helped me get to this point. As suggested I needed to add a parameter to chkVals() to be able to reference the this object. However,
this.questionClick = chkVals(this);
does not work due to this being a reserved object, so the whole header script will not run. I ended up changing all reference of this to that in my custom function and adding the parameter that as suggested:
function chkVals(that) {
...
... that.setChoiceValue(x, y)
}
To call the function with a parameter, I had to explicitly defined an anonymous function that called chkVals, otherwise it will not work (I am not sure why):
var that = this;
this.questionclick = function() {chkVals(that);}
for (i = 0; i < choices.length; i++) {
var x = document.getElementById(choices[i]);
x.addEventListener("blur", function() {chkVals(that);}, true);
};
The above works!

Clicking objects on the evaluated page using CasperJS

I'm trying to click x number of objects on the evaluated page but it's failing after all day sunday attempts.. I have the following code, you can see I build up a list of objects in my variable (called itemsToAdd) and then I need that to be passed onto the evaluated page and then those objects to be clicked.
I know you can't pass complex objects to the evaluated page but every attempt I've tried has failed.. I've tried everything, please help. I've also tried custom js files although I couldn't get that working either.
$(function(ns) {
ns.myMethod = function(itemArray) {
var items = itemArray.items;
for (i = 0; i < items.length; i++) {
casper.thenEvaluate(function() {
casper.click(items[i].buttonId);
casper.waitUntilVisible(items[i].basketId, function() {
casper.echo(items[i].successMessage);
});
});
}
return this;
};
})(namespace('test'));
This is my variable, the buttonId is the DOM id for the button on the evaluated page. The basketId is another section on the evaluated page that gets updated to represent the button clicking has worked.
Complex variable
var itemsToAdd = {
'items': [
{
buttonId: '#button1',
basketId: '#nowInBasket1',
successMessage: 'It worked'
},
{
buttonId: '#button2',
basketId: '#nowInBasket2',
successMessage: 'this worked aswell'
}
]
};
Calling the code
test.myMethod(itemsToAdd);
There are multiple problems with your code.
evaluate is the sandboxed page context. There are no CasperJS or PhantomJS functions accessible inside of it. But it doesn't look like you are using the page context, so you should change thenEvaluate to then. I have written post here which shows for which things you can use the evaluate function/page context.
JavaScript has function scope. Read this question to learn more. This means that after the loop has executed all is point to the last i. You need a closure to fix this (here I use an IIFE, but you can also change the loop to itemArray.items.forEach(...).
var items = itemArray.items;
for (i = 0; i < items.length; i++) {
(function(i){
casper.then(function() {
casper.click(items[i].buttonId);
casper.waitUntilVisible(items[i].basketId, function() {
casper.echo(items[i].successMessage);
});
});
})(i);
}
If this doesn't solve your problem then this is probably a problem with your $ framework, whatever that is.

Using this keyword within Socket.io on function

I'm using a socket.io listener within one of my functions to listen for a "loser" event to tell the client that the other client won. However, I can't use the "this" keyword to talk about my client while inside the socket.on function as this refers to the socket itself. Am I going about this the wrong way? Or can access the client object some other way, like super?
socket.on('loser', function() {
//Remove all current objects then restart the game.
//THIS PART DOESN'T WORK, SINCE 'THIS' NO LONGER REFERS TO
//THE GAME OBJECT, BUT INSTEAD REFERENCES THE SOCKET LISTENER.
for(var i = 0; i < this.board.objects.length; i++)
{
this.board.remove(this.board.objects[i]);
}
//WORKS AS EXPECTED FROM HERE ON...
Game.setBoard(1, new TitleScreen(gameType,
"Loser!",
"Press Space to Play Again",
playGame));
});
Functions don't carry any information about the objects that reference them, you can use .bind() to bind the function to your object before you pass it:
socket.on('loser', function() {
//Remove all current objects then restart the game.
//THIS PART DOESN'T WORK, SINCE 'THIS' NO LONGER REFERS TO
//THE GAME OBJECT, BUT INSTEAD REFERENCES THE SOCKET LISTENER.
for (var i = 0; i < this.board.objects.length; i++) {
this.board.remove(this.board.objects[i]);
}
//WORKS AS EXPECTED FROM HERE ON...
Game.setBoard(1, new TitleScreen(gameType, "Loser!", "Press Space to Play Again",
playGame));
}.bind(this));
In browser-land the common way to do this is to set a variable like var that = this; before you enter the function, and then use that instead.
However, ECMAScript5 brought in bind(), which allows you to prevent the value of this being lost. In NodeJS of course, it's safe to use this (unlike in browser-land, where you have to support older browsers).
socket.on('loser', (function() {
//Remove all current objects then restart the game.
for (var i = 0; i < this.board.objects.length; i++) {
this.board.remove(this.board.objects[i]);
}
//WORKS AS EXPECTED FROM HERE ON...
Game.setBoard(1, new TitleScreen(gameType, "Loser!", "Press Space to Play Again", playGame));
}).bind(this));​
For more info, see https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Function/bind
Whats wrong with something like this?
var self = this;
socket.on('loser', (function() {
//Remove all current objects then restart the game.
for (var i = 0; i < self.board.objects.length; i++) {
self.board.remove(self.board.objects[i]);
}
}

setTimeout in methods of multiple objects of the same type

I need help with the use of "setTimeout" in the methods of the objects of the same type. I use this code to initiate my objects:
function myObject(param){
this.content = document.createElement('div');
this.content.style.opacity = 0;
this.content.innerHTML = param;
document.body.appendChild(this.content);
this.show = function(){
if(this.content.style.opacity < 1){
this.content.style.opacity = (parseFloat(this.content.style.opacity) + 0.1).toFixed(1);
that = this;
setTimeout(function(){that.show();},100);
}
}
this.hide = function(){
if(this.content.style.opacity > 0){
this.content.style.opacity = (parseFloat(this.content.style.opacity) - 0.1).toFixed(1);
that = this;
setTimeout(function(){that.hide();},100);
}
}
}
Somewhere I have 2 objects:
obj1 = new myObject('Something here');
obj2 = new myObject('Something else here');
Somewhere in the HTML code I use them:
<button onclick="obj1.show()">Something here</button>
<button onclick="obj2.show()">Something else here</button>
When the user presses one button, everything goes OK, but if the user presses one button and after a short time interval he presses the other one, the action triggered by the first button stops and only the action of the second button is executed.
I understand that the global variable "that" becomes the refence of the second object, but I don't know how to create an automatic mechanism that wouldn't block the previously called methods.
Thank you in advance and sorry for my English if I made some mistakes :P
If you need something cancellable, use window.setInterval instead of setTimeout. setInterval returns a handle to the interval which can then be used to cancel the interval later:
var global_intervalHandler = window.setInterval(function() { ... }, millisecondsTotal);
// more code ...
// later, to cancel this guy:
window.clearInterval(global_intervalHandler);
So from here I'm sure you can use your engineering skills and creativity to make your own self expiring operations - if they execute and complete successfully (or even unsuccessfully) they cancel their own interval. If another process intervenes, it can cancel the interval first and hten fire its behavior.
There are several ways to handle something like this, here's just one off the top of my head.
First of all, I see you're writing anonymous functions to put inside the setTimeout. I find it more elegant to bind a method of my object to its scope and send that to setTimeout. There's lots of ways to do hitching, but soon bind() will become standard (you can write this into your own support libraries yourself for browser compatibility). Doing things this way would keep your variables in their own scope (no "that" variable in the global scope) and go a long way to avoiding bugs like this. For example:
function myObject(param){
// ... snip
this.show = function(){
if(this.content.style.opacity < 1){
this.content.style.opacity = (parseFloat(this.content.style.opacity) + 0.1).toFixed(1);
setTimeout(this.show.bind(this),100);
}
}
this.hide = function(){
if(this.content.style.opacity > 0){
this.content.style.opacity = (parseFloat(this.content.style.opacity) - 0.1).toFixed(1);
setTimeout(this.hide.bind(this),100);
}
}
}
Second, you probably want to add some animation-handling methods to your object. setTimeout returns handles you can use to cancel the scheduled callback. If you implement something like this.registerTimeout() and this.cancelTimeout() that can help you make sure only one thing is going on at a time and insulate your code's behavior from frenetic user clicking like what you describe.
Do you need that as global variable ? just change to var that = this; you will use variable inside of the function context.

Javascript Closures and *static* classes problem

I have a static class which contains an array of callback functions, I then have a few other classes that are used to interact with this static class...
Here is a simple example of the static class:
var SomeStaticInstance = {};
(function(staticInstance) {
var callbacks = {};
staticInstance.addCallback = function(callback) { callbacks.push(callback); }
staticInstance.callAllCallbacks = function() { /* call them all */ }
}(SomeStaticInstance));
Then here is an example of my other classes which interact with it:
function SomeClassOne() {
this.addCallbackToStaticInstance = function() { SomeStaticInstance.addCallback(this.someCallback); }
this.someCallback = function() { /* Do something */ }
this.activateCallbacks = function() { SomeStaticInstance.callAllCallbacks(); }
}
function SomeClassTwo() {
this.addCallbackToStaticInstance = function() { SomeStaticInstance.addCallback(this.someOtherCallback); }
this.someOtherCallback = function() { /* Do something else */ }
this.activateCallbacks = function() { SomeStaticInstance.callAllCallbacks(); }
}
Now the problem I have is that when I call either class and tell it to activateCallbacks() the classes only activate the callbacks within their own scope, i.e SomeClassOne would call someCallback() but not someOtherCallback() and vice versa, now I am assuming it is something to do with the scope of the closures, however I am not sure how to get the behaviour I am after...
I have tried turning the static class into a regular class and then passing it into the 2 classes via the constructor, but still get the same issue...
So my question is how do I get the classes to raise all the callbacks
-- EDIT --
Here is an example displaying the same issue as I am getting on my actual app, I have put all script code into the page to give a clearer example:
http://www.grofit.co.uk/other/pubsub-test.html
It is a simple app with 2 presenters and 2 views... one view is concerned with adding 2 numbers at the top of the page, the 2nd view is concerned with taking that total and multiplying it and showing a result.
The 3rd party library I am using is PubSubJS, and the first presenter listens for an event to tell it that the one of the boxes has changed and re-totals the top row. The 2nd presenter listens for when the multiply or total at the top changes, then recalculates the bottom one. Now the first presenter recalculates correctly, and the 2nd presenter will correctly recalculate whenever the multiply box changes, HOWEVER! It will NOT recalculate when the total on the top changes, even thought it should receive the notification...
Anyway take a quick look through the source code on the page to see what I mean...
First, I think you want var callbacks = [] (an array instead of an object) since you're using callbacks.push().
I'm not sure I understand your problem. The way your classes are structured, you can achieve what you want by instantiating both classes and calling addCallbackToStaticInstance() on both new objects. E.g.,
var one = new SomeClassOne();
var two = new SomeClassTwo();
one.addCallbackToStaticInstance();
two.addCallbackToStaticInstance();
one.activateCallbacks();
Then, as above, you can call activateCallbacks() from either object.
If you're saying you want to be able to call activateCallback() after instantiating only one of the classes, you really have to rethink your approach. I'd start with moving addCallbackToStaticInstance() and activateCallbacks() into their own class.
This is a very odd way of doing things, but your main problem is that your callbacks object it not part of SomeStaticInstance, it is defined within an anonymous closure. Also your callbacks object {} should be an array [].
try staticInstance.callbacks = []; instead of var callbacks = {};
and
staticInstance.addCallback = function(callback) {
this.callbacks.push(callback);
}

Categories

Resources