DOM Level 2 Event Model


One of the drawbacks of Basic Event Model is, you can register only one handler. If you want to register multiple event handlers, it doesn't work to do the following:
window.onload = function() {
    // handler1
};
window.onload = function() {
    // handler2
};

In the above example, window.onload will refer to the second function and the first one will be discarded. When an event happens, calling two or more functions in the Basic Event Model can only be done indirectly. The easiest way is ...
function handler1() {
}
function handler2() {
}
window.onload = function() {
    handler1();
    handler2();
};

But, ways such as this are usually hard to manage. DOM Level 2 standardizes the event model so it's called the Standard Event Model. DOM Level 2 Event Model allows us to register two or more event handlers. Internet Explorer, however, doesn't support DOM Level 2, so the following examples only work in standard-compliant browsers. (After Internet Explorer 9, DOM Level 2 Event Model finally gets better support.)

DOM Level 2 Event Model uses the addEventListener function to add multiple event handlers. For instance, the first example in Basic Event Model can be re-implemented as follows:
<html>
    <head>
        <meta content="text/html; charset=UTF-8" http-equiv="content-type">
        <script type="text/javascript">
            window.addEventListener('load', function() {
                function handler() {
                    document.getElementById('console').innerHTML 
                         = 'Who\'s clicked: ' + this.id;
                }
                document.getElementById('btn1')
                    .addEventListener('click', handler, false);
                document.getElementById('btn2')
                    .addEventListener('click', handler, false);
            }, false);
        </script>
    </head>
    <body>
        <button id="btn1">Button 1</button><br>
        <button id="btn2">Button 2</button><br>
        <div id="console"></div>
    </body>
</html>

The first parameter of addEventListener accepts the event name without an 'on' prefix. The second parameter accepts an event handler. If the third one is false, the specified event handler will be used in the bubbling phase, which will be explained soon.

In the above example, I use this to retrieve the element which triggers the event currently. Though most DOM Level 2 compliant browsers support this way, it's not in the standard of that. The specification uses the currentTarget property of an Event instance to return the element which triggers the event currently.

In DOM Level 2 Event Model, the first parameter of an event handler accepts an Event instance. Some events have a default action. Calling the preventDefault function of the Event instance can prevent the default action. For Example, we can use the function to rewrite the third sample in Basic Event Model:
<html>
    <head>
        <meta content="text/html; charset=UTF-8" http-equiv="content-type">
        <script type="text/javascript">
            window.addEventListener('load', function() {
                document.form1.addEventListener('submit', function(event) {
                    if(event.currentTarget.data.value.length === 0) {
                        event.preventDefault();
                    }
                }, false);
            }, false);
        </script>
    </head>
    <body>
        <form name="form1" action="fake.do">
            Input data: <input name="data"><br>
            <button type="submit">Submit</button>
        </form>
    </body>
</html>

There're two phases in DOM Level 2 Event Model. When an event happens, it'll propagate from document to the target element where you are operating on. This is called "Capturing Phase". Then, the event will propagate from the target element to document. This is called "Bubbling Phase". If the third parameter of the addEventListener function is true, the event handler is a capturing event handler. Otherwise, it's a bubbling event handler.

For example, we can rewrite the first sample in Event; set up handlers to observe two phase events.
<html>
    <head>
        <meta content="text/html; charset=UTF-8" http-equiv="content-type">
        <script type="text/javascript">
            window.addEventListener('load', function() {
                function handler(event) {
                    document.getElementById('console').innerHTML += 
                      '<br><b>currentTarget.id:</b> '
                          + event.currentTarget.id + 
                      ', <b>target.id:</b> ' + event.target.id;
                }
                document.getElementById('bodyId')
                    .addEventListener('click', handler, true);
                document.getElementById('bodyId')
                    .addEventListener('click', handler, false);
                document.getElementById('divId')
                    .addEventListener('click', handler, true);
                document.getElementById('divId')
                    .addEventListener('click', handler, false);
                document.getElementById('btnId')
                    .addEventListener('click', handler, true);
                document.getElementById('btnId')
                    .addEventListener('click', handler, false);
            }, false);
        </script>
    </head>
    <body id="bodyId">
        <div id="divId"><button id="btnId">Click me</button></div>
        <span id="console"></span>
    </body>
</html>

The target element can be got from the target property of the Event instance. Clicking the button will cause the following result. It can be observed that the event propagate from outside to inside, and then from inside to outside.
currentTarget.id: bodyId, target.id: btnId
currentTarget.id: divId, target.id: btnId
currentTarget.id: btnId, target.id: btnId
currentTarget.id: btnId, target.id: btnId
currentTarget.id: divId, target.id: btnId
currentTarget.id: bodyId, target.id: btnId

The stopPropagation function of an Event instance can stop the event propagation. For example, if the above sample only registers event bubbling handlers, stopping the event propagation can be done as follows:
<html>
    <head>
        <meta content="text/html; charset=UTF-8" http-equiv="content-type">
        <script type="text/javascript">
            window.addEventListener('load', function() {
                function handler(event) {
                    document.getElementById('console').innerHTML += 
                        '<br><b>currentTarget.id:</b> ' 
                            + event.currentTarget.id + 
                        ', <b>target.id:</b> ' + event.target.id;
                    event.stopPropagation();
                }

                document.getElementById('bodyId')
                    .addEventListener('click', handler, false);
                document.getElementById('divId')
                    .addEventListener('click', handler, false);
                document.getElementById('btnId')
                    .addEventListener('click', handler, false);
            }, false);
        </script>
    </head>
    <body id="bodyId">
        <div id="divId"><button id="btnId">Click me</button></div>
        <span id="console"></span>
    </body>
</html>

The removeEventListener function can remove an event handler. The first parameter accepts an event type; the second one accepts the event handler; the third one point out the handler is for the capturing phase (true) or bubbling phase (false).