views:

47

answers:

3

oK, so I have a couple, what may end up being for this forum overly-novice questions regarding unobtrusive event handling.

As I understand it, a properly set-up document would look something like this:

<html>
<head>
<title>Title</title>
<script src="jsfile.js" type="text/javascript></script>
</head>
<body>

//Body content, like some form elements in my case

</body>
</html>

Jsfile.js would look something like this:

function a() {
//code;
}
function b()...

window.addEventListener('load', a, false);
document.getElementById("id").addEventListener('click', b, false);
document.myForm.typeSel.addEventListener('change', c, false);

//or to use better browser-compatible code...

function addEvent(obj,evt,fn) {
 if (obj.addEventListener)
   obj.addEventListener(evt,fn,false);
 else if (obj.attachEvent)
   obj.attachEvent('on'+evt,fn);
}
addEvent(window, 'load', a);
addEvent(document.getElementById('id'), 'click', b);
addEvent(document.myForm.typeSel, 'change', c);

As I understand it, while in the head the browser will load this javascript code, adding each of those event handlers to their respective elements. HOWEVER... While the window handler is added properly, none of the others are. But if placed within a function, the (for instance) getElementById method of accessing an element works just fine, and the event handler is added. So I could conceivably make a loadEvents() function which is called via window onload, which contains all of the addEvent() functions for the other document elements for which I need event handlers. But as I understand the whole thing, I shouldn't have to do this.

In addition, if I were to stick the addEvent code within the body along with the element it addresses, such as...

<input type="checkbox" id="test" />
<script type="text/javascript>
document.getElementById("test").onclick = func;
</script>

...then it works fine. But of course it also violates the whole reason for removing inline event handlers!

So question being: In order to use "element.addEventListener('click',func,false)", "addEvent(element,'click',func)", or even "element.onclick = func" - how can I successfully reference an element at the end of a script file in the head, without having to stick it in another function? Why does getElementById and other such methods not work outside of a function in the head?

Or, is there some flaw in my underlying understanding?

+1  A: 

Putting <script> in the <head> used to be the wisdom. But nowadays, with heavy ajax pages, <script> is more and more often but in the body, as far down below as possible. The idea is that the loading and parsing of the <script> keeps the rest of the page from loading, so the user will be looking at a blank page. By making sure the body is loaded as fast as possible, you give the user something to look at. See YAHOO best practices for a great explanation on that issue: http://developer.yahoo.com/performance/rules.html

Now, regardless of that issue, the code as you set it up now, can't work - at least, not when the elements you attempt to attach the handlers to aren't created yet. For example, in this line:

document.getElementById("id").addEventListener('click', b, false);

you will get a runtime error if the element with id="id" is inside the body. Now, if you put the <script> in the body, way below, after the content (including the lement with id="id", it will just work, since the script is executed after the html code for those elements is parsed and added to the DOM.

If you do want to have the script in the head, then you can do so, but you'll need to synchronize the adding of the event handlers with the rendering of the page content. You could do this by adding them all inside the document or window load handler. So, if you'd write:

//cross browser add event handler 
function addEventHandler(obj,evt,fn) {
    if (obj.addEventListener) {
        obj.addEventListener(evt,fn,false);
    } else if (obj.attachEvent) {
        obj.attachEvent('on'+evt,fn);
    }
}
addEventHandler(document, 'load', function(){
    //set up all handlers after loading the document
    addEventHandler(document.getElementById('id'), 'click', b);
    addEventHandler(document.myForm.typeSel, 'change', c);
});

it does work.

Roland Bouman
Thanks much. I kind of kick myself for not thinking of that. I had even gone so far as to put an alert inside my addEvent() function, to find out what object had been sent through the argument, and discovered that it was "undefined". I should have figured it out logically. Oh well.Also, thanks for the link to the Yahoo page - I'm always looking for ways to make sure I'm coding to the highest standards, such as updating to unobtrusive javascript... the whole reason I was having this trouble in the first place!
David
No need to kick yourself :) For me, programming is always kind of a mixture between logical thinking and trial and error. I think it's like that for most people, otherwise we wouldn't have so many software testing frameworks. As for coding to the highest standards...I dunno, it is certainly good to be aware of what you're doing, and why you're doing it. But what is considered a high standard from one POV may be frowned upon by another.
Roland Bouman
A: 

hi, are you familiar with jQuery?
its a javascript library featuring some really awesome tools.
for instance if you want to have some js action done just after your page if fully loaded and all DOM elements are created (to avoid those annoying exceptions) you can simply use the ready() method.
also i see you want to attach click \ change events jQuery takes care of this too :) and you don't have to worry about all those cross-browser issues.
take a look at jQuery selectors to make your life easier when attempting to fetch an element.

well thats it, just give it a shot, its has a very intuitive API and a good documentation.

kfiroo
Thanks for the tip, I'll be sure to check it out.
David
A: 

The reason why window.addEventListener works while document.getEle...().addEventListener does not is simple: window object exists when you're executing that code while element with id="abc" is still not loaded.

When your browser downloads page's sources the source code is parsed and executed as soon as possible. So if you place script in head element - on the very beginning of the source - it's executed before some <div id="abc">...</div> is even downloaded. So I think now you know why

<div id="test">Blah</div>
<script type="text/javascript">document.getElementById("test").style.color = "red";</script>

works, while this:

<script type="text/javascript">document.getElementById("test").style.color = "red";</script>
<div id="test">Blah</div>

doesn't.

You can handle that problem in many ways. The most popular are:

  • putting scripts at the end of document (right before </body>)
  • using events to delay execution of scripts

The first way should be clear right now, but personally I prefer last one (even if it's a little bit worse).

So how to deal with events? When browser finally download and parse whole source the DOMContentLoaded event is executed. This event means that the source is ready, and you can manipulate DOM using JavaScript.

window.addEventListener("DOMContentLoaded", function() {
    //here you can safely use document.getElementById("...") etc.
}, false);

Unfortunately not every browser support DOMContentLoaded event, but as always... Google is the anwser. But it's not the end of bad news. As you noticed addEventListener isn't well supported by IE. Well... this browser really makes life difficult and you'll have to hack one more thing... Yes... once again - Google. But it's IE so it's not all. Normal browsers (like Opera or Firefox) supports W3C Event Model while IE supports its own - so once again - Google for cross-browser solution.

addEventListener might seems now the worst way to attach events but in fact it's the best one. It let you easly add or remove many listeners for single event on single element.

PS. I noticed that you consider of using Load event to execute your scripts. Don't do that. Load event is execute too late. You have to wait till every image or file is loaded. You should use DOMContentLoaded event. ;)

EDIT: I've forgotten... dealing with cross-browser event model is much easier when you're using some framework like very popular jQuery. But it's good to know how the browsers work.

Crozin

related questions