Event listeners/broadcasting solution

P

Pavils Jurjans

Hello,

I wanted to propose a small class that would help to overcome the
feature that's missing in MSIE. I'd like to get some feedback from
people and, perhaps, improvements in code/other ideas:

/*******************************************************************************************
DCListener class
Description: provides basic event listener functionality
Interface :
contructor DCListener(obj, evt)
public void dispatch(evt)
public int addSubscriber(fnSubscriber)
public bool removeSubscriber(id)
public void unregister()
public static int id
public static {DCListener} index
*******************************************************************************************/

function DCListener(obj, evt) {
var id = DCListener.id++;
this.id = id;
this.obj = obj;
this.evt = evt;
this.subscribers = {};
this.subId = 0;
this.save = obj.evt;

var listenerId = this.id;
obj[evt] = function() {
DCListener.index[listenerId].dispatch();
}

DCListener.index[id] = this;
}
DCListener.prototype.dispatch = function(evt) {
var subscribers = DCListener.index[this.id].subscribers;
for (var sub in subscribers) subscribers[sub](evt);
}
DCListener.prototype.addSubscriber = function(fnSubscriber) {
var id = this.subId++;
this.subscribers[id] = fnSubscriber;
return id;
}
DCListener.prototype.removeSubscriber = function(id) {
if (id in this.subscribers) {
delete this.subscribers[id];
return true;
} else {
return false;
}
}
DCListener.prototype.unregister = function() {
delete DCListener.index[this.id];
this.obj[this.evt] = this.save;
delete this.id;
delete this.obj;
delete this.evt;
delete this.subscribers;
delete this.subId;
delete this.save;
}
DCListener.id = 0;
DCListener.index = {};

********** end of code **********

Usage:

var lstDocumenOnClick = new DCListener(document, "onclick");
var idA = lstDocumenOnClick.addSubscriber(function() {
alert("[A] received document.onclick");
});
var idB = lstDocumenOnClick.addSubscriber(function() {
alert(" received document.onclick");
});
// Later, to remove the first subscriber:
lstDocumenOnClick.removeSubscriber(idA);

****************************************

This is just starting draft, and I know it needs some things to be
done about:
1) Passing the event object to every subscriber
2) Perhaps syncing the method naming with Mozilla event
listener/subscriber model.
3) Testing for memory leaks

Please, I am open to ideas, and I hope this thingy might be useful for
JS community.

Regards,

Pavils
 
Y

Yann-Erwan Perio

Pavils Jurjans wrote:

Hello,
I wanted to propose a small class that would help to overcome the
feature that's missing in MSIE.

Ah but it's not missing in IE, IE simply doesn't follow the W3C Events
model but its own model (which is inferior to the W3C's one IMHO) - the
methods you want to look at are attachEvent and detachEvent.
this.save = obj.evt;

You probably meant
this.save=obj[evt];

I note that you don't trigger the existing listener if there's one, this
might be a problem if your module is to interact with scripts setting
handlers the 'old' way.
DCListener.index[listenerId].dispatch();

You need to pass the evt object here, as you've noted in your "todos".
for (var sub in subscribers) subscribers[sub](evt);

Dispatching the handlers like this isn't enough, you should use "call"
to set the correct "this" value in the listener:

for (var sub in subscribers)
subscribers[sub].call(this.obj, evt);

This way, the "this" keyword will refer to the target element, like in a
element.onevent or element.addEventListener(...) (but not with
attachEvent, which unfortunately doesn't make the assignment).
Please, I am open to ideas, and I hope this thingy might be useful for
JS community.

I'm sure it will, the code's promising and your conception is strong
(I'd make the "dispatch", static members and DCListener private, though,
not public - a minor detail).

You might also want to look at a similar module written by Lasse
Reichstein Nielsen, less object-oriented than yours, but tackling all
relevant issues:

<URL:http://www.infimum.dk/privat/eventListener.js>


Regards,
Yep.
 
P

Pavils Jurjans

Thanks, Yann-Erwan for your suggestions, you really have an eagle's
eye

So, here comes an update code:
***********************************************************
/*******************************************************************************************
DCListener class
Description: provides basic event listener functionality
Interface :
private int id;
private object obj;
private string evtType;
private {Function} subscribers;
private int subId;
private int originalHandler;
contructor DCListener(obj, evt)
public void dispatch(evt)
public int addSubscriber(fnSubscriber)
public bool removeSubscriber(id)
public void unregister()
private static int id
private static {DCListener} index
*******************************************************************************************/

function DCListener(obj, evtType) {
var id = DCListener.id++;
DCListener.index[id] = this;
this.id = id;
this.obj = obj;
this.evtType = evtType;
this.subscribers = {};
this.subId = 1;
this.originalHandler = obj[evtType] ?
this.addSubscriber(obj[evtType]): 0;
obj[evtType] = function(evt) {
evt = evt||window.event;
DCListener.index[id].dispatch(evt);
}
}
DCListener.prototype.dispatch = function(evt) {
var subscribers = DCListener.index[this.id].subscribers;
for (var subId in subscribers) subscribers[subId].call(this.obj,
evt);
}
DCListener.prototype.addSubscriber = function(fnSubscriber) {
var subId = this.subId++;
this.subscribers[subId] = fnSubscriber;
return subId;
}
DCListener.prototype.removeSubscriber = function(subId) {
if (subId in this.subscribers) {
delete this.subscribers[subId];
return true;
} else {
return false;
}
}
DCListener.prototype.unregister = function() {
delete DCListener.index[this.id];
if (this.originalHandler) this.obj[this.evtType] =
this.subscribers[this.originalHandler];
delete this.id;
delete this.obj;
delete this.evtType;
delete this.subscribers;
delete this.subId;
delete this.save;
}
DCListener.id = 0;
DCListener.index = {};

***********************************************************

Some notes on your comments:
Ah but it's not missing in IE, IE simply doesn't follow the W3C Events
model but its own model (which is inferior to the W3C's one IMHO) - the
methods you want to look at are attachEvent and detachEvent.

Yes, I really missed that. But, this approach is quite poor, if
comparing with the one offered by W3C. It's a sad fact, that they
still prefer to stay away from standards.
I note that you don't trigger the existing listener if there's one, this
might be a problem if your module is to interact with scripts setting
handlers the 'old' way.

It's fixed now. The original handler becomes the first subscriber.
You need to pass the evt object here, as you've noted in your "todos".
Implemented.

Dispatching the handlers like this isn't enough, you should use "call"
to set the correct "this" value in the listener:

for (var sub in subscribers)
subscribers[sub].call(this.obj, evt);

This way, the "this" keyword will refer to the target element, like in a
element.onevent or element.addEventListener(...) (but not with
attachEvent, which unfortunately doesn't make the assignment).

That's right, I foregone the scope issue. It's fixed now.
I'm sure it will, the code's promising and your conception is strong
(I'd make the "dispatch", static members and DCListener private, though,
not public - a minor detail).

Of course, all the talk about private/public is pretty irrelevant,
since there are no private methods in JS. Well, they can be mastered
via closures tho. But, I prefer to use this notation in the interface
description, so that developer would honor it.

I think you mistyped, saying you'd like to see "DCListener" private,
as it's the class name ;) Regarding the dispatch method, I think it's
cool to let it be public, because it allows to 1)provoke the event
triggering from code; and 2) Create your own custom objects that can
issue events and can have subscribers attached:

var lstDocumentOnPress = new DCListener(document, "onkeydown");

var subA = lstDocumentOnPress.addSubscriber(function(evt) {
var charCode = event.keyCode ? event.keyCode : e.which;
document.f.traceA.value = "A has received the event: " + charCode;
});
var subB = lstDocumentOnPress.addSubscriber(function(evt) {
var charCode = event.keyCode ? event.keyCode : e.which;
document.f.traceB.value = "B has received the event: " + charCode;
if (charCode == 32) lstCustomObjectEvt.dispatch();
});

var myCustomObject = {};
var lstCustomObjectEvt = new DCListener(myCustomObject, "evt");
lstCustomObjectEvt.addSubscriber(function() {
alert("Hey, I've got custom event!");
});

<body>
<form name="f">
<input type="text" name="traceA" size="50"/><br/>
<input type="text" name="traceB" size="50"/>
</form>
You might also want to look at a similar module written by Lasse
Reichstein Nielsen, less object-oriented than yours, but tackling all
relevant issues:
<URL:http://www.infimum.dk/privat/eventListener.js>

I will surely have some study of his work. It's quite hard to read it,
as it has no much comments :),

Rgds,

Pavils
 

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,767
Messages
2,569,571
Members
45,045
Latest member
DRCM

Latest Threads

Top