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