Spying on elements!

Definition

An event is a "message" sent by the browser to inform that the internal state of a DOM object has changed.

DOM objects concerned can be the window or any element node of the DOM tree.

To get these "messages", you must use listeners.

This design pattern is know as the "observer pattern".

Listeners

There are different ways of listening to events on elements of the DOM. The correct solution is to use the addEventListener method directly on the element.

In order to keep the content/behaviour separation, you shouldn't use onclick, onload... attributes in your HTML code.

If you use IE before version 9, you'll have to deal with their specific method attachEvent. Event names a prefixed by on and the event object is not passed to your callback, it can be accessed with window.event.

Add

<div id="james" onclick="bond()">
<!-- no content/behaviour separation :-( -->
var james = document.getElementById('james');
james.onclick = bond;                         // only one listener :-(
james.addEventListener('click', bond, false); // W3C Standard
james.attachEvent('onclick', bond);           // IE < 9            :-(
var bond = function(ev) {
  ev = ev != null ? ev : window.event;        // IE < 9 needs this :-(
  console.log(ev);
  console.log('My Name is Bond...');
  setTimeout(function() { console.log('...James Bond.'); }, 800);
}

Remove

Same remarks as for adding event listeners, use the W3C standard when you can. Note that you must use the same function reference to remove an event listener. Removing an anonymous function won't work.

var james = document.getElementById('james');
div.onclick = null;
div.removeEventListener('click', bond, false);  // W3C Standard
div.dettachEvent('onclick', bond);              // IE < 9         :-(

Example

Try to click on the actors with or without maintaining the ctrl key down.

Types & properties

Common properties

When you receive an event with your listener, you can access the event object. It has several useful properties like target or type. Some of them are used in special conditions like bubbling, capturing or cancelling. See reference for more details.

UI Events

UI Events are special kind of events important for window, resources like images or just simple HTML elements.

Example : scroll

Scroll the window with your mouse or using the scrollbars!

Mouse

Mouse events are really popular when dealing with web applications. There's a lot of events available. Understanding the differences between them is not so simple.

When you reveive a mouse event with a listener, you can access mouse specific properties such as cursor position etc... See reference for more details.

Note that mouseenter and mouseleave are only supported by IE and Opera.

Events

Properties

Example : Mouse events

Keyboard

Except for input and textarea, it is often better to listen to keyboard events on the window object. See bubbling/propagation below for more details.

When you reveive a keyboard event with a listener, you can access keyboard specific properties to know which key was pressed etc... See reference for more details.

DOM Event specification level 3 deprecates widely used properties such as keyCode or charCode. They are replaced by key and char but have a different behaviour. See reference for more details.

Events

Properties

Example : Keyboard events

Focus

Focus events are often used on HTML forms. Note that getting and losing focus is not only done by mouse clicks. The order in which those different events are fired is not consistent over each browsers. See reference for more details.

Events

Properties

Example : Focus events

Click on the two gray input text!.

Forms

These events are specific to HTML forms and are not part of the DOM 3 specification. See reference for more details...

Events

There's more...

Mutation events were a DOM Events level 2 specification. There's a few reason for its deprecation in DOM Events level 3. There's a work in progress to find a replacement. See reference for more details.

In order to go further from just handling simple keyboard events, the textinput event is added to handle new ways to input text : speech, handwriting... See reference for more details.

Composition events are also added to handle specific and special text input. See reference for more details.

Bubbling / propagation

Principle

When an event is fired on an element of the DOM, the event registered listeners are invoked. After this step, most events “bubble” up the DOM tree.

This behaviour can be used to improve performances. Instead of adding N listeners for N sibling elements, just listen to their parent. Because of event propagration your unique listener set on the parent will still receive events fired on children elements.

You can determine if the event you're dealing with has been bubbled to your listener or not with eventPhase property.

You can prevent an event from bubbling up the DOM tree by calling stopPropagation() on the event.

Bubbling is also called propagation.

// Determine if the event has bubbled up to the listener
console.log(ev.eventPhase === Event.BUBBLING_PHASE); // true
// Prevents event from "bubbling" up the DOM tree
ev.stopPropagation();

Example : propagation

Cancellation / prevent default

Principle

A lot of events have a default behaviour. The most common example is clicks on links.

You can determine if the default behaviour of an event can be cancelled using the cancelable property.

You prevent an event from firing default behaviour after being handled by your listener by calling preventDefault() on the event.

// Determine if the default behaviour of the event can be cancelled
console.log(ev.cancelable);
// Prevents event from firing default behaviour
ev.preventDefault();

Example : cancellation

Capture

Principle

When you listen to events on a given element using the W3C standard addEventListener method, you can specify if you want to capture events.

A listener with capture will receive events from children elements BEFORE they receive it themselves.

// Set last argument to true to capture
// Doesn't work on IE < 9 with attachEvent
james.addEventListener('click', bond, true);
// Determine if the event was "captured" by the listener
console.log(event.eventPhase === Event.CAPTURING_PHASE); // true

Example

jQuery & Events

How jQuery helps

In general jQuery makes the code easier to read when dealing with events. It's incredibly easier to implement event delegation. It also helps a lot to deal with IE strange way to manipulate events.

Classic on/off

Since jQuery 1.7, all event listening are done using on and off methods. If you were used to use bind/unbind, delegate/undelegate or live/die, you should stop and prefer on and off.

For example a classic addEventListener like this :

var james = document.getElementById('james');
james.addEventListener('click', bond, false);

Would be replaced by this jQuery code :

$('#james').on('click', bond);

It also works for many elements.

var lis = document.querySelectorAll('#actors li');
for (var i = 0; i < lis.length; i++) {
  lis[i].addEventListener('click', callback, false);
}
$('#actors li').on('click', callback);

The code above creates as many listeners as li elements in the DOM. It's usually bad. Be sure to prefer a syntax that leverage event delegation.

You could also use shortcuts like that. There's almost one shortcut for each event type.

$('#actors li').click(callback);
// same shortcuts for other events

When you wanna removeEventListener, just call off method.

$('#actors li').off('click', callback);

Event delegation

The new on method eases event delegation. Just select the node you want to delegate events to using $ and pass the targeted children selector as second argument of on.

This example would be easy to change to use event delegation.

// Doesn't uses event delegation
$('#actors li').on('click', callback);

Now that's better.

// Uses event delegation
$('#actors').on('click', 'li', callback);

It's almost the same as doing that :

// More or less equivalent to that
var ul = querySelector('#actors');
ul.addEventListener('click', function(ev) {
  if (ev.target.nodeName === 'LI') {
    callback(ev);
  }
}, false);