The event handler functions, which we have to write, are invoked automatically whenever an event of the relevant type occurs on the element to which we attached the handler (as described on the previous page).
When this happens the system passes an object into the handler as a parameter, an object which contains data about the event. In the case of mouse events this will include the position of the mouse at the time when the event occurred. In the case of a key being pressed it will include the character code for that key. So of course you need to know how to get the relevant data from the event object.
Looking again at the JavaScript file grDraw.js, examples can be seen: the functions handleClick () and handleMove (). The first of these begins like this:
function handleClick (event)
{
var pt = getMousePoint (event);
if (op == "circ0")
{
pt0 = pt1 = pt;
how.innerHTML = "Click on the rim of the circle";
op = "circ1";
}
else if (op == "circ1")
{
op = "none";
pt1 = pt;
how.innerHTML = " ";
g2.putImageData (imData, 0, 0);
drawCircle (pt0, pt1, strokeColour, fillColour);
imData = g2.getImageData (0, 0, cnv.width, cnv.height);
}
else // ... other operations
}
The system passes in the parameter which we have called event
. It will be an object of a subclass of Event. There are many such subclasses, each with different properties available to be read. The ones you are most likely to use at first are shown in the following table, together with their most useful properties.
Event type | Properties | Comments |
---|---|---|
KeyboardEvent | key, altKey, ctrlKey, shiftKey | key value booleans: whether modifier key is pressed |
InputEvent | data | The characters inserted into an input field (empty if deleting) |
MouseEvent | clientX, clientY, pageX, pageY | Unfortunately not simple - see below |
The Mozilla JavaScript reference page for Event gives a lot more detail.
The two pairs of values are relative to different origins. event.pageX and event.pageY, are coordinates within the whole document, taking into account any scrolling. On the other hand event.clientX and event.clientY are measured within the visible window area, the top left corner of which may not be the corner of the whole document if scrolling or panning has occurred. The client values have been recognised in browsers for longer than the page ones - for example Internet Explorer only started recognising pageX and pageY in version 9. So when our handler code was written (2010) it was necessary to allow for the possibility that the preferred page coordinates were not available. Therefore we used a function to get the coordinates from the event in any browser:
function getMousePoint (ev)
{
if (ev.pageX || ev.pageY) return { x: ev.pageX - cnv.offsetLeft, y: ev.pageY - cnv.offsetTop }
return { x: ev.clientX + document.body.scrollLeft + document.documentElement.scrollLeft - cnv.offsetLeft,
y: ev.clientY + document.body.scrollTop + document.documentElement.scrollTop - cnv.offsetTop };
}
This function always returns an object containing two values, x and y, but uses a more complicated calculation if the properties pageX and pageY are not available (such as in IE before version 9).
The way this is written shows what typically has to be done if properties are possibly not available in all browsers. Notice that we do not have to identify the browser, merely check for the existence of the properties. The same goes for methods that may not be available in all browsers: check for the existence of the name of the method.
For convenience we will repeat the code snippet shown at the beginning of this page:
function handleClick (event)
{
var pt = getMousePoint (event);
if (op == "circ0")
{
pt0 = pt1 = pt;
how.innerHTML = "Click on the rim of the circle";
op = "circ1";
}
else if (op == "circ1")
{
op = "none";
pt1 = pt;
how.innerHTML = " ";
g2.putImageData (imData, 0, 0);
drawCircle (pt0, pt1, strokeColour, fillColour);
imData = g2.getImageData (0, 0, cnv.width, cnv.height);
}
else // ... other operations
}
So you can see our getMousePoint () being called first to get a point object containing 2 coordinate values.
The op
variable is global to our program. If the user clicks the button for drawing a circle the onclick handler for that button sets op
to the value "circ0" and at the same time a variable called how
is set to prompt the user to point to the centre of a circle. When the mouse is clicked our handler is then able to determine that the mouse point is the centre of a circle. The user is then prompted to point to the rim of the circle, to set its size, and op
is changed to "circ1", ready for the next click. When that happens there is enough information to be able to draw the circle and we have written a function for doing that.
As always in programming, there would have been other ways of writing this and certain choices have been made. For a start, instead of using a series of if ... else if
statements we could have used a switch / case
. Another possibility would be to remove the listener after each step and add a different one to the canvas ready for the next click. The new listener would then not have to test which operation it is dealing with but you would have a proliferation of handler functions. To some extent this is a matter of taste, as long as you have thought whether there could be performance considerations which make one approach preferable.