My personal love for Prototype.js is evident, and that love has grown further as I have become familiar with object oriented programming using the framework. However, my appreciation has come at the expense of a few growing pains, one of which is maintaining the this keyword when dealing with event handlers. Normally in JavaScript, when you add an event handler such as onclick to an element, the triggered function can use the this keyword to refer to the element. Likewise, a class uses the this keyword to reference its member variables and methods. So, what happens when you attach an event to an element from within a class? Which object receives the attention of this this keyword?

Let’s take a look at a simple class that adds an onclick event to a link. A simple line passing in the link will initialize the class:

new foo($('mylink'));

And then the class itself. Notice how we are adding the onclick event to the link using Event.observe (similar to addEvent()).

var foo = Class.create();foo.prototype = {    initialize: function(ctrl) {
        this.ctrl = ctrl;
        this.ctrl.className = "testing";
        Event.observe(this.ctrl, 'click', this.dosomething, false);
    },    dosomething: function() {
        alert(this.className);
        // results in alerting 'testing'
    }}

Once the link is clicked, the method dosomething() will fire. When we use the this keyword we are getting a reference to the link, not the class. But what if we want to access the class. For example, we may want to call a member function as shown below.

dosomething: function() {
    this.dosomethingelse();
},dosomethingelse: function() {
    // do something else
}

The code above will produce the wonderful error:

> this.dosomethingelse is not a function

And the reason we get that error is because JavaScript is looking for a method of the link called dosomethingelse(). Instead, we want JavaScript to look for a method of the class called dosomethingelse(). In order to make the code above work, we need to preserve the this keyword. To do that, change this line:

Event.observe(this.ctrl, 'click', this.dosomething, false);

to this:

Event.observe(this.ctrl, 'click', this.dosomething.bindAsEventListener(this), false);

The difference between the two lines is that the second one has bindAsEventListener(this) added to it. bindAsEventListener() takes one parameter: the object that the this keyword should reference. If you wanted the this keyword to reference the link instead of the class, you could put this.ctrl as the parameter:

Event.observe(this.ctrl, 'click', this.dosomething.bindAsEventListener(this.ctrl), false);

So that is how the this keyword works within the Prototype.js class structure. Now, if anyone can help elaborate on the functionality of bindAsEventListener(), that would be great. This post show that it works, but I could use some clarification on why it works.

HTML Form Builder
Ryan Campbell

Prototype And The This Keyword by Ryan Campbell

This entry was posted 5 years ago and was filed under Notebooks.
Comments are currently closed.

· 20 Comments! ·

  1. Jonathan Snook · 5 years ago

    From the Microsoft script documentation (I use it often):

    “The call method is used to call a method on behalf of another object. The call method allows you to change the object context of a function from the original context to the new object”

    To clarify, the bindAsEventListener essentially allows you to change the context of “this” for the function you are binding to. This is done via the call method which bindAsEventListener uses. bindAsEventListener also passes the event object as an argument so that it’ll be available within the function called.

    I highly recommend downloading the Script documentation from Microsoft at http://microsoft.com/scripting/ (they’ve changed their system and requires a genuine OS to download)

  2. Jehiah · 5 years ago

    You should read PPK’s blog on finding a solution,

    http://www.quirksmode.org/blog/archives/2005/10/_and_the_winner_1.html

    And the final entry which solves

    http://ejohn.org/projects/flexible-javascript-events/

  3. Dustin Diaz · 5 years ago

    I’m not sure where I heard it, but the bind method was recently shown to me as well because I too was frustrated in Prototype’s lack of ‘this’ preservation for IE. It’s cool to see that it can be done, but it looks too much like a hack to me even though it’s perfectly valid.

    That’s one reason I still just stick to my own addEvent function which is an alteration of the addEvent() contest winner along with some other memory leak prevention tweaks.

  4. alexander · 5 years ago

    I was memory leaks was mentioned above.. they can be a big problem (in IE, real browsers do fine) but fortunaly if you add an event handler using Event.observe prototype will automaticly clean up on unload, to help IE with the garbage handling..

  5. logicnazi · 5 years ago

    Well I haven’t looked at the bindasEventlistner function but I did take a look at the code to bind and what it does is pretty simple.

    Basically instead of binding the function you pass to the event it binds a new anonymous function to the event. This anonymous function has one line which uses the call (or is it apply?) method to call the method you passed with the this variable set to the object you passed. Pretty simple so far.

    The only place this gets complicated (conceptuall the code is trivial) is in creating that anonymous function. Once you understand how functions work it is pretty obvious but it can be confusing at first. Essentially the anonymous function defined by bind is a closure, meaning it retains the scope it had while created, i.e., it can still access the object you passed into bind.

    If you want some help understanding closures in JS I found this great page on closures in JS

    Peter

  6. logicnazi · 5 years ago

    Oops, I screwed up in my post above. Ignore the entire part about binding to an event I had a brain fart because we were talking about events.

    All bind does is return a function which is bound to a specific object, i.e., will have the same this pointer no matter where you call it. This is usefull if, for instance, you want to pass a method around.

    So where I wrote “instead of binding the function you pass to the event” ignore that bit. What I should have said was that bind returns a new anonymous function which calls your function with the appropriate this pointer.

  7. Leif Olsen · 5 years ago

    Use an indirect method call to avoid conflicting this

    Hello, I often use what I call an indirect method call to solve the conflicting this. See code below:

    var foo = Class.create(); foo.prototype = {

    initialize: function(ctrl, msg) {
        this.ctrl = ctrl;
        this.ctrl.msg = msg;
        this.msg = 'foo-->' + msg;
        // prepare for indirect method call
        this.prepareIndirect();
    },prepareIndirect: function() {
      // Preserve 'this' for indirect call
      var thisObj = this;   this._doSomething = function(ev) {
        thisObj.memberfunction(this);
      };  // 2 versions of indirect call
      Event.observe(this.ctrl, 'click', 
        this._doSomething, false); 
      Event.observe(this.ctrl, 'click', 
        function(ev) { thisObj.memberfunction(this);}, 
        false);},memberfunction: function(thisFromEventHandler) {
      // M$ie does not allow for adding extra 
      // attributes to this, so thisFromEventHandler.msg 
      // will be undefined here! Firefox OK
      alert('memberfunction: this.msg: ' + this.msg + 
        ',   thisFromEventHandler.msg: ' + thisFromEventHandler.msg );
    } 
    

    };

  8. Rakesh Pai · 5 years ago

    I just use a var self = this; within the class’s (global) declarations. This makes the self object point to the class throughout the class. Later, when I want to use the classes methods in an anonymous function or a closure within the class, I simply use self.doSomething();

    That way, I get to use the real power of the this keyword with its changing values depending on the context, while still retaining the reference to the class whenever I need it.

  9. brent · 5 years ago

    This means of “enforcing” scope is used in dragdrop.js, though the one you describe is more compact. They have things like this:

    this.eventMouseDown=this.startDrag.bindAsEventListener(this);

    followed by:

    Event.observe(this.handle, “mousedown”, this.eventMouseDown);

    this is one case where putting it all smashed together (as you did) improves readability…

  10. brent · 5 years ago

    ahem. let me try that again:

    This means of “enforcing” scope is used in dragdrop.js, though the one you describe is more compact. They have things like this:

    this.eventMouseDown=this.startDrag.bindAsEventListener(this);

    followed by:

    Event.observe(this.handle, “mousedown”, this.eventMouseDown);

    this is one case where putting it all smashed together (as you did) improves readability…

  11. Tobie Langel · 5 years ago
    If you want some help understanding closures in JS I found this great page on closures in JS

    Thanks Peter for one of the best and most useful javascript related reference ever!

  12. bill · 5 years ago

    I’m trying to decide if Prototype offers any value to me. Can anyone tell me why all these observe and bind syntaxes are any better than the “var self = this”??

  13. Simon · 4 years ago

    The functionality of bindAsEventListener() is explained in detail on the page of its creator Daniel Brockman http://www.brockman.se/writing/method-references.html.utf8

  14. Jim · 4 years ago

    “I highly recommend downloading the Script documentation from Microsoft”

    I had a look at it, and now I’m confused. Am I the only one wondering what windows OS scripting has to do with javascript?

  15. Alex Zendejas · 4 years ago

    Everyone needs a hug.

  16. Eimantas · 4 years ago

    Everyone needs a hug.

  17. Frankenstein · 4 years ago

    Yes, everyone needs a hug!

  18. greenie2600 · 4 years ago

    I love you more than life itself. You just solved the problem I’ve been struggling with for the past several hours. Thanks!

  19. Jan · 3 years ago

    Thank you very much for ferreting this out. I wasted an entire day tinkering ….