Javascript : Event handling models

In the previous post we created a simple calculator. We created a table with href and onclick event calls javascript fuctions. Although its correct but we had to write a lot of code for it, like writing onclick on every element and passing values. That was actually the inline model of DOM level 0. They are called that because they were created before any DOM standards were finalized and published.

DOM Level 0 Event Handlers

The traditional way of assigning event handlers in JavaScript is to assign a function to an event handler property. This event handling model was introduced by Netscape Navigator, and it still remains in all modern browsers because of its simplicity and cross-browser support. There are two model types: inline model and traditional model.

Inline model – In the inline model, event handlers are added as attributes of elements. Like in our calculator example :

<td><a href=”#” onclick=”return addDigit(1)”>1</a></td>

which calls a Javascript funtion:


 function addDigit(digit) {
 var resultField = document.getElementById("results");
 resultField.innerHTML += digit;
 return false;
 }

One common misconception with the inline model is the belief that it allows the registration of event handlers with custom arguments, like digit in addDigit function here. While it may seem like that is the case in the example above, what is really happening is that the JavaScript engine of the browser creates an anonymous functioncontaining the statements in the onclick attribute. The onclick handler of the element would be bound to the following anonymous function:


function() {
 addDigit(digit);

return false;
 };

Traditional model – In the traditional model, event handlers can be added/removed by scripts. Like the inline model, each event can only have one event handler registered. The event is added by assigning the handler name to the event property of the element object. To remove an event handler, simply set the property to null.

Lets remove all the onclick listner from all <a> from our calculator. Now we have to add events to all of them through javascript. There is a event called load that the browser fires whenever it completely loads the html document and we can hadle that event by specifying window.onload (window can be omitted). We can assign an annonyous function to it and register our event hadlers. Whenever onload firest this function executes. calculatorDomLvl0Traditional.html


<!DOCTYPE html>

<html>
<head>
<title>Simple Calculator</title>
<style type="text/css">
td {
 border: 1px solid gray;
 width: 50px;
 background-color: lightgreen;
}

#results {
 height: 20px;
}
</style>
</head>
<body>
 <table border="0" cellpadding="2" cellspacing="2">
 <tr>
 <td colspan="4" id="results"></td>
 </tr>
 <tr>
 <td><a href="#">1</a></td>
 <td><a href="#">2</a></td>
 <td><a href="#">3</a></td>
 <td><a href="#">+</a></td>
 </tr>
 <tr>
 <td><a href="#">4</a></td>
 <td><a href="#">5</a></td>
 <td><a href="#">6</a></td>
 <td><a href="#">-</a></td>
 </tr>
 <tr>
 <td><a href="#">7</a></td>
 <td><a href="#">8</a></td>
 <td><a href="#">9</a></td>
 <td><a href="#">*</a></td>
 </tr>
 <tr>
 <td><a href="#">Clear</a></td>
 <td><a href="#">0</a></td>
 <td><a href="#">=</a></td>
 <td><a href="#">/</a></td>
 </tr>
 </table>
 <script type="text/javascript">
 function addDigit(digit) {
 var resultField = document.getElementById("results");
 resultField.innerHTML += digit;
 return false;
 }

function calculate() {
 var resultField = document.getElementById("results");
 resultField.innerHTML = eval(resultField.innerHTML);
 return false;
 }

function reset() {
 var resultField = document.getElementById("results");
 resultField.innerHTML = "";
 return false;
 }

 function getHandlerFunction(innerHTML) {
 return function() {
 addDigit(innerHTML);

return false;
 };
 }

 onload = function() {
 var links = document.getElementsByTagName("a");
 var length = links.length;//we used a varialbe so as the for loops doesnt have to check size everytime

for (var i = 0; i < length; i++) {
 var link = links[i];
 var innerHTML = link.innerHTML;

switch(innerHTML) {
 case "Clear":
 link.onclick = reset;
 break;
 case "=":
 link.onclick = calculate;
 break;
 default:
 link.onclick = getHandlerFunction(innerHTML);
 break;
 }
 }

};
 </script>
</body>
</html>

When the onload occures the annonyous function excutes. Here we are getting all the elements by “a”. Now we have list. We iterate throught the list and assigns onclick events accordingly. Notice link.onclick = reset; and link.onclick = calculate; , we are not using reset() or calculate() because () means it will execute and assign the value (which in this case is false) to link.onclick. link.onclick as seen previouly should assign to a funtional only and not a result. Notice the default section. We cannot directly use addDigit(innerHTML), because  its going to executing the addDigit fucntion. Thats why we are writing getHandlerFunction(innerHTML) function that returns a function to handle this (just like what browser does when we write an inline model with parameters). Output:

calculatorDomLvl0Traditional

Before we go to DOM level 2, lets have a quick look on capture and bubbling. The basic problem is very simple. Suppose you have a element inside an element

element2InsideEmelemt1
and both have an onClick event handler. If the user clicks on element2 he causes a click event in both element1 and element2. But which event fires first? Which event handler should be executed first? What, in other words, is the event order?

Two models

Not surprisingly, back in the bad old days Netscape and Microsoft came to different conclusions.

  • Netscape said that the event on element1 takes place first. This is called event capturing. When you use event capturing, the event handler of element1 fires first, the event handler of element2 fires last.
  • Microsoft maintained that the event on element2 takes precedence. This is called event bubbling. When you use event bubbling, the event handler of element2 fires first, the event handler of element1 fires last.

The two event orders are radically opposed. Old Explorer only supports event bubbling. Mozilla, Opera 7 and Konqueror support both. Nightmare for web developers.

W3C very sensibly decided to take a middle position in this struggle. Any event taking place in the W3C event model is first captured until it reaches the target element and then bubbles up again.

w3cEventHadling

Web developers, can choose whether to register an event handler in the capturing or in the bubbling phase. This is done through the addEventListener() method explained later.

DOM Level 2 Event Handlers

There could be issues using DOM level 0 event hadlers. Nw we have our function which is mapped with onload. Suppose we decided some one else code, which also use window.onload evet hadler. One of the code will be overridden. Thankfully there is better way to handke these evet handlers.

DOM Level 2 Events define two methods to deal with the assignment and removal of event handlers: addEventListener() and removeEventListener(). These methods exist on all DOM nodes and accept three arguments: the event name to handle, the event handler function, and a Boolean value indicating whether to call the event handler during the capture phase (true) or during the bubble phase (false).

Microsoft initialy did not follow the W3C model. Up until Internet Explorer 8 and below, microsoft has it own model. Internet Explorer 9 follows both standard and the microsoft specific model. Internet Explorer 11 deletes its support for Microsoft-specific model.

Lets use the standard event model in our calculator example. Changing the javascript part:


function addDigit(digit) {
var resultField = document.getElementById("results");
resultField.innerHTML += digit;
return false;
}

function calculate() {
var resultField = document.getElementById("results");
resultField.innerHTML = eval(resultField.innerHTML);
return false;
}

function reset() {
var resultField = document.getElementById("results");
resultField.innerHTML = "";
return true;
}

function getHandlerFunction(innerHTML) {
return function() {
addDigit(innerHTML);

return false;
};
}

window.addEventListener('load', function() {
var links = document.getElementsByTagName("a");
var length = links.length;//we used a varialbe so as the for loops doesnt have to check size everytime

for (var i = 0; i < length; i++) {
var link = links[i];
var innerHTML = link.innerHTML;
var func=null;
switch(innerHTML) {
case "Clear":
func= reset;
break;
case "=":
func= calculate;
break;
default:
func= getHandlerFunction(innerHTML);
break;
}
link.addEventListener('click', func, false);
}

},false);

After the switch block we are now using addEventListner if the standard model, which listen to the event ‘click’ and attaches the fuction to it (setted inside the switch block). Also we have removed the onload call and now using the window.addEventListene instead which again listen to the click and now has this anonymous function attahed to it. false at the eved indicates that we are uing bubbling.

Output:

calculatorDomLvl2WithoutPreventDefault

Its working the same, but notice the # sign at the end which was not there when using traditional approach. This is because we are not cancelling the default action of these link. That is because we cannot simply returns false in our event hadlers. (returning false will work when using Level0 as previously done). Lets just get rid of all the return false statements. Also, lets revamp the code into a more object oriented way. Instead of individual function lets have them called as a method of a object Calculator. Previously we were looping through all <a> elements and assigning them click events individually, we are going to hadle the click event for the document. This will allow us to create one event handler as oppose to 16 event handlers which will speed up our performance. The event param will be taken care by the browser.


var calculator = {
resultField : document.getElementById("results"),
reset : function() {
this.resultField.innerHTML = "";
},

calculate : function() {
this.resultField.innerHTML = eval(this.resultField.innerHTML);
},

addDigit : function(digit) {
this.resultField.innerHTML += digit;
}
};

document.addEventListener('click', function(event) {
var target = event.target;
if (target.tagName.toUpperCase() === "A") {
var innerHTML = target.innerHTML;

switch (innerHTML) {
case "Clear":
calculator.reset();
break;
case "=":
calculator.calculate();
break;
default:
calculator.addDigit(innerHTML);
break;
}

event.preventDefault();
}
}, false);

Because of the preventdefault() call at the end , the default action of <a> is stopped. Some useful things to know :

  • To prevent an event from bubbling, developers must call the “stopPropagation()” method of the event object.
  • To prevent the default action of the event to be called, developers must call the “preventDefault” method of the event object.

These code wont work in IE 8 or below, because they did not followed the W3C specification for example instead of addEventListener and removeEventListener there were attachEvent and detachEvent. But since IE 9 they have started supporting standard event model.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: