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 !