Taming JavaScript in practice: event handlers

May 22, 2007 Edward Hieatt

Suppose we have some behavior attached to a button’s onclick event: when clicked, the button should append “foobar” to the value in the text field with ID “output_field”. We might do it like this:

<input type="text" id="output_field">
<button onclick="var textField = document.getElementById('output_field'); var currentValue = textField.value; textField.value=currentValue + 'foobar';">

Inline event handler code such as this is extremely common. It’s easy to write, but it’s not testable, reusable, or readable, and it’s mixed up in the HTML world. The way it is currently, it’s hard to think of it as code that can be refactored, added to, abstracted, generalized, etc. Of course, this is a contrived, simple example, and if it were real code, probably not worth worrying too much about. However, we can use it to demonstrate some techniques that we can apply to real-world situations.

Extracting the handler to a function

Let’s attack it by considering it from the point of view of testability with JsUnit; very often this can the best way to move forward because it creates a second client to the code that is relatively independent of the HTML DOM. The first step is to extract the onclick event into a function, rather than having it inline:

<script language="javascript">
  function appendFoobar() {
    var textField = document.getElementById("output_field");
    var currentValue = textField.value;
    textField.value = currentValue + "foobar";
  }
</script>

<input type="text" id="output_field">
<button onclick="appendFoobar()">

Notice that even ignoring testability, this is a dramatic improvement. First, we don’t a fragment of JavaScript floating around in an HTML element. Second, we have a name for our behavior (appendFoobar) that makes the code more readable: it’s now much clearer that clicking the button should write the current date. Third, we can now reuse this code from more than just our onclick handler.

Writing a test page

The second step is to move our function to an external .js file, say appender.js, so that we can write a Test Page:

<script language="javascript" src="appender.js"></script>

<script language="javascript">
function testAppendFoobar() {
  assertEquals("initialvalue", document.getElementById("output_field"));
  appendFoobar();
  assertEquals("initialvaluefoobar", document.getElementById("output_field"));
}
</script>

<input type="text" id="output_field" value="initialvalue">

Our test is for our extracted function: our test, rather than the button element, calls our extracted function.

Injecting DOM dependencies

Now that we have a green test, and code that we can actually read, we might want to consider the following refactoring. The code and test both go out and grab an element from the DOM with a certain ID. The code would be more self-contained and reusable if rather than going out and finding the DOM element, we instead passed it in to appendFoobar. That is, we could inject the dependency on the DOM element:

function appendFoobar(textField) {
  var currentValue = textField.value;
  textField.value = currentValue + "foobar";
}

function testAppendFoobar() {
  var textField = document.createElement("input");
  textField.value="initialvalue";
  appendFoobar(textField);
  assertEquals("initialvaluefoobar", textField.value);
}

<button onclick="appendFoobar(document.getElementById('output_field'))">

Extracting an object

Let’s do one more refactoring: objectifying our code. Obviously, at this point our simple example doesn’t warrant this refactoring, but let’s keep going to illustrate the point. We’ll use prototype.js to keep things simple:

Appender.prototype = {
  initialize: function(textField) {
    this.textField = textField;
  },

  appendFoobar() {
    var currentValue = this.textField.value;
    this.textField.value = currentValue + "foobar";
  }
}

function testAppender() {
  var textField = document.createElement("input");
  textField.value="initialvalue";
  var appender = new Appender(textField);
  appender.appendFoobar();
  assertEquals("initialvaluefoobar", textField.value);
}

<button onclick="new Appender(document.getElementById('output_field')).appendFoobar()">

Summary

Our code has come a long way from being an inline event handler, muddled up with HTML code. In its new form, it’s the kind of code that many developers are more comfortable working with. By aggressively refactoring even simple event handlers with these techniques, we can make working with our JavaScript a far more pleasant experience.

About the Author

Edward Hieatt

Edward Hieatt is Senior Vice President of Pivotal’s services organization. In this role, Edward is responsible for the strategy, execution, and business results of Pivotal's next-generation client services organization—Pivotal Labs. In addition, Edward is responsible for Pivotal's worldwide technical field and software subscription renewal organizations, which help clients adopt and become successful with Pivotal's products and services.

More Content by Edward Hieatt
Previous
Don’t Underestimate the Power of Laughter
Don’t Underestimate the Power of Laughter

Do you laugh everyday at your job? If not, why? I think too many people become complacent at their job and...

Next
Rails Conference Links
Rails Conference Links

(Blabbers who were at the conference, feel free to add your links to this post.) Alex's RailsConf2007 Flic...

Enter curious. Exit smarter.

Register Now