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.

11 thoughts on “Object Oriented Programming and Event Handlers”

  1. WOW! I’ve been looking for months for how to do this! It all came down to your simple little “methodize” function. That was my missing link! Thank you, thank you, thank you! Works like a CHARM!

  2. I know this is an old article but I just thought worth mentioning that I have been trying to get the parameter passing to work (methodize_args) and I can’t – it works excellently with no parameters though. I have done a lot of reading and from what I can see the arguments array doesn’t work with call although it may work with apply (but haven’t tested this yet)

    thanks for posting though – it’s a great article

  3. Wow, that’s an old post. The good news is that now there’s a standardized way to do what you’re trying to do, it’s called “bind”: https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Function/bind . The article includes a nifty little fallback as well. Using bind, all you have to do is:

    Assuming this is your class:
    var Program=function(argText){
    this.text=argText;
    };
    Program.prototype.text=””;
    Program.prototype.myMethod=function(arg0,arg1){
    alert(this.text+” “+arg0+” “+arg1);
    };

    You can bind myMethod using
    var progInstance=new Program(“Hello World”);
    window.addEventListener(“load”,progInstance.myMethod.bind(progInstance),false);
    (which would output the property plus a text describing the load event plus undefined.)

    If you want to pass additional args, you just specify it as 2+nth arg to bind:
    window.addEventListener(“load”,progInstance.myMethod.bind(progInstance,”myArg”),false);
    which in this case would output property + “myArg” + event, because these additional arguments are always inserted in front of the one that the function receives, in this case the event.

  4. Thanks so much for the feedback Hans – I’ll definitely look into “bind” further!

  5. Hi again Hans,

    For completeness, here is a very simple conceptual idea of what I’ve been attempting. The problem is passing the vCounter + 1 value to methodizeFn as it seems to be ignored (I’ve tried amending the call to

    function loopy() {

    var item1 = function(vCounter) {
    setTimeout(methodizeFn(item2,this,vCounter + 1), 10);
    }

    var item2 = function(vCounter) {
    if (vCounter < 50) { // this will error with "vCounter is undefined"
    setTimeout(methodizeFn(item1,this,vCounter + 1), 10);
    }
    }

    this.go = function() {
    item1(1);
    }
    }

    var myLoop = new Loopy();
    myLoop.go();
    ———————–
    I've also tried to replace "copy" with "apply" as follows:
    return (function(evt){methodize_func.apply(methodize_scope,[evt].concat(methodize_args));});

    and although it doesn't fall over the arguments are not passed in to the called function for some reason I don't understand – oh well.

  6. The function simply was very much bound to event handling and as such assumes one arg from the calling function (setTimeout in your case) and any number from when you call methodizeFn, which it puts into an array. So your item2 would have to be
    var item2 = function(dummy, vCounter){
    vCounter=vCounter[0];

    You should really look at bind and the fallback… it’s a heck of lot cleaner than what I cobbled together back in the day.

  7. Hi Hans,

    I can’t thank you enough for the information you’ve given above. I truly am an idiot, but with your comment I now have the function working exactly as desired (using both call and apply):

    I suppose the challenge I have around using bind at this point is a combination of 2 things:
    1. The methodize function was discovered some time ago and it has always worked brilliantly
    2. the above combined with delivery pressure – we will definitely investigate bind however would prefer to take a holistic approach to its implementation and we unfortunately don’t have the time currently.

    I do have one last question also I’m hoping you can help with…
    As you can see from the above one of the challenges we find is making sure we stay on top of JS developments (information seems very distributed on the net). As you are clearly very knowledgeable about Javascript I am wondering whether you perhaps have a recommendation as to the most effective way – ie. buying books? doing courses? searching on the web – as we do currently? (is there a particular JS web resource you would recommend?)

    Either way – thanks both for your post and your time – you’ve been incredibly helpful!

  8. I wish I could give you a straightforward answer to that, but I have yet to read a single book about Javascript or visit any courses (although I’ve taught it occasionally), whether online or in real-life… I just pick things up here and there.

    Strangely, the most important resource for me nowadays is the ECMA-262 spec itself, plus the articles that float around on the Mozilla MDN, The rest is simple googling 🙂

    The thing about Javascript, is that it’s brain-dead simple, once you master the basics. Everything after that is just a matter of finding the right commands to do it.

  9. Thanks Hans, pity, i had a feeling you may say googling 🙂 – believe it or not I’ve actually written a fair amount of javascript over time but it’s not my primary development area so my involvement is very erratic. While there are a lot of things I feel I do understand frustratingly there are some basic concepts that I feel I don’t and would love to get on top of.

    Thanks again – stay well

Leave a Reply

Your email address will not be published. Required fields are marked *

*

This site uses Akismet to reduce spam. Learn how your comment data is processed.