I am working on the new Palm Pre WebOS, the Apps for Palm Pre are developed in MojoSDK which is developed on top of Prototype Javascript Framework.
I am trying to access variables defined at assistant level in event handlers which are a part of the same assistant as well. When I access the assistant level variables in an event handler, I get it as undefined. However, the variables are accessible in the setup function.
For reference, have a look at the Code below:
Code:
function MyTestAssistant(passedValue)
{
this.passedValue = passedValue;
}
MyTestAssistant.prototype.setup = function()
{
Mojo.Log.info("Passed Value Is: " + this.passedValue); // Prints the value set in Constructor
}
MyTestAssistant.prototype.testListTapHandler = function(event)
{
Mojo.Log.info("Passed Value Is: " + this.passedValue); // Logs undefined
}
I call this here:
Mojo.Event.listen(this.myTestList, Mojo.Event.listTap, this.testListTapHandler);
Is anyone else having this issue or I am doing something wrong here? Is it possible to access the variables in handler or can we have think of workarounds to achieve it?
I'm not familiar with mojo-sdk, but this sounds a lot like you've just got your "this" reference mixed up when you set up the event handler. In all likelihood, when testListTapHandler is called, this references the object which is firing the event.
Prototype has the very handy bind() method to help clear up this confusion though.
I'll guess you had something like this
elem.observe('eventname', myTestAssistant.testListTapHandler);
Trouble is, when the event is fired, inside testListTapHandler, this will refer to elem. To correct this, we bind the event handler with the desired object:
elem.observe('eventname', myTestAssistant.testListTapHandler.bind(myTestAssistant));
I have found the solution to the problem. Another Forum helped me as well.
The core issue, as pointed out by Paul is of Binding and Scope.
I updated my implementation to the following for making it work:
function MyTestAssistant(passedValue)
{
this.passedValue = passedValue;
}
MyTestAssistant.prototype.setup = function()
{
Mojo.Log.info("Passed Value Is: " + this.passedValue); // Prints the value set in Constructor
// Was Using the following code before and this.passedValue wasn't accessible in
// testListTapHandler
// Mojo.Event.listen(this.testList, Mojo.Event.listTap, this.testListTapHandler);
// Used the following code now and this.passedValue is accessible in
// testListTapHandler
this.testListTapHandler = this.testListTapHandler.bindAsEventListener(this);
Mojo.Event.listen(this.testList, Mojo.Event.listTap, this.testListTapHandler);
}
MyTestAssistant.prototype.testListTapHandler = function(event)
{
Mojo.Log.info("Passed Value Is: " + this.passedValue); // Prints the value set in Constructor
}
Thanks for your help Paul.
Regards,
Muhammad Haseeb Khan
Related
Here is the app I'm referring to:
I am trying to fundamentally understand the bind method in Javascript.
My understanding when I play around with it in the console is that bind returns a copy of the function, with "this" bound to whatever you pass into bind.
function logThis(){
console.log(this)
}
logThis.bind({today: 'Tuesday'})
//Will return a copy of the logThis function, with 'this' set to the
{today:'Tuesday'} object. The code does not run right away though.
var explicitlyLogThis = logThis.bind({today: 'Tuesday'});
explicitlyLogThis(); //This will run the code and display the {today: 'Tuesday'} object to the console.
This is my understanding so far. I understand that to actually run this new function that has 'this' explicitly bound using the bind method, you need to set it to a variable and then run it.
I see a contradiction when I look at the app in the above link. If you look at the bindEvents method on line 56, we have .on('keyup', this.create.bind(this)). I understand that we have to set 'this' to App when we run the create method because jQuery defaults to setting 'this' to the jQuery object itself. So this line is actually the same as: $('#new-todo').on('keyup', App.create.bind(App)).
That isn't where my confusion is. My question is:
How exactly are these copies of the functions with 'this' set to App actually being called? The app does not set them to a variable and then call that variable the way I had to when I was working in the console.
It just invokes the bound functions directly as soon as an event occurs on one of the jQuery elements. But I thought writing it this way would just return a copy of the function, and not run the function itself, if I am basing my assumptions on what I have figured out in the code I wrote above. I thought in order to invoke the function immediately, you would need to use call or apply.
I also realize that the app runs the bindEvents method when it starts (see line 46). So I understand that when you start the app, copies of the various functions are created with the correct 'this' bound to the functions. But...when/how do they actually get invoked without assigning them to variables? How are these copies accessed?
I think I have a flawed understanding of the bind method, so I would love some help. Thanks!
It sounds like you understand bind well enough. Perhaps there is some confusion with passing anonymous functions. As you know calling bind returns a new function and this can optionally be stored as a variable or passed as a function argument.
In the example below btn1 accepts a bound function as you've seen. This could also be written in a more long hand fashion with btn2. They're identical. btn3 doesn't receive a bound function, when its clicked its context is the button element, this looses all visibility of MagicalApp fucntions.
<button id="example1">button one bound</button>
<button id="example2">button one bound</button>
<button id="example3">button two unbound</button>
<script>
class MagicalApp {
add() {
console.log('this could do addition');
}
}
const app = new MagicalApp();
function contextOfEvent(event) {
console.log('contextSensitive', this.add)
}
const btn1 = document.querySelector("#example1");
btn1.addEventListener('click', contextOfEvent.bind(app));
const btn2 = document.querySelector("#example2");
const btn2ClickHandler = contextOfEvent.bind(app)
btn2.addEventListener('click', btn2ClickHandler);
const btn3 = document.querySelector("#example3");
btn3.addEventListener('click', contextOfEvent);
</script>
Coming from a C++ background, trying to work with an OO language that doesn't have explicit typing is a little more than a headache.
So I have dynamic elements for a webpage that are "controlled" by objects since there are tons of stuff I need to manage on each for it to work. The element is just the visual output of the data inside of the object itself, that's all I really need it for.
Except that I need the object to perform an internal function when it's clicked. That seems to be the biggest source of my headache thus far.
Javascript:
function onClick(file) //The external onClick function I use to try to get it to call from.
{
file.state = INUSE;
file.checkState();
}
function fileObject () { //The file object itself
this.element;
this.newElement();
//initialize stuff for the object
}
fileObject.prototype.newElement = function() { //creates a new element and sets its event listener
this.element.click(function() {onClick(this)});
}
fileObject.prototype.checkState = function() {/*does stuff*/} //apparently this is "not a function"
The error I get exactly is "file.checkState is not a function" from Firefox's console panel.
I'm still new to javascript, but after doing some debugging, I've come to find out that it's explicitly the onClick(this) function that is causing all of the errors. When used with something else, the onClick function works perfectly, but for some reason, the this keyword doesn't appear to actually be sending the reference to the fileObject since all checks show file being undefined when inside of the onClick scope.
Is there something fundamentally wrong about the way I'm trying to do this or am I just missing a step (or adding something that I don't need) that will help get this snippet working.
So you know, your initial problem isn't actually handling the action, but listening to it. click will trigger a synthetic click event, rather than liste for one.
You want ... .element.addEventListener("click", callback); that said, you face a second problem, immediately thereafter.
I will leave my example code as you've written it to not confuse the matter...
But when you see click( ) know that I mean subscribing with addEventListener, if element really does mean a browser DOM element. If it's not a standard browser element, and your own API, then ignore the previous portion, and carry on.
this is dynamically bound at the invocation time of the function (not at definition time).
The nearest function, scoped above, is your callback function that you are passing into .click( ... ).
Which is entirely different than the this which you mean outside of the callback.
Whatever is on the left-hand side of the dot is the this context for the duration of that particular invocation.
Needless to say, click() doesn't know enough to bind the this you mean, to the left-hand side of your callback.
The solution (or one of many) is to use lexical scoping and/or closure to retain the value of the object you mean.
// easy but messier
var fileObject = this;
... .click(function () { onClick(fileObject); });
// Cleaner with thunks:
function clickHandler (onClick, obj) {
return function () { onClick(obj); };
}
... .click(clickHandler(this));
Coming from c++ the way Javascript handles this will seem a little crazy, it looks like here you need to tell the function you've defined what this is - like so:
this.element.click(function() {onClick(this)}.bind(this));
I've been reading just about every article I can get my hands on about JavaScript scope to better understand it. I'd like to perfectly understand it by the end. I'm currently reading this article: http://www.digital-web.com/articles/scope_in_javascript/ and I've just finished reading the "Complications" section (a little more than halfway down) and thought it was very helpful, but not quite clear enough.
It uses the following code and considers the onclick behavior of the_button:
function BigComputer(answer) {
this.the_answer = answer;
this.ask_question = function () {
alert(this.the_answer);
}
}
function addhandler() {
var deep_thought = new BigComputer(42),
the_button = document.getElementById('thebutton');
the_button.onclick = deep_thought.ask_question;
}
window.onload = addhandler;
The article states ... an event handler[,] runs in a different context than when it’s executed as an object method. So, if I'm to understand correctly, then the call to the ask_question method in context of the script's object method is deep_thought.ask_question, making this deep_thought. But when an event in the DOM is triggered, then the call chain changes to DOMelement.eventHandler.deep_thought.ask_question making this DOMelement?
That is correct! 'this' in event handlers is the element you bound to. In this case it would be the_button. The alert would be 'undefined' as the_button has no the_answer property.
You can see an example at: http://jsfiddle.net/zG7KR/
See what this outputs:
this.ask_question = function () {
alert(this.the_answer);
};
The question
In Javascript, how can an event handler function refer to members of its parent? i.e. can you define an event handler function part of a larger object and have that function "know about" its parent?
(Note that someone else posted a nearly identical question Accessing variables of parent function in Javascript event handlers . It hadn't been answered. Hence the repost )
Erroneous Presuppositions
I had thought that at "function definition" time you could capture a copy of "this" for later re-use, (e.g. copy to "self"). Evidently I was wrong: after I bind the function to the event ("click()" below), "self" subsequently refers to the html anchor tag ('');
The general context :
trying to use encapsuation/object-orientedness for code-re-use in javascript.
Example
Here's a simple example (cribbed from elsewhere and modified).
The function succeeds when called during page load, but fails when the user preses "click"
MY LINK
<script type="text/javascript">
var Construct = function() {
var self = this;
this.attr1 = 3;
this.attr2 = 2;
this.childObj = {
method1: function () {
// this function fails if called from an event handler
// edited this function to "do something", i.e. provide a visual cue upon execute
var foo = self.attr1 * self.attr2;
alert ('value is ' + foo);
return foo;
}
}
}
var obj = new Construct();
// this call succeeds
alert (obj.childObj.method1());
//this call fails as soon as the event handler refers to "self"
$("#myLink").click(obj.childObj.method1);
</script>
</body>
</html>
Update/Edit
-Updated the example to give a 'visual cue' when it runs
-added this section.
My error. As pointed out below, the example works fine. My original, non-working code used this:
this.self = this
instead of
var self = this
I didn't realize the distinction (in Java they would be identical) and missed the fact that my example actually worked, (whereas my code failed).
Your code works fine. self refers to the object as it is supposed to. That's how the lexical scoping of javascript is defined.
The problem is your handler does nothing. method1 simply returns 6 but you never tell it do anything with that value. If you want to prove it to yourself, the line before the return, add an alert: alert(self.attr1 * self.attr2);
Working Example
I want to do something that in a classical object oriented language like Java, C# etc. is very easy to do. I simply want to access a property of an instantiated object. The object is globally scoped in the browser's window object, and provided by the twitter #anywhere API.
For my code examples, assume you have already logged the user in.
If I were using java for instance, I would say (assuming all fields were public:
twttr = new twtter();
String screenName = twtter.currentUser.data('screen_name');
For some reason, this is way hard in Javascript. I've gotten a workaround working where inside the anonymous method that the twitter anywhere API is using, I set the value I want to a DOM element, and fish it out later. This is ugly though. I just want to access it directly.
Here's what I have so far, which doesn't even pass syntax checks in eclipse:
function AnywhereFacade()
{
var twitterReference;
window.twttr.anywhere
(
return function(T)
{
twitterReference = T;
};
)
getValue(propertyToGet)
{
return twitterReference.currentUser.data(propertyToGet);
}
};
var anywhereFacade = AnywhereFacade();
var screen_name = anywhereFacade.getValue("screen_name");
alert("screen name is: " + propertyGetter);
Please help! Why is Javascript so hard to use anyway? What I'm trying to do is use a closure I think.
Thanks!
I have done something similar in my app since I am using the Facebook JavaScript SDK and Twitter SDK and want to provide a consistent interface to access both. So I namespace the variables under App. For twitter anywhere, this is how the variable is captured.
window.App = {};
twttr.anywhere(function(T) {
App.Twitter = {
getValue: function(property) {
return T.currentUser.data(property);
},
getPublicTimeline: function() {
return T.Status.publicTimelime();
}
};
});
We are calling the anywhere function and passing it a callback function. The callback function is needed because the anywhere library might not be loaded at this point. By passing the entire function, we are saying that this function should be executed whenever the anywhere library is loaded.
Now when the library does load, this function will execute, define the App.Twitter property which contains a getValue function. The anywhere or T object is captured in the closure.
If you now call,
App.Twitter.getValue("screen_name");
the actually anywhere object (T), will be used to get the screen_name property.
this is all I needed to do.
document.getElementById('messagePanel').innerHTML = "loading...";
window.twttr.anywhere(function(T)
{
document.getElementById('messagePanel').innerHTML = "screen_name: " + T.currentUser.data('screen_name');
});
this made me realize my issue was just that I had to use a callback for when twitter returned from the async call. that helped me solve my initial problem of how to wrap it for gwt.