Section 17.6. The onload Event

17.6. The onload Event

JavaScript code that modifies the document in which it is contained must typically run after the document is fully loaded (this is discussed in further detail in Section 13.5.7.). Web browsers fire an onload event
on the Window object when document loading is complete, and this event is commonly used to trigger code that needs access to the complete document. When your web page includes multiple independent modules that need to run code in response to the onload event, you may find a cross-platform utility function like the one shown in Example 17-7 to be useful.

Example 17-7. Portable event registration for onload event handlers

/*
* runOnLoad.js: portable registration for onload event handlers.
*
* This module defines a single runOnLoad( ) function for portably registering
* functions that can be safely invoked only when the document is fully loaded
* and the DOM is available.
*
* Functions registered with runOnLoad( ) will not be passed any arguments when
* invoked. They will not be invoked as a method of any meaningful object, and
* the this keyword should not be used. Functions registered with runOnLoad( )
* will be invoked in the order in which they were registered. There is no
* way to deregister a function once it has been passed to runOnLoad( ).
*
* In old browsers that do not support addEventListener( ) or attachEvent( ),
* this function relies on the DOM Level 0 window.onload property and will not
* work correctly when used in documents that set the onload attribute
* of their <body> or <frameset> tags.
*/
function runOnLoad(f) {
if (runOnLoad.loaded) f( ); // If already loaded, just invoke f( ) now.
else runOnLoad.funcs.push(f); // Otherwise, store it for later
}
runOnLoad.funcs = []; // The array of functions to call when the document loads
runOnLoad.loaded = false; // The functions have not been run yet.
// Run all registered functions in the order in which they were registered.
// It is safe to call runOnLoad.run( ) more than once: invocations after the
// first do nothing. It is safe for an initialization function to call
// runOnLoad( ) to register another function.
runOnLoad.run = function( ) {
if (runOnLoad.loaded) return; // If we've already run, do nothing
for(var i = 0; i < runOnLoad.funcs.length; i++) {
try { runOnLoad.funcs[i]( ); }
catch(e) { /* An exception in one function shouldn't stop the rest */ }
}
runOnLoad.loaded = true; // Remember that we've already run once.
delete runOnLoad.funcs; // But don't remember the functions themselves.
delete runOnLoad.run; // And forget about this function too!
};
// Register runOnLoad.run( ) as the onload event handler
for the window
if (window.addEventListener)
window.addEventListener("load", runOnLoad.run, false);
else if (window.attachEvent) window.attachEvent("onload", runOnLoad.run);
else window.onload = runOnLoad.run;

17.7. Synthetic Events

Both the DOM Level 2 event model and the IE event model allow you to create synthetic
event objects and dispatch them to event handlers registered on document elements. In essence, this is a technique for tricking browsers into invoking the event handlers registered on an element (and, in the case of bubbling events, the handlers registered on the ancestors of the element). With the Level 0 event model, synthetic events
are not necessary because event handlers are available through the various event handler properties. In the advanced event models, however, there is no way to query the set of handlers registered with addEventListener or attachEvent, and those handlers can only be invoked using the techniques demonstrated in this section.

In the DOM event model, you create a synthetic event with Document.createEvent( ), initialize that event with Event.initEvent( ), UIEvent.initUIEvent( ), or MouseEvent.initMouseEvent( ) and then dispatch the event with the dispatchEvent method of the node to which it is to be dispatched. In IE, you create a new event object with Document.createEventObject and then dispatch it with the fireEvent( ) method of the target element. Example 17-8 demonstrates these methods. It defines a cross-platform function for dispatching
synthetic dataavailable events and a function for registering event handlers for events of that type.

It is important to understand that synthetic events dispatched with dispatchEvent( ) and fireEvent( ) are not queued and handled asynchronously. Instead, they are dispatched immediately, and their handlers are invoked synchronously before the call to dispatchEvent( ) or fireEvent( ) returns. This means that dispatching a synthetic event is not a technique for deferring execution of code until the browser has handled all pending events. For that, it is necessary to call setTimeout( ) with a timeout value of 0 milliseconds.

It is possible to synthesize and dispatch low-level raw events such as mouse events, but it is not well specified how document elements respond to these events. It is typically more useful to use this functionality for higher-level semantic events to which the browser does not have a default response. This is why Example 17-8 uses the dataavailable event type.

Example 17-8. Dispatching synthetic events

/**
* DataEvent.js: send and receive ondataavailable events.
*
* This module defines two functions, DataEvent.send( ) and DataEvent.receive( ),
* for dispatching synthetic dataavailable events and registering event
* handlers for those events. The code is written to work in Firefox and other
* DOM-compliant browsers, and also in IE.
*
* The DOM event model allows synthetic events of any type, but the IE model
* supports only synthetic events of predefined types. dataavailable events
* are the most generic predefined type supported by IE and are used here.
*
* Note that events dispatched with DataEvent.send( ) are not queued the way
* real events would be. Instead, registered handlers are invoked immediately.
*/
var DataEvent = {};
/**
* Send a synthetic ondataavailable event to the specified target.
* The event object will include properties named datatype and data
* that have the specified values. datatype is intended to be a string
* or other primitive value (or null) identifying the type of this message,
* and data can be any JavaScript value, including an object or array.
*/
DataEvent.send = function(target, datatype, data) {
if (typeof target == "string") target = document.getElementById(target);
// Create an event object. If we can't create one, return silently
if (document.createEvent) { // DOM event model
// Create the event, specifying the name of the event module.
// For a mouse event, we'd use "MouseEvents".
var e = document.createEvent("Events");
// Initialize the event object, using a module-specific init method.
// Here we specify the event type, bubbling, and noncancelable.
// See Event.initEvent, MouseEvent.initMouseEvent, and UIEvent.initUIEvent
e.initEvent("dataavailable", true, false);
}
else if (document.createEventObject) { // IE event model
// In the IE event model, we just call this simple method
var e = document.createEventObject( );
}
else return; // Do nothing in other browsers
// Here we add some custom properties to the event object.
// We could set existing properties as well.
e.datatype = datatype;
e.data = data;
// Dispatch the event to the specified target.
if (target.dispatchEvent) target.dispatchEvent(e); // DOM
else if (target.fireEvent) target.fireEvent("ondataavailable", e); // IE
};
/**
* Register an event handler for an ondataavailable event on the specified
* target element.
*/
DataEvent.receive = function(target, handler) {
if (typeof target == "string") target = document.getElementById(target);
if (target.addEventListener)
target.addEventListener("dataavailable", handler, false);
else if (target.attachEvent)
target.attachEvent("ondataavailable", handler);
};