Eventhandling, preserving Scope with Closures?

M

Marc Tanner

Hello,

I am currently working on a eventhandling system or something similar,
and have the problem of loosing scope. I have read many interesting
posts on this group and the faq article about closure, but it seems
that i have still not understood everything. Below is my attempt to
preserve the scope but it's not really nice and i think with the use
of closure could it be done better. But at the moment i am quite
confused and hope that someone can give me a hint...

Another problem is that different browser have different ways of
walking through a for-in loop, and so the functions are called in
different sequences. Any idea how the js-engine handles a for-in loop?

any ideas, comments, recommendations are welcome...

<html>
<head>
<script type="text/javascript">

HTMLElement = function (type) {
this.node = typeof type == "string"
? document.createElement(type) : type;
this.events = {};
};

HTMLElement.prototype.initEvent = function(event) {
this.events[event] = {};
/*
self is a reference which gives us the ability
to access the object from the inner function below
*/
var self = this;
this[event] = function (e) {
e = e || window.event;
for (var x in self.events[event]) {
if (typeof self.events[event][x] == "function") {
self.events[event][x](e, this);
} else if (typeof self.events[event][x][x] == "function") {
self.events[event][x][x](e, this);
}
}
};

this.node[event] = this[event];
};

HTMLElement.prototype.attachEvent = function(event, obj, name) {
if (!this.events[event]) {
this.initEvent(event);
}
if (typeof obj == "object" && typeof name == "string") {
this.events[event][name] = obj;
} else if (typeof obj == "string" && typeof name == "function") {
this.events[event][obj] = name;
}
};

HTMLElement.prototype.removeEvent = function(event, name) {
if (this.events[event] && this.events[event][name]) {
delete this.events[event][name];
}
};

var myDemoClass = function (a, b) {

var self = this;

this.a = a;
this.b = b;

/* private function */

var alertAandB = function (){
alert(self.a+" "+self.b);
};

this.output = new HTMLElement(document.body);
this.output.attachEvent("onclick", this, "alertA");
this.output.attachEvent("onclick", this, "alertB");
this.output.attachEvent("onclick", "alertAandB", alertAandB);
};

/* 2 public methods */

myDemoClass.prototype.alertA = function() {
alert(this.a);
};

myDemoClass.prototype.alertB = function() {
alert(this.b);
};

this.onload = function() {
myDemoInstance = new myDemoClass("Hello", "World");
};

</script>
</head>
<body>
click me
</body>
</html>
 
R

Richard Cornford

Marc said:
I am currently working on a eventhandling system or something similar,

If you cannot decide what your are building how will you be able to tell
when your have finished?
and have the problem of loosing scope.

Scope or object instance associations?
I have read many interesting
posts on this group and the faq article about closure,
but it seems that i have still not understood everything.
Below is my attempt to
preserve the scope but it's not really nice and i think
with the use of closure could it be done better.

Yes, that code is inelegant, but then it seems to be doing what it
purports to do, which makes it an inadequate explanation of what
constitutes a problem in this context.
But at the moment i am quite
confused and hope that someone can give me a hint...

That works both ways. Can you give a hint about what it is that is
confusing you? You could, for example, start by explaining how you
understand closures to work. That may then become the subject for
comment, correction, additional explanation and examples.
Another problem is that different browser have different ways of
walking through a for-in loop, and so the functions are called in
different sequences. Any idea how the js-engine handles a for-in loop?

The ECMA specification says "The mechanics of enumerating the properties
.... is implementation dependent." So the order cannot be assumed or
relied upon in a web browser context.
any ideas, comments, recommendations are welcome...

<html>
<head>
<script type="text/javascript">

HTMLElement = function (type) {
^^^^^^^^^^^
Absolutely do not give any of your object constructors, functions or
global variables names that correspond with W3C DOM defined interfaces
as Mozilla/Gecko browsers make objects with those names available as
properties of the global object and there will be naming conflicts and
resulting unpredictable behaviour.
this.node = typeof type == "string"
? document.createElement(type) : type;
this.events = {};
};

HTMLElement.prototype.initEvent = function(event) {
this.events[event] = {};
/*
self is a reference which gives us the ability
to access the object from the inner function below
*/
var self = this;
this[event] = function (e) {
e = e || window.event;
for (var x in self.events[event]) {
if (typeof self.events[event][x] == "function") {
self.events[event][x](e, this);
} else if (typeof self.events[event][x][x] == "function") {
self.events[event][x][x](e, this);
}
}
};

this.node[event] = this[event];

I don't see any need for - this[event] -, it is never used elsewhere so
I can't see any reason for not assigning the inner function directly
to - this.node[event] -.

this.output = new HTMLElement(document.body);
this.output.attachEvent("onclick", this, "alertA");
this.output.attachEvent("onclick", this, "alertB");
this.output.attachEvent("onclick", "alertAandB", alertAandB);
};
this.onload = function() {
myDemoInstance = new myDemoClass("Hello", "World");
<snip>

So the output should be an alert saying "Hello", an alert saying "World"
and an alert saying "Hello World", but not necessarily in that order.
And that is what does happen; the scope seems to be working and the -
this - associations seem to be working.

If you want suggestions on alternative implementations you will have to
provide something approaching a specification for the task or a
description of the problem that it is attempting to solve. Lots of
implementations might be something similar to an event handling system,
few would really be applicable to your situation.

Richard.
 
M

Marc Tanner

Richard Cornford said:
Yes, that code is inelegant, but then it seems to be doing what it
purports to do, which makes it an inadequate explanation of what
constitutes a problem in this context.

Yes, it does what it should, but i can't figure out a solution with
closure which does the same and is elegant.
That works both ways. Can you give a hint about what it is that is
confusing you? You could, for example, start by explaining how you
understand closures to work. That may then become the subject for
comment, correction, additional explanation and examples.

Well i think the main goal of using closure is that a function has
access to all private member of it's outer function(s) even when it's
already returned.
The ECMA specification says "The mechanics of enumerating the properties
... is implementation dependent." So the order cannot be assumed or
relied upon in a web browser context.

So i have to use scalar arrays instead another problem...
^^^^^^^^^^^
Absolutely do not give any of your object constructors, functions or
global variables names that correspond with W3C DOM defined interfaces
as Mozilla/Gecko browsers make objects with those names available as
properties of the global object and there will be naming conflicts and
resulting unpredictable behaviour.

OK, i totally agree.
this.node = typeof type == "string"
? document.createElement(type) : type;
this.events = {};
};

HTMLElement.prototype.initEvent = function(event) {
this.events[event] = {};
/*
self is a reference which gives us the ability
to access the object from the inner function below
*/
var self = this;
this[event] = function (e) {
e = e || window.event;
for (var x in self.events[event]) {
if (typeof self.events[event][x] == "function") {
self.events[event][x](e, this);
} else if (typeof self.events[event][x][x] == "function") {
self.events[event][x][x](e, this);
}
}
};

this.node[event] = this[event];

I don't see any need for - this[event] -, it is never used elsewhere so
I can't see any reason for not assigning the inner function directly
to - this.node[event] -.

Right, until now it's not used but it could be useful if i want to
fire the event by myself.
<snip>

So the output should be an alert saying "Hello", an alert saying "World"
and an alert saying "Hello World", but not necessarily in that order.
And that is what does happen; the scope seems to be working and the -
this - associations seem to be working.

If you want suggestions on alternative implementations you will have to
provide something approaching a specification for the task or a
description of the problem that it is attempting to solve. Lots of
implementations might be something similar to an event handling system,
few would really be applicable to your situation.

Ok, it should be possible to associate several functions/methods from
different classes (i know JS doesn't really have classes) with the
same event of an (x)HTML-node. Calling the methods in a way that this
is pointing to the right object/instance. Further should it be done as
elegant as possible ;)

thanks for your effort

Marc
 
R

Richard Cornford

Marc said:
Richard Cornford wrote:

Well i think the main goal of using closure is that a function has
access to all private member of it's outer function(s) even when it's
already returned.

Where closures only are concerned you probably don't want to be talking
in terms of private members. The items that the inner function has
access to are the function declarations, local variables and formal
parameters from its containing functions. They only become 'private
members' if that is the concept that is being implemented (i.e. some
sort of object).
So i have to use scalar arrays instead another problem...

Not necessarily. One of my earliest applications of closures was in
making stacks of functions. Previously I had been assembling chains of
object instances with 'next' and 'previous' relationships and having
each call its 'next' (or 'previous') so that execution could ripple
through an indefinitely long structure of like-objects.

Closures (and scope chain augmentation) allow similar structures, often
more simply, and with the advantage that the 'objects' in the chain are
functions so the 'object' can be called itself instead of calling a
method on that object. The procedure is also flexible enough to allow
other structures such as trees, where execution of a node will ripple
execution back to the root, for example. And a combination of closures,
scope chain augmentation and function properties referring to related
nodes allows the same structure to act as a tree and two stack/lists
with different ordering at the same time (though mostly there is no
need).

The stacks represent stacks of call-back function rather than object
instances so the callback functions themselves need implement any
associations with other objects they are interested in. The call-back
functions also need to satisfy some sort of contract (be of a like form
and behaviour) so that the stacking functions can know how to handle
them, pass parameters, ect.

The following are two such function stacks. The contract that the
call-back functions need to fulfil is that they should not expect more
than one parameter (as passed on from the call to the function that acts
as the object representing the stack, and that they provide a return
value. That return value should be - true - if the callback function
want to say in the stack and false if it wants to be removed (has done
its job). I use this style of contract a great deal with this type of
function stack because it avoids needing an explicit - removeFunction -
method to be implemented on the function object that represents the
stack.

The execution order of the two implementations is first in last called
(FILC) and first in first called (FIFC). More complex implementation may
allow for the re-ordering of such a stack, under external control or as
a consequence of its operation, but that is not relevant here.

function getNewFILCFncStac(fnc){
function getNewStackFnc(f){
var next = null;
function t(a){
next = next&&next(a);
return (f(a))?t:next;
}
t.addItem = function(d){
if(f != d){
if(next){
next.addItem(d);
}else{
next = getNewStackFnc(d);
}
}
return this;
};
return t;
}
var base = getNewStackFnc(fnc);
fnc = function(a){
base = base&&base(a);
};
fnc.addItem = function(d){
if(base){
base.addItem(d)
}else{
base = getNewStackFnc(d);
}
};
return fnc;
}

function getNewFIFCFncStac(fnc){
function getNewStackFnc(f, next){
function t(a){
next = next&&next(a);
return (f(a))?t:next;
}
return t;
}
var base = getNewStackFnc(fnc, null);
fnc = function(a){
base = base&&base(a);
};
fnc.addItem = function(d){
base = getNewStackFnc(d, base);
};
return fnc;
}

Their operation is to call, say, - getNewFIFCFncStac - and pass a
reference to a callback function as the argument. The function object
returned is then assigned to something; private member, object property,
event handler, etc., from where it can be executed. But that function
object also provides an - addItem - method that can be used to add
additional call-back functions to the stack.

An example application of one of these stacks is:-

function resizeMonitor(fnc){
var fncStack,global = this;
function main(funcRef){
if(funcRef){
fncStack.addItem(funcRef);
globalCheck();
}
}
function globalCheck(){
if(global.onresize != fncStack){
if(global.onresize){
fncStack.addItem(oldHandler(global.onresize));
}
global.onresize = fncStack;
}
}
function retFalse(){return false;}
function oldHandler(f){return (function(e){f(e);return true;});}
fncStack = getNewFILCFncStac(fnc);
if(global.addEventListener){
global.addEventListener("resize", fncStack, false);
globalCheck = retFalse;
}else if(global.attachEvent){
global.attachEvent("onresize", fncStack);
globalCheck = retFalse;
}
global.resizeMonitor = main
}

Which allows an indefinite number of call-back functions to be executed
when window.onresize events occur. I say indefinite but in reality the
main drawback of function stacks is that they can produce (internal)
stack overflow errors if they get too big, or the callback functions
attempt to call too many other functions themselves. In practice you
have to create a fairly big (500+ items) function stack before that is a
problem, and usually that is well beyond what any application requires.

Ok, it should be possible to associate several functions/methods from
different classes (i know JS doesn't really have classes) with the
same event of an (x)HTML-node. Calling the methods in a way that this
is pointing to the right object/instance. Further should it be done as
elegant as possible ;)

One of the questions in OO design is to determine the best demarcation
between the objects used. Wanting a facility for any
instance of any class (we can accept the OO terminology here as a
convenient shorthand for broadly analogous concepts) to attach a
listener, that may be a method of a specific object, to any event
handler on any DOM element, means that the classes using the
facility should not have any interest in the implementation of the
facility; it should be a simple common interface that is globally
available when needed.

In your original code you are trying to handle both callback functions
and calling methods on specific object instances. Overall I think these
two activities can (and so probably should) be separated. That is, I
would want the interface to the event handlers to be interested in
callback functions and have a separate mechanism that allowed the
callback functions to call methods on associated object instances. That
will involve wrapping the object instance and methods to call
information in a closure that returned a function that would then act as
the callback function.

I would also implement the event handler removal using function
references, which is going to hand a little more work back to the
classes using the facility to call their own instance method because
they will have to keep a reference to the callback function that is
wrapping the call to each of their methods if they want to use the
facility to remove event listeners by reference.

One of the main reasons that I would choose this separation is that it
would make it relatively easy for the event listener handling
implementation to employ the DOM Events - add/removeEventListener - as
its primary mechanism and only use the intrinsic event handlers as a
fall-back. Allowing the intrinsic event aspects of the code to be
dropped entirely in the future if DOM Events becomes universally
supported in browsers in use, without the need to modify any of the code
for the classes using the facility. For that reason I will also be using
the shorter event names, without the 'on' prefix, in discussing the
interfaces. However, I will not actually employ -
add/removeEnventListener - in any of the following code.

Using a Java style public constructor-less class with public static
methods forming the interface seems like a reasonable implementation. So
a global variable named - EventHandling - (or as you like it) will have
the methods - addListner - and - removeListener -, and each method will
expect the arguments - domElement -, - eventName - and - callBackFnc -.
eventName being the short form without the 'on' prefix.

(Incidentally, there is no way that your original code should have been
creating a DOM element when it was not passed one by reference. That
gave you HTMLElement object an extremely strange duel role.)

Next we need to decide on the contract for - callBackFnc -; It is going
to be an event handler (at least triggered by one) so the event object
is an obvious candidate as an argument. It is also likely to be
interested in which domElement it was called from so that should
additionally be an argument for - callBackFnc. In this case we have
a - removeListener - method for detaching callback functions so the
return value from the callback function can do a different job. But we
are dealing with intrinsic events so return values may cancel default
actions; We will attempt to employ the return values from -
callBackFnc - in this way, but we will also _specify_ that the -
EventHandling - system does not get involved in calling
event.preventDefault or event.stopPropagation, or in setting
event.cancelBubble or event.returnValue. Those operations are left the
responsibility of the callback function itself (or the instance methods
it calls). The callback functions are specified as _always_ returning a
boolean value, - true - if they are not attempting to cancel the default
action.

At this point it is possible to implement a function that will create
callback functions that calls a particular method on an object instance
to satisfy the needs of classes wishing to employ the - EventHandling -
facility. The instances of such classes will need to keep a reference to
the returned callback function object if they intend to use -
removerListener - at some later point. And the method called is expected
to provide the boolean return value.

function associateObjInstanceMethodWithCallBackFunction(obj methodName){
return (fucntion(e, domElement){
return obj[methodName](e, domElement);
});
}

The function returned from a call to this method satisfies the contract
for the callback function and will call a named method on the object
referred to by the - obj - parameter when it is executed, passing its
event and domElement arguments on to that method, and returning whatever
(boolean) value that method returns.

The return values from intrinsic event handlers can be problematic. For
an onclick event, for example, returning true or not returning anything
at all (undefined) allows the default action. Only returning boolean
false (or something that will type-convert to boolean false other than
undefined) will cancel the default action. This is also true of -
onsubmit - but not necessarily true for all events that may be used (I
can't think of an example right now). That may be a problem for a
general event handling system, but I don't intend addressing it here. My
strategy will be to create a default - true - return value and then AND
it with the return value form each callback function. The effect will be
that if any return - false - false will be returned, otherwise the
default - true - will be returned.

Another problem with a generalised method is that it is going to have to
attempt to handle the actions of other scripts and of HTML authors. Any
element may already have an event handler and that handler is unlikely
to satisfy the contract for our callback functions. This is one of the
reasons for preferring the DOM Events - add/removeEventListener -
approach as it allows additional listeners to be associated with the
events on elements without any concern for pre-existing handlers.

All we can hope to do about pre-existing handlers is wrap them in a
function that satisfies our callback function contract and calls the
original handler in a way that resembles how it would otherwise have
been called. That involves using Function.prototype.call or
Function.prototype.apply (preferably - call - in this context). However,
using intrinsic event implies a system that will work on older browsers,
but some older browsers do not implement - call - or - apply -. This
problem can be handled with a - call - or - apply - emulation function.
I will not be including one here but examples may be found by searching
the comp.lang.javascript archives at groups.google.com.

Another problem with pre-existing event handlers is going to be their
return values. ANDing their return value with the default will not be
sufficient as an undefined return value is possible (our callback
function contract requires a boolean return value always). It will be
necessary to check the value returned for a call to an original event
handler and ensure that it is not undefined before ANDing it with the
default, otherwise an undefined value will need to be converted to true.

Obviously another script might still overwrite a handler assigned with -
EventHandling - with one of its own, but that highlights the stupidity
of attempting to mix scripts from various sources on the same page
without understanding how they work.

The preceding text is probably sufficiently close to being a
specification that - EventHandling - can be implemented from it so here
goes:-

var EventHandling = (function(){
var global = this;
var onName = ['on',''];
function getNewFncStac(fnc){
function getStackFnc(f){
var next = null;
function t(e, domElement){
/* Swap the next two lines to have the stack execute
in reverse order (currently FILC):-
*/
(next&&next(e, domElement));
var ret = f(e, domElement);
fnc.defalutReturn = ret && fnc.defalutReturn;
}
t.addItem = function(d){
if(f != d){ // don't add duplicates
if(next){
next.addItem(d);
}else{
next = getStackFnc(d);
}
}
};
t.removeItem = fucntion(d){
if(f == d){
f = null;
return next;
}else if(next){
next = next.removeItem(d);
}
return this;
}
return t;
}
var base = getStackFnc(fnc);
fnc = function(e){
e = e||global.event;
fnc.defalutReturn = true;
(base&&base(e, this));
return fnc.defalutReturn;
};
fnc.addItem = function(d){
if(base){
base.addItem(d)
}else{
base = getStackFnc(d);
}
};
fnc.removeItem = function(d){
base = base&&base.removeItem(d);
};
fnc.defalutReturn = true;
return fnc;
}
function getPresevationWraper(original){
return (function(e, domElement){
var ret = original.call(domElement, e);
return ((typeof ret == 'undefined')||Boolean(ret));
});
}
return ({
addListner:function(domElement, eventName, callBackFnc){
var evName, hndlr;
if(domElement && eventName && callBackFnc){
onName[1] = eventName;
evName = onName.join('');
hndlr = domElement[evName];
if(hndlr){
if(hndlr.addItem){
hndlr.addItem(callBackFnc);
}else{ //not one of our handlers.
(domElement[evName] = getNewFncStac(
getPresevationWraper(hndlr)
)).addItem(callBackFnc);
}
}else{
domElement[evName] = getNewFncStac(callBackFnc);
}
}
},
removeListener:function(domElement, eventName, callBackFnc){
var evName, hndlr;
if(domElement && eventName && callBackFnc){
onName[1] = eventName;
evName = onName.join('');
hndlr = domElement[evName];
if(hndlr && hndlr.removeItem){
hndlr.removeItem(callBackFnc);
}
}
}
});
})();

/*
Called as:-

EventHandling.addListner(domElement, eventName, callBackFnc);

- and:-

EventHandling.removeListener(domElement, eventName, callBackFnc);

- and from object instances as (for example):-

this[methodName].callBack =
associateObjInstanceMethodWithCallBackFunction(this, methodName);

- with:-

EventHandling.addListner(
domElement,
eventName,
this[methodName].callBack
);

- and:-

EventHandling.removeListener(
domElement,
eventName,
this[methodName].callBack
);
this[methodName].callBack = null;

Remembering that the - eventName - is the shorter version without the
'on' prefix.

*/

Untested, but nothing stands out as wrong to me. If you have questions,
ask.

If the system was intended to be used exclusively, or mostly, to call
instance methods then it might make a lot as sense to have the -
associateObjInstanceMethodWithCallBackFunction - function on the -
EventHandling - interface. otherwise it may be regarded as a utility
that could be employed in other contexts where a callback function was
wanted that would call an instance method, but where contract of the
callback function was either satisfactory or unimportant.

Richard.
 
M

Marc Tanner

Great stuff, it is quite another style of programming and it may take
a moment to become familiar with it, but i think that i have
understood the main things. Otherwise i will ask again...
One of the questions in OO design is to determine the best demarcation
between the objects used. Wanting a facility for any
instance of any class (we can accept the OO terminology here as a
convenient shorthand for broadly analogous concepts) to attach a
listener, that may be a method of a specific object, to any event
handler on any DOM element, means that the classes using the
facility should not have any interest in the implementation of the
facility; it should be a simple common interface that is globally
available when needed.

In your original code you are trying to handle both callback functions
and calling methods on specific object instances. Overall I think these
two activities can (and so probably should) be separated. That is, I
would want the interface to the event handlers to be interested in
callback functions and have a separate mechanism that allowed the
callback functions to call methods on associated object instances. That
will involve wrapping the object instance and methods to call
information in a closure that returned a function that would then act as
the callback function.

The reason why i was trying to handle both things in the same method
is that i would like to keep it simple for the end user. The code here
is acting as a basic library, that means that there will (hopefully)
be a few people using it. So i tend to solve the problem with a
all-in-one solution, the perfect thing would be if the call could be
done with something like that:

[XHTMLElementInstance].addEventlistener("event",function_reference);
One of the main reasons that I would choose this separation is that it
would make it relatively easy for the event listener handling
implementation to employ the DOM Events - add/removeEventListener - as
its primary mechanism and only use the intrinsic event handlers as a
fall-back. Allowing the intrinsic event aspects of the code to be
dropped entirely in the future if DOM Events becomes universally
supported in browsers in use, without the need to modify any of the code
for the classes using the facility. For that reason I will also be using
the shorter event names, without the 'on' prefix, in discussing the
interfaces. However, I will not actually employ -
add/removeEnventListener - in any of the following code.

The application is just avalaible for DOM-compatible Browser so i
could use add/removeEventListener.
Using a Java style public constructor-less class with public static
methods forming the interface seems like a reasonable implementation. So
a global variable named - EventHandling - (or as you like it) will have
the methods - addListner - and - removeListener -, and each method will
expect the arguments - domElement -, - eventName - and - callBackFnc -.
eventName being the short form without the 'on' prefix.

(Incidentally, there is no way that your original code should have been
creating a DOM element when it was not passed one by reference. That
gave you HTMLElement object an extremely strange duel role.)

Well normally it does create DOM elements but i didn't want to include
that in my example. In fact we start with a blank page an then we
start to add DOM elements. For better understanding you may visit
http://free.pages.at/giftzwerg/webos0.1/webos.html

I know it's bad coding-style and there are many bugs, but until now I
didn't have the time to correct it and to wirte it better...

If the system was intended to be used exclusively, or mostly, to call
instance methods then it might make a lot as sense to have the -
associateObjInstanceMethodWithCallBackFunction - function on the -
EventHandling - interface.

Yes i think it makes sense...

Again thanks for spending your time, i will take a closer look on this
article later (at the moment i am quite busy).

Marc
 

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,744
Messages
2,569,483
Members
44,902
Latest member
Elena68X5

Latest Threads

Top