Forum Moderators: open

Message Too Old, No Replies

addEventListener doesn't work

Seems like scope prob. but is mystifying

         

MarkFilipak

4:11 am on Mar 10, 2008 (gmt 0)

10+ Year Member



Suppose I have a global library named "myLib" that has the following body event handlers:

<body
onkeyup='myLib.onkeyupBody()'
onmouseover='myLib.onmouseoverBody()'
onresize='myLib.onresizeBody()'>

The above works, but if I remove these attributes and bind using addEventListener as follows:

myLib =
{ onkeyupBody: function() {...} // no calling args
, onmouseoverBody: function() {...} // no calling args
, onresizeBody: function() {...} // no calling args
, initialize: function() {
if (document.body.addEventListener)
{
document.body.addEventListener('keyup', this.onkeyupBody, false);
document.body.addEventListener('mouseover', this.onmouseoverBody, false);
document.body.addEventListener('resize', this.onresizeBody, false);
}
else
{
document.body.onscroll = this.onkeyupBody;
document.body.onmouseover = this.onmouseoverBody;
document.body.onresize = this.onresizeBody;
}
} // end of initialize
} // end of myLib

the events don't happen.
I've tried replacing "this" in initialize() with "myLib" (which is global) and when that didn't work, with "self.myLib" (which didn't work either of course).
If within initialize() I alert(this.onkeyupBody) for example, the function's there, but if within myLib.onkeyupBody() I put alert("HELLO") nothing happens -- the handler is not getting called.
Firebug shows no errors.

I have written tons of such handlers in the past, some with dynamic binding, but only when each handler is global, not part of a library. Am I just not seeing something -- what a surprise that would be <grin> -- or is my hacker education showing regarding the scoping of functions in a library.

Any and all help appreciated. Thanks.

fside

9:13 am on Mar 10, 2008 (gmt 0)

10+ Year Member



Try click. That will probably work, regardless. Something like keyup might require an input/text box, instead, to fire the event. Or make sure your style makes the element as wide as you think it is, and the style of its parent container, and so on. Then you should get the keydown, press, up. But it looks like you want it window-wide. You could have window.addEventListener( . . etc).

So you could do things to various elements onclick. But if you want keydown for the entire window, and scroll and resize will lend themselves to that, as well, you might want window-wide functions and handle whichever specific elements from the single handler.

As for globals, if your library/framework is all attached to myLib, then that's your only global, apart whatever few misc you might have.

MarkFilipak

7:20 pm on Mar 10, 2008 (gmt 0)

10+ Year Member



The issue is not click vs. mouseover (for example). The issue is binding to an event handler. But thanks for your input.

<body onmouseover='myLib.onmouseoverBody()'>

does the binding for me. But if within myLib.initialize() I do this:

document.body.addEventListener('mouseover', this.onmouseoverBody, false);

I'm doing my own binding. Somehow that binding is failing because myLib.onmouseoverBody never gets called.

Re, window.addEventListener(...): This is not standards compliant. Everything north of document is outside of the DOM.

httpwebwitch

12:24 am on Mar 11, 2008 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member



I get what you're trying to do, and I'm not sure why it wouldn't work... it does seem like a scope issue. Unless someone jumps in with an answer I'll try some experiments tomorrow morning + post here with what I find out.

My hunch is that "this" (as in "this.onmouseoverBody") is not the "this" that you'd expect "this" to be.

MarkFilipak I see you answering questions in here a lot, so when I saw you come in with a "help me!" request, I knew it would be a challenging one ;)

MarkFilipak

2:01 am on Mar 11, 2008 (gmt 0)

10+ Year Member



> I knew it would be a challenging one ;)
LOL!
When the binding is made (i.e., when addEventListener is invoked), this===myLib. As you would expect, myLib is an object the only purpose of which is to prevent all my library functions from cluttering the global object (sort of a private namespace) -- there are other, reverse-engineering-resistance reasons for doing this, but I won't go into those.

myLib = {fun1: function(){...}, fun2: function(){...}, fun3: function(){...}, ...}

Essentially, these are methods of the myLib object.

From global, a call is (for example): myLib.fun1();
From fun2 (for example), a call is: this.fun1();
And it's always OK to do this from anywhere: myLib.fun1();

This scheme has never failed me -- until now.

I've tried the following:
document.body.addEventListener('mouseover', this.onmouseoverBody, false);
document.body.addEventListener('mouseover', myLib.onmouseoverBody, false);
document.body.addEventListener('mouseover', self.myLib.onmouseoverBody, false);

None of them bind! -- the event does not call myLib.onmouseoverBody.

If you experiment you will find that, at least in Moz, the event object is floating in the cloud with no connection to anything except itself and it's properties -- event has no direct scope chain! (Exception: In IE, the event object is window.event).

Here's an example.

<body>
<div onclick="alert(event.currentTarget.parentNode.parentNode.parentNode)">Hello</div>
</body>

What you get when you click the div is "[object HTMLDocument]" -- this is the document object (as in document.body) -- in the example it is the div's great grandparent. The document object is the top of the DOM.

(Note: If you add another ".parentNode", the alert returns "null".)

Normally you can reach the global object from the document object because it is the root of the document object's scope chain, but in this case, you can't reach the global object because,
1, it's not a property of the document object or anything else that's in the scope chain (in the way that parentNode, previousSibling, and nextSibling are references that facilitate browsing of the DOM via javascript), and
2, the event object does not, itself, scope to the global object.

So, for some arbitrary event.currentTarget, you can browse upwards to the document object and no further.

But if that were so, you wouldn't be able to reach any global function (handler) either and we all know that's not true. In other words, if I make onmouseoverBody a global function instead of a method of myLib, the listener will bind and work.

So, I remain mystified.

(To all who are following this.... HELP! I'VE FALLEN AND I CAN'T GET UP!)

fside

4:59 am on Mar 11, 2008 (gmt 0)

10+ Year Member



As I said, just know at what level the events are being fired, and if it's a case of being over an element, at all, that the element is there, or out that far. Maybe put a colored background and just visually see how wide and tall it is.

You want a resize handler. So just use the old DOM, onresize = myLib.resizeHandler. It'll work fine. Or if you like, for some reason, window.addEventListener(resize).

The keyup works fine in a textbox, this way. And if you want window-wide, then window.addEventListener(keydown, press, up, what have you).

But it does work. If you want window events, I'd just use the old style, one main handler for an event type, essentially. But it works for elements, too, if you're over the element. For onclick, as an example, your function is being assigned as a handler for that element, for that event. See here:

<ul id="comtainer" style="width:50%;background:#ffa;">
<li id="sprite-linux">a</li>
<li id="sprite-window">b</li>
<li id="sprite-mac">c</li>
</ul>
<input type="text" id="iput">

<script>
var myLib = {
onkeyupBody: function() { alert('keyup') },
onclickBody: function() { alert('click') },
onresizeBody: function() { alert('resize') }
}

document.getElementById("iput").addEventListener('keypress', myLib.onkeyupBody, false);
document.getElementById("comtainer").addEventListener('click', myLib.onclickBody, false);
//window.addEventListener('resize', myLib.onresizeBody, false);
onresize = myLib.onresizeBody;
</script>

httpwebwitch

3:48 pm on Mar 11, 2008 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member



I copied your code into the <head> of a new page and played around a little; the major problem I was finding was that "body has no properties". Turns out that document.body is null. Even when I put an ID on the <body> element and alert(document.getElementById('body')) within the initialize() method, it returns null. Not what I expected!

So I replaced "document.body" with "document"

This script works:


<script>
var myLib = {
onkeyupBody: function() {
alert('onkeyupBody');
},
onmouseoverBody: function() {
alert('onmouseoverBody');
},
onresizeBody: function() {
alert('onresizeBody');
},
initialize: function() {
document.addEventListener('keyup', this.onkeyupBody, false);
document.addEventListener('mouseover', this.onmouseoverBody, false);
document.addEventListener('resize', this.onresizeBody, false);
}
}
myLib.initialize();
</script>

... works! But - the events are attached to the document, not the body as you intended.

Even bringing the document into scope as an object of myLib doesn't help finding document.body. Note the new definition "doc" and the alerts inside initialize():


<script>
var myLib = {
doc:document,
onkeyupBody: function() {
alert('onkeyupBody');
},
onmouseoverBody: function() {
alert('onmouseoverBody');
},
onresizeBody: function() {
alert('onresizeBody');
},
initialize: function() {
alert(this.doc);
alert(this.doc.getElementById('body'));
this.doc.addEventListener('keyup', this.onkeyupBody, false);
this.doc.addEventListener('mouseover', this.onmouseoverBody, false);
this.doc.addEventListener('resize', this.onresizeBody, false);
}
}
myLib.initialize();
</script>

Those alerts return "[object HTMLDocument]" and "null" respectively.

I tried putting addEventListener() on other elements like named <div> and <p> content, with similar results.

So I tried this exposure loop within the initialize function:


for(var s in document){
document.write(s + '<br/>');
}

And hark! one of the properties of document is body. It exists, so why doesn't it have any properties or methods?

THEN IN A FOREHEAD-SLAPPING EUREKA MOMENT, I moved my entire <script> to the END of the document, injected right before </body>. Ran it... and it worked perfectly.

It wasn't a scope problem at all. It was simply that when the <head> is parsed and "myLib.initialize();" is executed, the <body> doesn't exist yet. Thus document.body, though the object name exists as a predefined child of document, returns null. The trick is to wait until the DOM is fully loaded before initializing myLib.

You can do this easily by wrapping everything as a function triggered by the window.onload event. I've done that in the example below, though in practice I usually employ the Mootools domready event [docs.mootools.net] instead.

Mootools domready works like this:


window.addEvent('domready', function(){
alert('the dom is ready');
});

The final working code is:


<script>
window.onload = function(){
var myLib = {
onkeyupBody: function() {
alert('onkeyupBody');
},
onmouseoverBody: function() {
alert('onmouseoverBody');
},
onresizeBody: function() {
alert('onresizeBody');
},
initialize: function() {
document.body.addEventListener('keyup', this.onkeyupBody, false);
document.body.addEventListener('mouseover', this.onmouseoverBody, false);
document.body.addEventListener('resize', this.onresizeBody, false);
}
}
myLib.initialize();
}
</script>

~ Cheers

MarkFilipak

8:31 pm on Mar 11, 2008 (gmt 0)

10+ Year Member



> THEN IN A FOREHEAD-SLAPPING EUREKA MOMENT
Hi! My name is Mark and I have a confession. I still have the problem and it's a real problem, but it's not quite like I portrayed. In my eagerness to simplify my problem statement I went a little too far and left out body onload. My actual code is:
<body
onload='myLib.initialize();'
onkeyup='myLib.onkeyupBody();'
onmouseover='myLib.onmouseoverBody();'
onresize='myLib.onresizeBody();'
onunload='myLib = null; if (this.CollectGarbage) CollectGarbage();'>

From the code above you can see that, contrary to what I originally posted, I am calling the initialization function when body onload fires. Though it has nothing to do with my problem, I apologize for stripping that out and misleading you as a result.

Dear httpwebwitch, you presented 3 code snippets. The first binds the listeners to document. The second binds them to a reference to document (i.e., "this.doc"). And the third binds them to document.body, however, the library is local to window.onload. I'd like to comment on each offering.

I like your first offering. I've never bound to document before. It never occurred to me. I've traditionally bound to document.body. However, as it is not a religious issue, I will try binding to document and let you know the result. Thanks for the suggestion. (Note that binding to document may bypass my problem but it doesn't solve the mystery regarding why document.body can't be scoped.)

Regarding your second offering. It shows imagination but I think it might lead to a sizable memory leak.

var myLib = {
doc: document, ...

This creates an object property of myLib named 'doc' and then assigns a document-reference to it. In later lines, the event listeners are bound to that reference. By binding to a reference, when the page unloads (or reloads) the runtime system may not be able to unbind it and free the memory. If there is a leak, the memory involved might be the entire document store or, if the reference is freed, it might be just the memory allocated to the listener functions.

Regarding your third offering, the entire myLib library will be local to window.onload(). This might be a bad idea, or it might be brilliant. I'm not sure. The call to myLib.initialize() is inside the window.onload() function, so the listeners will get bound. After window.onload() finishes, the myLib object will no longer exist. I don't think this would break the binding but the listeners, if they survive at all, can only survive as anonymous functions. As anonymous functions, the 'this' value inside those functions is... well, I don't know what it would be -- document.body I suppose but I'm not sure. I'm going to do some experiments with this offering and see what happens.

httpwebwitch, you have quite an imagination. It's been a delight to receive your input and I'm going to do some experiments and get back to you.

httpwebwitch

9:04 pm on Mar 11, 2008 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member



Since discovering that most of my problems were caused by poorly handled asynchronicity (both in life and in code), I've been tossing everything into a domready event. And I've never had so much free time! All those hours I used to spend debugging scripts, I can now spend reading, swimming, taking long walks on beaches, drinking tequila, and enjoying life.

I highly recommend it. You will become a happier person, if that's even possible.

window.addEvent('domready', function(){
// put everything you do in here, even if it's encapsulated in an object library
});

:)

fside

11:13 pm on Mar 12, 2008 (gmt 0)

10+ Year Member



> everything into a domready event. <

I think Filipak wanted to phrase it as a 'binding' problem, when I don't think it was even a matter of undefined objects (suggesting even in the OP that his script was in the BODY). I got the initial sense that it concerned which events fire for which elements and where. For example, I'm still surprised that you activate a handler by attaching resize to the BODY element, rather than, window.

Wouldn't this work, instead? :

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<body>

<ul id="comtainer" style="width:40%;background:#ffa;">
<li id="sprite-linux">a</li>
<li id="sprite-window">b</li>
<li id="sprite-mac">c</li>
</ul>

<input type="text" id="iput">

<script>
window.onload = function(){
var myLib = {
onkeyupBody: function() { window.status = 'onkeyupBody'; },
onmouseoverBody: function() { window.status = 'onmouseoverBody'; },
onresizeBody: function() { window.status = 'onresizeBody'; }
}

document.documentElement.addEventListener('keyup', myLib.onkeyupBody, false);
document.body.addEventListener('mouseover', myLib.onmouseoverBody, false);
document.body.addEventListener('mouseout', function(){window.status=""}, false);
window.addEventListener('resize', myLib.onresizeBody, false);
}
</script>

</body>
</html>

I don't understand, either, what you mean about treating some 'domready' as a catch-all for all event handlers. Is that what you meant? If so, how would it work? Isn't the advantage of addEventListener that in tying events to elements one can easily know which element fired the event? If there are problems with, this, and IE's attachEvent, one can speak of the workarounds, instead, as mentioned elsewhere.

httpwebwitch

3:35 am on Mar 13, 2008 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member



I don't understand, either, what you mean about treating some 'domready' as a catch-all

I mean I don't execute any procedural script until the DOM is fully ready. Thus I never have that problem of my script performing manipulations on obejcts or elements that don't yet exist. It accomplishes the same as, but works better than, <body onload="do_something()">.

I don't mean that every event in the script is happening ondomready. I do mean that those events don't get defined until domready. To show how various objects and events can all be defined on an event, I'll describe - for the first time ever in code - a typical day in my life.


httpwebwitch.addEvent('getoutofbed', function(){
brushteeth();
getdressed();
$('car').onenter = function(){
driveto(office);
}
$('office').onarrival = function(){
setInterval(drinkcoffee,50000);
}
$('office').ondeparture = function(){
driveto(home);
}
while(conscious(httpwebwitch)){
alcohol++;
}
});

See, the drivehome() event isn't synchronous with getoutofbed() at all. But I can't drive home until I've gotten out of bed, and it makes no sense to define the office.ondeparture event that triggers a drive home if the getoutofbed doesn't happen.

It's a perfect analogy for how I use ondomready. It really is.

the entire myLib library will be local to window.onload().

Sort of. If you want to think of it that way. I prefer "the entire myLib library will be protected from sequential execution, like a fetus in its loving womb is protected from having to work in a cubicle farm"

Wouldn't this work, instead?

Why ask? Open it in a browser, and you'll be able to tell us decisively that it does work, or doesn't, instead of asking if it would. If it works, then it's perfect because there are a kajillion ways to make things happen in a browser and all the techniques that work are correct. Even ones where variable names are misspelled.

Yet because of my own habits, I would criticize its elegance; I don't like putting <script> down at the end of the <body>. I'd always rather keep my script in an external *.js file referenced in the <head>. That's a personal preference. For the sake of example I kept the script inline, but I do keep it in the <head>

fside

3:55 am on Mar 13, 2008 (gmt 0)

10+ Year Member



> I don't like putting <script> down at the end of the <body> <

But a) that's how you have to do it, here. I think it's just understood that the script is external and if parts are not required for certain reasons in the HEAD, the script is brought in just before the closing BODY tag. And b) these are fairly trivial and artificial examples that can just be cut and pasted in this way and "open it in a browser", as you say. It's just a convenience. That's all. One copy, one paste, however many browsers. Don't mistake it as a recommendation for how a page should look on a website.

What you describe of event handlers isn't merely list order, sorting, etc, but that it's best to place the script at the end of the BODY to allow elements to be defined. As for ordering, this was something else added to the rework I mentioned from Edwards and Zidjel. One model might go in order, another reverse order, and the W3C spec might not call for any order. But if it needs to be sorted, it can be done so with their substituted method, again for IE. If by 'domready', though, you mean that handlers are assigned by the order of the elements in the document hierarchy, or the order the handlers are encountered in code, that seems a safe assumption. Maybe I'm wrong about that. But it does seem the reasonable assumption that browsers and devices and whatever would work top-down in that way. So you could count on that sorting, that ordering.

> the entire myLib library will be local to window.onload(). <

This was actually said by someone else. And I'll let them go through it, and explain it. I wouldn't have phrased it that way, either.

MarkFilipak

8:36 am on Mar 13, 2008 (gmt 0)

10+ Year Member



Hi folks,

I've made some progress but time's tight and I'm too busy. So far, I have a scoping AND an execution context (i.e., 'this') problem.

httpwebwitch, though I like your spirit (alcohol++ indeed), my problem has nothing to do with domready (nor even with haslayout, ugh!).

onmouseoverBody always works in both IE6 and FF2.
onresizeBody always works in IE6 but in FF2, if the handler is not loaded by the body-element's onresize attribute, it appears not to bind and will not even execute alert("Hello World").

The scoping problem you've already explored, httpwebwitch. I can avoid it easily, but then comes the context problem. Here it is:

When myLib.onresizeBody is bound, I think that all is OK -- no way to test this but no reason to believe otherwise, either -- but it doesn't APPEAR to bind. When myLib.onresizeBody runs, 'this' is the event object, not the myLib object. Inside myLib.onresizeBody I call other methods of myLib -- that's what a library is all about, isn't it?

Well, those other methods are referred to as this.othermethod. That works when you call myLib.onresizeBody from the global context or from any-method within the myLib context, however, in the event handler context, 'this' is the event object. So the call to this.othermethod breaks BUT IT DOESN'T THROW AN EXCEPTION.

So I've gone from a situation in which my handler is just silent, so I think it's a scoping problem, to a situation in which I (intellectually) see that my handler is broken because of a context problem, but there's no exception in a condition that should throw an exception, which seems to indicate an additional, scope problem. In this logic, bear in mind that I can't even alert("Hello World").

Back to more testin' and drinkin'. Ain't compooter programmin' fun!

httpwebwitch

2:41 pm on Mar 13, 2008 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member



wow this really is a tricky one.
but I think you're close to a solution... or figuring out that there isn't a solution!

If you can boil the problem down to its simplest implementation (like, a simple "hello world" with one object, one handler, one element, etc) and show that it can or can't be solved, it will make an excellent advanced JS article. Please share what you find out - and holler if you get stuck, K?