JavaScript Event Handlers & Variables
Posted on Friday, 23rd October 2015 by admin
One of the most important aspects of using JavaScript is responding to user events. When the user clicks on something, types something or even sits and stares, it is possible for JavaScript to respond.
A piece of JavaScript code, typically a function, which responds to an event is referred to as an event handler. This article discuss some of the issues when designing event handler functions.
Event Handlers
JavaScript allows you to attach functions as event handlers. For example, you can have a button which responds to a click using a function as its click event handler.
The traditional way to attach an event handler is: 1 2 3 4
//Assume var button = appropriate button element button.onclick=function() { … ; }
Alternatively you can define a separate function as follows: 1 2 3 4 5
button.onclick=doit;
function doit() { … ; }
Typically, such event handlers are handled in the script’s initialisation function, which is, in turn, an event handler, running when the page has finished loading: 1 2 3 4 5
window.onload=function() { button.onclick=function() { … ; } }
or 1 2 3
window.onload=function() { button.onclick=doit; }
Now the event handler function is, of itself, just an ordinary function. However, when actually called as an event handler, it has two special features:
The this variable becomes a reference to the HTML element which activated the event. This is particularly useful if multiple HTML elements share an event handler function.
The function receives as its first (and only) parameter an event object which contains information about the event as well as the element activating the event. This is not true in Legacy browsers, but what can you do?
Note the last point. Apart from the event object, event handlers do not have parameters, so the only data that you can pass to them is normally via inherited variables. We’ll get on to passing data later. Attaching Events Properly
One problem with the traditional method of attaching event handlers is that you can only attach one event event handler; attaching another will only replace the former one.
Another minor problem is that using this technique gives you no control over how events inside nested elements are handled. To put it simply, event Capturing handles the events from the outside in; event Bubbling handles events from the inside out (which is probably more natural). This is referred to as Event Propagation. Using the traditional method, you are limited to event Capturing.
All modern browsers, even IE9 and above, use a more flexible method called addEventListener. You add it to the element thus: 1
element.addEventListener(type,handler,capture)
For example 1
button.addEventListener('click',doit,false);
The event type is a string, and does not use the “on” prefix.
The last parameter is technically optional, but you should always set it, normally to false. Up until recently, the default was true, reflecting Netscape’s original preference, but lately has changed to false. In any case, you certainly don’t want to be unsure.
Legacy IE, however, uses its own function, attachEvent(). The naming of the event requires the ”on” prefix, and you cannot set the Event Propagation, which is always Bubbling.
Note that attachEvent() does not properly set the this property to the target element, and this might make writing your code even more challenging than it has to be.
A more general purpose code snippet, including testing for feature availability, would look something like this: 1 2 3
if(button.addEventListener) button.addEventListener('click',doit,false); else if (button.attachEvent) button.attachEvent('onclick',doit); else button.onclick=doit;
To be honest, if you don’t need to allow for additional events, you may as well use the traditional method until the Legacy browsers all die out. The loss of a useful this property can make coding unnecessarily tricky. Passing Data
Of themselves, all event handler functions are self-contained, and are not designed to handle additional data. This makes handling a general event handler function more tricky, as you are limited to data values which are fixed.
You can set up some data variables as global variables, but that’s generally regarded as a very bad idea. Among other things, it’s very hard to rely on a value when any old function, including one in a separate file, is free to change it. This is particularly likely if you are using good variable names, the names that are so good that you or someone else is bound to have used it elsewhere.
A common trick with global variables is to create a pseudo-namespace by defining an object and setting properties. For example: 1 2 3
var stuff={}; stuff.title = … ; stuff.name = … ;
This doesn’t prevent overwriting, but it makes it less likely that you’ll do it by accident. It also reduces your global variables into a single object, which is better than doing nothing. Closures
JavaScript has a feature referred to as closures. Put simply:
A function creates a local scope for variables, as long as you define the variables with the var keyword.
Inner variables (and inner function) remain and retain their values as long as they’re needed, even if the outer function has completed.
This last point comes as a shock to developers accustomed to working with other languages.
For most languages, when a function has finished, all of its data, including local variables and inner functions goes the way of all flesh.
In JavaScript they are retained until JavaScript is convinced that they are no longer needed. If you are setting up an event handler, then the function certainly will continue to be needed, and if the event handler happens to use variable from their outer scope, then they, too, will continue to be needed.
Yo might say that a closure creates a sort of semi-global scope for inner functions.
For example: 1 2 3 4 5 6 7
window.onload=init; function init() { var message; //local variable document.getElementById('button').onclick=function() { alert(message); //inherits message from outer function } }
The down side to this technique is that it only works if you define your event handler code inside of the initialisation function. If you define it as a separate function and attach it later, then it won’t be able to inherit closure variables, and you will have to fall back on global variables. Attaching Generated Functions
If you are prepared to involve an additional support function, you can take advantage of many of the more sophisticated features of JavaScript to generate a new function compete with its own data. This offers the best of both worlds.
The function I have called eventHandler, because I couldn’t think of anything else to call it. Basically, you pass this function the function you want to attach, as well as any parameters you might want to include. It will then generate a new function with the data ready to go.
Below is the function: 1 2 3 4 5 6 7
function eventHandler() { var fn=[].shift.call(arguments); var args=arguments; return function() { fn.apply(this,args); } }
To attach an event handler using this method:
Create an event handler function, complete with parameters.
Call this function, and attach its result as the event handler.
For example: 01 02 03 04 05 06 07 08 09 10 11
window.onload=init; function init() { var button1=document.getElementById('button1'); var button2=document.getElementById('button2'); button1.onclick=eventHandler(say,'Hello'); button2.onclick=eventHandler(say,'Goodbye'); }
function say(message) { alert(message); }
Here, we can use the same event handler with different messages. How the Function Works 1 2 3 4 5 6 7
function eventHandler() { var fn=[].shift.call(arguments); var args=arguments; return function() { fn.apply(this,args); } }
The function is only a few lines long, but it involves many of the more sophisticated features of JavaScript. Below is a description of what is going on:
Firstly, every function has access to an arguments object. This object is similar to an array in that it is numbered, but does not have the usual array methods.
In JavaScript, all arguments are optional. Leaving out arguments is fine, as long as you don’t rely on them in your function. Naming your arguments is a convenience for your expected arguments, but you can name more than you expect, or less than you expect.
The arguments object picks up all arguments, including the named ones. For some functions, you don’t care, and for some functions you may name the first few and use the arguments object to discover and handle the rest.
The eventHandler() function relies entirely on the arguments object, though it assumes that the first of these arguments is, in fact, the function which is to be attached.
Secondly, the arguments object is not an array. This means that it has none of the usual array methods, including the shift() method. The shift() method removes the first element, reducing the size of the array. The return value of the function is the removed first element.
JavaScript provides a technique for borrowing methods from other objects. Two similar methods, call() and apply, allow you to use a method from a different object, and apply it to the provided object.