Object method as event listener

J

Jeremy

I want each instance of an object to be able to listen for input events.
When the event occurs, a method of the object should be called, such
that "this" is in scope and refers to the object instance.

Is this possible?

Example:

function MyConstructor(element)
{
//element is some HTML element
this.addListeners(element);

this.foo = "Bar";

return this;
}

MyConstructor.prototype.addListeners = function(element)
{
element.addEventListener("keypress", this.doSomething, true);
}

MyConstructor.prototype.doSomething = function(e)
{

alert(this.foo.length); //Error: this.foo has no properties

alert(this);
//shows the HTML element that triggered the event
}

Is there any way to get "this" to refer to the object in the event listener?

Thanks,
Jeremy
 
A

aka

No problem:
function MyConstructor(element)
{
//element is some HTML element
this.addListeners(element);

this.foo = "Bar";
// not necessary
//return this;
}

MyConstructor.prototype.addListeners = function(element)
{
// don't take native DOM function, it's not supported by IE5/6,
take a workaround, for example:
// http://aka-fotos.de/research/js/prototype_extended.js < take the
last both functions
//element.addEventListener("keypress", this.doSomething, true);
addEventListener(element, "keypress", this.doSomething, true);
// make a backlink
element._obj = this;
// it's not necessary any more to make a object function because
THIS is the html element
MyConstructor./*prototype.*/doSomething = function(e)
{

alert(this._obj.foo.length); // should work now

alert(this._obj);
//shows the MyConstructor object now
}

That should work, I do it very often in OO and UI programming.

Andi
 
R

RobG

Jeremy said:
I want each instance of an object to be able to listen for input events.
When the event occurs, a method of the object should be called, such
that "this" is in scope and refers to the object instance.

All function objects have a this value that is established when the
function is called, not when you declare it. You can modify the object
used for the this value using the function's apply() or call() methods.

The usual method is to use the this keyword to create a reference back
to the object when you attach the event, not try to get a reference to
the object at some later time. You might also consider simply adding
the property to a prototype object at an appropriate point in the scope
chain and avoid munging the this keyword altogether.

Is this possible?

Example:

function MyConstructor(element)
{
//element is some HTML element
this.addListeners(element);

this.foo = "Bar";

return this;
}

MyConstructor.prototype.addListeners = function(element)
{
element.addEventListener("keypress", this.doSomething, true);

Is this only for W3C browers? You seem to be ignoring IE's attachEvent.
}

MyConstructor.prototype.doSomething = function(e)
{

alert(this.foo.length); //Error: this.foo has no properties

Because the foo property belongs to the (possibly anonymous) object
that was used to attach the event, which is not what the this keyword
will refer to when the event calls the function.

In Gecko browsers, the this keyword will refer to the HTML element that
the event is attached to. In IE, it will refer to the window/global
object.
alert(this);
//shows the HTML element that triggered the event

Or the window object in IE. Try replacing the body of the addListeners
function with:

var obj = this;
element.onkeypress = function(){
obj.doSomething.apply(obj);
}

But the closure will likely cause a memory leak in IE. If you really
want to use attachEvent, try:

var obj = this;
if (element.addEventListener){
element.addEventListener(
'keyup',
function() {obj.doSomething.apply(obj)},
true
);
} else if (element.attachEvent){
element.attachEvent(
'onkeyup',
function() {obj.doSomething.apply(obj)}
);
}

Again you have a closure/memory leak issue in IE.

}

Is there any way to get "this" to refer to the object in the event listener?

Another strategy is to add a property to the element that points back
to the object and use the event to find the target/source element and
hence get the foo property:

MyConstructor.prototype.addListeners = function(element)
{
element.srcObj = this;
if (element.addEventListener){
element.addEventListener('keyup', this.doSomething, true);
} else if (element.attachEvent){
element.attachEvent('onkeyup', this.doSomething);
}
}

MyConstructor.prototype.doSomething = function(e)
{
var e = e || window.event;
var tgt = e.target || e.srcElement;
alert(tgt.srcObj.foo);
}
 
Y

Yanick

Jeremy said:
I want each instance of an object to be able to listen for input events.
When the event occurs, a method of the object should be called, such
that "this" is in scope and refers to the object instance.

Is this possible?

Yes ! With the help and magic of 'apply', this can be done. Here's how
it can be done (the bindAsEventListener function was taken from the
Prototype library at http://prototype.conio.net/) :


<div id="test1">Click me 1</div>
<div id="test2">Click me 2</div>

<script type="text/javascript">

Function.prototype.bindAsEventListener = function(object) {
var args = [];
for (var i = 0; i < arguments.length; i++)
args.push(arguments);

var __method = this;
var object = args.shift();
return function(event) {
return __method.apply(object, [( event ||
window.event)].concat(args).concat(arguments));
}
};

var Events = {

clickHandler: function(event) {

alert( this.id + ' was clicked !' );

}

};

var test1 = document.getElementById('test1');
var test2 = document.getElementById('test2');

test1.onclick = Events.clickHandler.bindAsEventListener(test1);
test2.onclick = Events.clickHandler.bindAsEventListener(test2);


As you can see, the bindAsEventListener ensures that 'this' inside the
function always refers to the object passed as parameter.

Hope this helps.

-Yanick
 
Y

Yanick

BTW : you would normally only worry about memory leaks when repeatedly
manipulating the same object property with such code
(bindAsEventListener), so it's mostly a matter of choosing wisely how
to use what when.
 
J

Jeremy

RobG said:
Is this only for W3C browers? You seem to be ignoring IE's attachEvent.

Initially, I'm targeting standards, not I.E. If IE7 fails to support
the W3 model I might consider hacking in attachEvent support, but this
is a personal project so I can support whatever I wish and I prefer to
target standards.

Thanks for the thorough explanation. All that replied have been quite
helpful.

Jeremy
 
R

RobG

Yanick said:
Jeremy said:
I want each instance of an object to be able to listen for input events.
When the event occurs, a method of the object should be called, such
that "this" is in scope and refers to the object instance.

Is this possible?

Yes ! With the help and magic of 'apply', this can be done. Here's how
it can be done (the bindAsEventListener function was taken from the
Prototype library at http://prototype.conio.net/) :

<div id="test1">Click me 1</div>
<div id="test2">Click me 2</div>

<script type="text/javascript">

Function.prototype.bindAsEventListener = function(object) {
var args = [];
for (var i = 0; i < arguments.length; i++)
args.push(arguments);

var __method = this;
var object = args.shift();
return function(event) {
return __method.apply(object, [( event ||
window.event)].concat(args).concat(arguments));
}
};

var Events = {
clickHandler: function(event) {
alert( this.id + ' was clicked !' );


The value of this.id isn't necessarily the id of the element that was
clicked on, it is the id of the element reference that was passed to
the function. It would be better example to have:

clickHandler: function(event) {
var tgt = event.target || event.srcElement;
if (tgt.nodeType != 1) tgt = tgt.parentNode;
alert(
'You clicked on ' + tgt.id + '\nbut '
+ 'this.id is: ' + this.id
);
}

and modify the attach code as suggested below.

}
};

var test1 = document.getElementById('test1');
var test2 = document.getElementById('test2');

test1.onclick = Events.clickHandler.bindAsEventListener(test1);
test2.onclick = Events.clickHandler.bindAsEventListener(test2);

It might have been a better example to use:

test1.onclick = ...(test2);
test2.onclick = ...(test1);

To show how the this value had been modified.

As you can see, the bindAsEventListener ensures that 'this' inside the
function always refers to the object passed as parameter.

But it seems a long way around, considering you passed the object to
the function in the first place and could just grabbed it from the
arguments object instead of messing with apply and the this value.

The following:

var Events ={};
Events.showID = function (){
alert(this.id);
}
var test3 = document.getElementById('test3')
test3.onclick = Events.showID;


achieves the same result in much less code - provided you have an
element with id='test3' of course. :)
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,773
Messages
2,569,594
Members
45,125
Latest member
VinayKumar Nevatia_
Top