Learn how to dispatch custom events in Javascript properly.

The Javascript events are an important part of the JS ecosystem as they allow us to know when an event occurs without compromise the performance of the browser.

You can create your own events using the CustomEvent API in order to react to those events with an event listener as you usually do with existent events.

Listen event

As we are going to dispatch events with the CustomEvent API, we need to listen first for it, otherwise we'll never get any result.

Remember that an event can only be dispatched on a DOM element (document,input,buttons,div's etc). In this case the event will be named "OurSuperEvent" and it will be (in the next step) dispatched on the document, therefore the listener will be added for the document too.

var element = document;
// or 
// var element = document.getElementById("aRandomId");

element.addEventListener("OurSuperEvent", function (e) {
    // This is the information that we receive from the CustomEvent
    console.log("The custom data of the event is : ", e.detail);
    // The event as itself (object with properties like {srcElement,isTrusted,target,defaultPrevent etc})
    console.log("Event is : ",e);
}, false);

The detail property of the event, contains the information that you send when you dispatch the event.

Note: as said before, the event listener has been appended to the document element. Therefore the event needs to be dispatched in the document element too, otherwise the event will be never listened as it's not the element that we asigned for this event.

Dispatching Custom Event

Now that we have a listener that tell us when our event occurs, you need to learn how to dispatch it.

In the listener we appended the listener to the document, therefore our event (in order to be listened) will be dispatched too in the document.

var myData = {
    id: 1,
    name: "An imaginary name",
    customProperty:"I need to know this string when the event is dispatched",
    moreData: [1,2,4,5,6,7]
};

var element = document;
// or 
// var element = document.getElementById("aRandomId");

// The custom event that receives as first parameter the name of the type of the dispatchedEvent
var event = new CustomEvent("OurSuperEvent", { detail: myData });

element.dispatchEvent(event);

As you can see, the implementation of a custom event is very simple, minimalist and clean.

However (and always in the javascript world), some browsers (Internet explorer) doesn't implement the same initialization.

While a window.CustomEvent object exists in the browser, it cannot be called as a constructor. Instead of new CustomEvent(...), you must use event = document.createEvent('CustomEvent') and then event.initCustomEvent(...) as shown in the following example :

var myData = {
    id: 1,
    name: "An imaginary name",
    customProperty:"I need to know this string when the event is dispatched",
    moreData: [1,2,4,5,6,7]
};

var element = document;
// or 
// var element = document.getElementById("aRandomId");

//Dispatch an event
var event = document.createEvent("CustomEvent");
event.initCustomEvent("OurSuperEvent", true, true, myData);
element.dispatchEvent(event);

Polyfill

Note that CustomEvent API is not supported in some versions of Android's old WebKit-based WebView. Fortunately, there's a polyfill available for this feature.

This snippet covers event IE6. This script polyfills addEventListener, removeEventListener, dispatchEvent, CustomEvent, and DOMContentLoaded. It is less than half a kilobyte minified and gzipped. 

You can visit the repository here and see the source code , or get the polyfill directly here :

// EventListener | CC0 | github.com/jonathantneal/EventListener

this.Element && Element.prototype.attachEvent && !Element.prototype.addEventListener && (function () {
	function addToPrototype(name, method) {
		Window.prototype[name] = HTMLDocument.prototype[name] = Element.prototype[name] = method;
	}

	// add
	addToPrototype("addEventListener", function (type, listener) {
		var
		target = this,
		listeners = target.addEventListener.listeners = target.addEventListener.listeners || {},
		typeListeners = listeners[type] = listeners[type] || [];

		// if no events exist, attach the listener
		if (!typeListeners.length) {
			target.attachEvent("on" + type, typeListeners.event = function (event) {
				var documentElement = target.document && target.document.documentElement || target.documentElement || { scrollLeft: 0, scrollTop: 0 };

				// polyfill w3c properties and methods
				event.currentTarget = target;
				event.pageX = event.clientX + documentElement.scrollLeft;
				event.pageY = event.clientY + documentElement.scrollTop;
				event.preventDefault = function () { event.returnValue = false };
				event.relatedTarget = event.fromElement || null;
				event.stopImmediatePropagation = function () { immediatePropagation = false; event.cancelBubble = true };
				event.stopPropagation = function () { event.cancelBubble = true };
				event.target = event.srcElement || target;
				event.timeStamp = +new Date;

				var plainEvt = {};
				for (var i in event) {
					plainEvt[i] = event[i];
				}

				// create an cached list of the master events list (to protect this loop from breaking when an event is removed)
				for (var i = 0, typeListenersCache = [].concat(typeListeners), typeListenerCache, immediatePropagation = true; immediatePropagation && (typeListenerCache = typeListenersCache[i]); ++i) {
					// check to see if the cached event still exists in the master events list
					for (var ii = 0, typeListener; typeListener = typeListeners[ii]; ++ii) {
						if (typeListener == typeListenerCache) {
							typeListener.call(target, plainEvt);

							break;
						}
					}
				}
			});
		}

		// add the event to the master event list
		typeListeners.push(listener);
	});

	// remove
	addToPrototype("removeEventListener", function (type, listener) {
		var
		target = this,
		listeners = target.addEventListener.listeners = target.addEventListener.listeners || {},
		typeListeners = listeners[type] = listeners[type] || [];

		// remove the newest matching event from the master event list
		for (var i = typeListeners.length - 1, typeListener; typeListener = typeListeners[i]; --i) {
			if (typeListener == listener) {
				typeListeners.splice(i, 1);

				break;
			}
		}

		// if no events exist, detach the listener
		if (!typeListeners.length && typeListeners.event) {
			target.detachEvent("on" + type, typeListeners.event);
		}
	});

	// dispatch
	addToPrototype("dispatchEvent", function (eventObject) {
		var
		target = this,
		type = eventObject.type,
		listeners = target.addEventListener.listeners = target.addEventListener.listeners || {},
		typeListeners = listeners[type] = listeners[type] || [];

		try {
			return target.fireEvent("on" + type, eventObject);
		} catch (error) {
			if (typeListeners.event) {
				typeListeners.event(eventObject);
			}

			return;
		}
	});

	// CustomEvent
	Object.defineProperty(Window.prototype, "CustomEvent", {
		get: function () {
			var self = this;

			return function CustomEvent(type, eventInitDict) {
				var event = self.document.createEventObject(), key;

				event.type = type;
				for (key in eventInitDict) {
					if (key == 'cancelable'){
						event.returnValue = !eventInitDict.cancelable;
					} else if (key == 'bubbles'){
						event.cancelBubble = !eventInitDict.bubbles;
					} else if (key == 'detail'){
						event.detail = eventInitDict.detail;
					}
				}
				return event;
			};
		}
	});

	// ready
	function ready(event) {
		if (ready.interval && document.body) {
			ready.interval = clearInterval(ready.interval);

			document.dispatchEvent(new CustomEvent("DOMContentLoaded"));
		}
	}

	ready.interval = setInterval(ready, 1);

	window.addEventListener("load", ready);
})();

(!this.CustomEvent || typeof this.CustomEvent === "object") && (function() {
	// CustomEvent for browsers which don't natively support the Constructor method
	this.CustomEvent = function CustomEvent(type, eventInitDict) {
		var event;
		eventInitDict = eventInitDict || {bubbles: false, cancelable: false, detail: undefined};

		try {
			event = document.createEvent('CustomEvent');
			event.initCustomEvent(type, eventInitDict.bubbles, eventInitDict.cancelable, eventInitDict.detail);
		} catch (error) {
			// for browsers which don't support CustomEvent at all, we use a regular event instead
			event = document.createEvent('Event');
			event.initEvent(type, eventInitDict.bubbles, eventInitDict.cancelable);
			event.detail = eventInitDict.detail;
		}

		return event;
	};
})();

Have fun !


Senior Software Engineer at Software Medico. Interested in programming since he was 14 years old, Carlos is a self-taught programmer and founder and author of most of the articles at Our Code World.

Sponsors