Object Oriented Programming and Event Handlers

When I started writing Object Oriented Javascript, there was one major pitfall: Event handlers. Event handlers are functions that are launched when a certain event occurs inside the browser, say a click on an image.

Most people are familar with the onclick, onload (…) attributes in HTML and a few selected ones even know how to work with addEventListener and attachEvent (see below), but no matter what system you use, there’s one thing that never works and that’s using event handlers in an object oriented application.

Why? Well, for some reason (I wish I knew which one), both the W3C, which is the consortium for web standards and Microsoft (which is a company against standards, but they usually behave the same way) defined event listeners to take exactly one parameter, and that’s the function which is supposed to be called when an event occurs.

Time for sample code. Imagine you have three buttons and want each one to display its number when you click on it. And imagine you don’t want to do this manually, because there could be as many as 1000 buttons. Here’s the HTML part:

<html>
 <head>
  <title>HelloButton</title>
  <script></script>
 </head>
 <body>
 </body>
</html>

OK, now let’s define our “Button” class. A class is nothing more than a blueprint which you can lateron use to create objects that are all constructed according to this blueprint… In Javascript they are really easy to make, you just create a constructor function and that’s pretty much it. The only differences to a normal function are that:

a) A constructor can’t return anything, because it always returns the object it created.

b) You can use “this” to set properties of that object (object, not class: each object has its own properties, for example two objects of the class “person” might have a property “name”. For person1, this could be “Hans”, for person2 “Peter”).

So, to define a button class with a property “buttonTitle” and a HTML element to display the button all we have to do is this:

function button(title){
 /* Create an input element and save a reference in the element property */
 this.element=document.createElement("input");
 /* Set the type of the HTML element to "button" */
 this.element.setAttribute("type","button");
 /* Set the label of the HTML element to title */
 this.element.setAttribute("value", title);
 /* Append that element to the HTML body */
 document.getElementsByTagName("body")[0].appendChild(this.element);
 /* Set the title property to the supplied title */
 this.title=title;
}

Nice, huh? OK, one more thing before we actually get to the event handlers. To define a function that can also use “this” (that’s called a method) we have to use a special notation…

classname.prototype.methodname=function(parameters){code}

Looks strange, I know. But it never really changes, so all you’ve got to do is copy paste. Let’s say we want to add a “showTitle” method that uses alert() to display this.title:

button.prototype.showTitle=function(){
 alert(this.title);
}

Not that hard, is it? OK, now we’ve got everything working, but we still have to actually create the objects using a simple “for” loop (we do this inside a “main” function which we specify for body.onload, because otherwise the code which tries to append something to the body might run before the body is actually there) , so now our whole document looks like this:

<html>
 <head>
  <title>HelloButton</title>
  <script>
function button(title){
 this.element=document.createElement("input");
 this.element.setAttribute("type","button");
 this.element.setAttribute("value", title);
 document.getElementsByTagName("body")[0].appendChild(this.element);
 this.title=title;
}

button.prototype.showTitle=function(){
 alert(this.title);
}

function main(){
 var buttonObjects=new Array();
  for(var i=0;i<3;i++){
   buttonObjects[i]=new button("Button #"+i);
  }
}
  </script>
 </head>
 <body onload="main()">
 </body>
</html>

So now we have the button class, the objects and the HTML buttons: Hurray! But nothing happens when we click them, because we haven’t defined any behaviour yet.

So let’s extend our constructor function (“function button(title)…”) a little. You can look at the previous post if you want to use proper eventListeners, but right now we’re going to just use the onclick attribute.

The seemingly logical thing would be to just add a line that sets the onclick attribute to this.showTitle, right?

this.element.onclick=this.showTitle;

Sadly, this doesn’t work… we don’t get this.title! Why? well, when an event handler gets executed, it gets executed in such a way that “this” means the element that sent the event, in the case the HTML “input” element, not our object.

So what do we do? Well, there’s a nasty little method that every function in Javascript has: this is the “call” method and it executes a function so that “this” means the first parameter. So what we want to do would be something like:

this.element.onclick=this.showTitle.call(this);

Looks nice I know, but sadly it still doesn’t work, because that line actually evaluates this.showTitle.call(this) and then sets this.element.onclick to the value it returned.

But if we wrap this inside yet another function, we’re at least getting closer: Now onclick is at least set to a function, eventhough it still doesn’t work, because “this” is still the HTML element.

this.element.onclick=function(){this.showTitle.call(this)};

The trick is now to actually create a function that we can somehow pass “this” to and which will then return a new function which has “this” hardcoded to the right value. The thing we need here is called Javascript closures and you can easily find documents that explain in 100 or more pages what it is and how it works… but we really only need to know how to use it.

First we create a new function outside any object. Let’s call it methodize. This function should take as a parameters a given function and a reference to “this”, we call this “scope”:

function methodize(methodize_func, methodize_scope){
}

As we said before methodize should return a function:

function methodize(methodize_func,methodize_scope){
    return (function(){});
}

And what should that function contain? Exactly. The nasty “this.showTitle.call(this)”, but because we designed this for any function, not just “this.showTitle” we use “methodize_func” instead and “methodize_scope” instead of “this”.

function methodize(methodize_func,methodize_scope){
    return (function(){methodize_func.call(methodize_scope);});
}

Now, when we run methodize we get a function that when executed calls the given method in the given scope. We can now asign that to onclick

this.element.onclick=methodize(this.showTitle,this);

And that’s it! Now we get the eventListener executed in the correct scope! I know it seems hard and unneccessary, but once you get used to it, it’s really just copy/paste. Here’s the full code again:

<html>
 <head>
  <title>HelloButton</title>
  <script>
function methodize(methodize_func,methodize_scope){
    return (function(){methodize_func.call(methodize_scope);});
}

function button(title){
 this.element=document.createElement("input");
 this.element.setAttribute("type","button");
 this.element.setAttribute("value", title);
 document.getElementsByTagName("body")[0].appendChild(this.element);
 this.title=title;
 this.element.onclick=methodize(this.showTitle,this);
}

button.prototype.showTitle=function(){
 alert(this.title);
}

function main(){
 var buttonObjects=new Array();
  for(var i=0;i<3;i++){
   buttonObjects[i]=new button("Button #"+i);
  }
}
  </script>
 </head>
 <body onload="main()">
 </body>
</html>

P.S:
There’s one thing that we could add to our methodize function and that’s passing of the event and any other parameters to the methodize_func.

It’s really not much of a deal

function methodize(methodize_func,methodize_scope){
    /* Copy the arguments array, which contains all parameters
       to  methodize_args, except entries #0 and #1  (methodize_func
       ,methodize_scope) */
    var methodize_args=new Array();
    for(var i=2;i<arguments.length;i++) methodize_args.push(arguments[i]);
    /* Return a function that takes an event parameter itself
       and passes it on to methodize_func, along with methodize_args */
    return (function(evt){methodize_func.call(methodize_scope,evt,methodize_args);});
}

And voilĂ : Suddently our target function runs not only in the correct scope, but it also receives the event that triggered its execution as first parameter and whatever you specified after methodize_scope as an array.

A few words about Javascript

I’m a bit of a Javascript fan and since I don’t have anything better to do right now I though I could tell you about a few typical errors that even the guys over at Google, along with 99% of all pages I know, make.

The problem is that people learn Javascript as well, a Script language, not a proper programming language… which is a shame because Javascript itself is probably one of the most comfortable languages I know. And because they don’t see it as a proper programming language they just hack their code until it works… for now.

The worst abomination onto JS is probably browser sniffing: It’s a pretty simple technique that’s easy to understand which is probably why beginners tend to use it, however it’s also a technique that a) requires a lot of testing, b) requires a lot of updates and c) goes completely bonkers when new browsers are released.

What browser sniffing (a.k.a. useragant sniffing) does is “ask” the browser about itself and then taking appropriate measures. Seems simple, right?

Well if it is so simple, then what would happen if you ask a browser if it is a “Mozilla”… Surprise! Pretty much all major browsers (including Internet Explorer) claim to be Mozilla. So let’s ask about MSIE to make sure which Mozillas are actually Internet Explorer. Oops, Opera and a few others report that too. OK, then how about finding out which ones are really Mozilla by looking for “Gecko”. Oh, Safari says it’s “like Gecko”. If you want an almost complete list, have a a look here.

You see: it doesn’t work and it’s really a shame that people still use this first-grader technique if there’s a much easier alternative: Method sniffing.

In Javascript, every function or method that does not exist has the value “undefined”. So if you want to use something that you are afraid isn’t available everywhere, you just ask if the browser supports it directly, instead of asking for the browser and then assuming that a certain browser supports this or that.

For example, lets say we want to use the addEventListener method and as a fallback the attachEvent method, then we simply create a wrapper function:

function wrapperAddEventListener(obj,type,callback){
if(obj.addEventListener!=undefined) obj.addEventListener(type,callback,false);
else if(obj.attachEvent!=undefined) obj.attachEvent(“on”+type,callback);
else alert(“Sorry, your browser is not supported”);
}

And that’s it. And it works for pretty much everything, except for some strange HTML behaviours. Now my minions: Spread the word.

The next time I’ll be looking at the scope of Variables in JS… a topic that isn’t understood by more than a handfull of people, eventhough it’s not that difficult. See you next time.