views:

285

answers:

2

Hello,

I'm trying to use the Class methods of prototype.js to manage my object hierarchy on my current project, but I have some trouble with the this keyword in conjonction with Class.create.

Here is a piece of old-fashioned plain js code to create inheritance:

var Super1 = function () {
    this.fu = "bar";
}

var Sub1 = function () {
    this.baz = "bat";
    this.f = function (e) {
     alert("Sub1:"+this.fu+this.baz);
    }.bindAsEventListener(this);
    document.observe("click", this.f);
};
Sub1.prototype = new Super1();
new Sub1();

And here is my first attempt at mimicking this with Class.create:

var Super2 = Class.create({
    fu: "bar"
});

var Sub2 = Class.create(Super2, {
    baz: "bat",
    f: function (e) {
     alert("Sub2:"+this.fu+this.baz);
    }.bindAsEventListener(this),
    initialize: function () {
     document.observe("click", this.f);
    }
});
new Sub2();

So far so good... But of course it doesn't work: f is bound to window, not to the object created with new. The only way I found is:

var Super3 = Class.create({
    fu: "bar"
});

var Sub3 = Class.create(Super3, {
    baz: "bat",
    f: function (e) {
     alert("Sub3:"+this.fu+this.baz);
    },
    initialize: function () {
     this.f = this.f.bindAsEventListener(this);
     document.observe("click", this.f);
    }
});
new Sub3();

But it's really inelegant. How am I supposed to handle this?

edit (to reply to Colin):

  • I need to bind f itself, so that I can call stopObserving on f (see http://prototypejs.org/api/event/stopObserving)

  • I still need bindAsEventListener every time I need this inside the listener, since by default this is the element that fires the event (see http://prototypejs.org/api/event/observe)

  • I'm still waiting for an anwser on the googlegroup :) I kind of posted here to see whether I can get a faster answer via S.O.

  • I could (and probably should) use bind instead of bindAsEventListener. I used the latter to make it clear that I'm getting a listener. It doesn't change the fact that the binding procedure is inelegant.

A: 

I'm not too clear about this area, but I think the answer is (untested):

var Sub3 = Class.create(Super3, {
  baz: "bat",
  f: function() {
    alert("Sub3:"+this.fu+this.baz);
  },
  initialize: function () {
    document.observe("click", this.f.bind(this));
  }
});

Class arranges that initialize is called as an object method of the new object, so 'this' refers to the new object inside 'initialize', so you just need to bind this.f to this.

Incidentally, bindAsEventListener is almost never needed: Event.observe (and Element.observe) call the handler with the event as the first argument anyway.

Incidentally 2, the Prototype & Scriptaculous google group is a very helpful forum.

Colin Fine
Thanks Colin. I edited the original post to reply to you.
Alsciende
+2  A: 

You almost got it, just no need to redeclare this.f:

var Sub3 = Class.create(Super3, {
    baz: "bat",
    f: function () {
        alert("Sub3:"+this.fu+this.baz);
    },
    initialize: function () {
        document.observe("click", this.f.bindAsEventListener(this));
        // You could also use .bind(), since you don't pass any 
        // other arguments to f
    }
});

Edit: In reply to your comment, you could do something like this (although it is arguably as 'ugly' as your example):

var Sub3 = Class.create(Super3, {
    baz: "bat",
    initialize: function () {
        this.f = function () {
           alert("Sub3:"+this.fu+this.baz);
        }.bind(this);

        document.observe("click", this.f);
    }
});

Now you can use stopObserving with this.f. When you need to re-register the listener, you can simply use this.f again, since it'll still be bound to the same instance as was used when initialize was called.

PatrikAkerstrand
As explained before, I do need to bind f itself so I can then call stopObserving.
Alsciende
Alright, code updated. Tell me if you like it or if it's too 'ugly' for your taste =)
PatrikAkerstrand
@Machine is correct in his implementation
Josh
That's quite good, and deserves the point. But I think you should get rid of clickHandler altogether, and bind f (with #bind) on its definition : var f = function () { ... }.bind(this);
Alsciende
Or better, this.f, so that it can be stopObserved from another function: initialize: function () { this.f = function () { ... }.bind(this); document.observe("click", this.f); }
Alsciende
Updated as you suggested Cédric
PatrikAkerstrand