Source: br-util/src/br/util/EventUtility.js

'use strict';

/**
 * @module br/util/EventUtility
 */

var Errors = require("br/Errors");
var MapFactory = require('br/util/MapFactory');

/**
 * @class
 * @alias module:br/util/EventUtility
 */
function EventUtility() {
}

/** @private */
EventUtility.UNIQUE_ID = 1;

/** @private */
EventUtility.m_mEvents = undefined;

/** @private */
EventUtility.m_bUnloadListenerAdded = false;

/**
 * Registers the specified event function to a particular event using the provided DOM element.
 *
 * @param {Object} oTargetElem The document element the event will be registered against.
 * @param {String} sEvent The name of the event that will be registered (e.g. 'click', but not 'onclick').
 * @param {Function}  fEventListener The function that will be called when the event fires.
 * @param {boolean}  bDirectAttachedEvent Whether the simpler form of event registration (e.g. <code>elem.onclick</code>)
 *				   should be used (<code>false</code> by default). This is still occasionally useful in IE when you
 *				   want access to the target element and that is different to <code>window.event.srcElement</code>.
 *
 * @see #getEventHandlerFromMethod
 *
 * @returns An event listener id to be used in the method (@link #removeEventListenerById) to remove
 *		 the newly added event listener or NULL if the event listener could not be added for any reason.
 * @type int
 */
EventUtility.addEventListener = function(oTargetElem, sEvent, fEventListener, bDirectAttachedEvent, bUseCapture) {
	EventUtility._initMap();

	if (sEvent.match(/^on/)) {
		throw new Errors.IllegalStateError("events should be given without the on prefix");
	}
	var bSuccess = false;

	// set up the event
	if (bDirectAttachedEvent) {
		oTargetElem["on" + sEvent] = fEventListener;
		bSuccess = true;
	} else if (oTargetElem.addEventListener) {
		oTargetElem.addEventListener(sEvent, fEventListener, bUseCapture||false);
		bSuccess = true;
	} else {
		bSuccess = oTargetElem.attachEvent("on" + sEvent, fEventListener);
	}

	if (bSuccess) {
		// store a reference to the event for unload removal
		var nUniqueListenerId = EventUtility.UNIQUE_ID++;
		EventUtility.m_mEvents[nUniqueListenerId] = {"elem":oTargetElem, "event":sEvent, "listener":fEventListener, "isDirect":bDirectAttachedEvent};
		return nUniqueListenerId;
	} else {
		return null;
	}
};

/**
 * Removes the DOM event listener that has previously been added via the {@link #addEventListener} method.
 *
 * @param {int} nUniqueListenerId The event Listener Id that was returned by the method {@link #addEventListener}.
 */
EventUtility.removeEventListener = function(nUniqueListenerId)
{
	EventUtility._initMap();

	EventUtility._registerUnloadListener();
	var oEvent = EventUtility.m_mEvents[nUniqueListenerId];
	if (!oEvent) {
		return;
	}

	if (oEvent.isDirect) {
		oEvent.elem["on" + oEvent.event] = null;
	} else if (oEvent.elem.removeEventListener) {
		oEvent.elem.removeEventListener(oEvent.event, oEvent.listener, false);
	} else {
		oEvent.elem.detachEvent("on" + oEvent.event, oEvent.listener);
	}

	EventUtility.m_mEvents = MapFactory.removeItem(EventUtility.m_mEvents, nUniqueListenerId);
};

/**
 * Stops the propagation of an event.
 *
 * This method should be used within an event listener to prevent bubbling of an
 * event up to other event listeners.
 *
 * @param {Event} oEvent Event passed to your event handler. Note that event handlers are not wrapped
 *				so you need to do something like <code>oEvent = oEvent || window.event;</code>
 */
EventUtility.stopPropagation = function(oEvent)
{
	oEvent.cancelBubble = true;
	if (oEvent.stopPropagation) {
		oEvent.stopPropagation();
	}
};

/**
 * Allows the user to prevent the default event, with the use <code> return Utility.preventEventDefault(oEvent); </code>
 *
 * @param {Event} oEvent Event passed to your event handler. Note that event handlers are not wrapped
 *				so you need to do something like <code>oEvent = oEvent || window.event;</code>
 *
 * @type boolean
 * @returns Always returns false
 */
EventUtility.preventDefault = function(oEvent)
{
	oEvent.returnValue = false;
	if (oEvent.preventDefault) {
		oEvent.preventDefault();
	}
	return false;
};

/** @private */
EventUtility._initMap = function()
{
	if (EventUtility.m_mEvents === undefined) {
		EventUtility.m_mEvents = MapFactory.createMap();
	}
};

/** @private */
EventUtility._registerUnloadListener = function()
{
	if (!EventUtility.m_bUnloadListenerAdded) {
		if (window.addEventListener) {
			window.addEventListener("unload", EventUtility._handlePageUnload, false);
		} else {
			window.attachEvent("onunload", EventUtility._handlePageUnload);
		}
		EventUtility.m_bUnloadListenerAdded = true;
	}
};

/**
 * Since event handlers are prone to cause memory leaks in IE, we unregister
 * them all when the page is unloaded.
 *
 * @private
 */
EventUtility._handlePageUnload = function()
{
	EventUtility._initMap();

	// remove all the event handler registered with addEventListener()
	for (var nUniqueListenerId in EventUtility.m_mEvents) {
		EventUtility.removeEventListener(nUniqueListenerId);
	}

	// remove the unload event listener that caused this callback
	if (window.removeEventListener) {
		window.removeEventListener("unload", EventUtility._handlePageUnload, false);
	} else {
		window.detachEvent("onunload", EventUtility._handlePageUnload);
	}

	EventUtility.m_mEvents = MapFactory.createMap();
};

module.exports = EventUtility;