Forum Moderators: open
<body
onkeyup='myLib.onkeyupBody()'
onmouseover='myLib.onmouseoverBody()'
onresize='myLib.onresizeBody()'>
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
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.
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.
<body onmouseover='myLib.onmouseoverBody()'>
document.body.addEventListener('mouseover', this.onmouseoverBody, false);
Re, window.addEventListener(...): This is not standards compliant. Everything north of document is outside of the DOM.
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 ;)
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>
(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!)
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>
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>
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
<body
onload='myLib.initialize();'
onkeyup='myLib.onkeyupBody();'
onmouseover='myLib.onmouseoverBody();'
onresize='myLib.onresizeBody();'
onunload='myLib = null; if (this.CollectGarbage) CollectGarbage();'>
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, ...
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.
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
});
:)
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.
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?
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>
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.
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!
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?