Hey, I'm writing a book too :) Click the link below to preorder and save ;)
»» Get the Only Reference You'll "ever" Need on JavaScript Engineering Interviews, Now! ««
May 9th in o2.js Modules by .

Cross-Browser Event Handling in JavaScript

Use Events When Handling Stuff

Use Events When Handling Stuff

In one of the former posts, I’ve mentioned that we were going to create a simple mobile chat application. In order to achive this, we need to add several modules to our o2.js framework, one of which is a cross-browser Event Handler.

Creating a cross-browser Event Handler is pretty straightforward, once you do proper object detection.


You can click here, to get the source code of this tutorial (5Kb zipped archive) »»

Adding an Event Listener

/**
 * @function {static} o2.EventHandler.addEventListener
 *
 * <p>Adds a new event listener to the DOM Node.</p>
 *
 * @param {DomNode} obj - the object the evet shall be attached.
 * @param {String} evt - the name of the event (like "click", "mousemove"...)
 * @param {Function} fn - a reference to the on[event] callback action.
 */
addEventListener: function(obj, evt, fn) {
	if(!obj) {
		return;
	}

	if(obj.addEventListener) {
		o2.EventHandler.addEventListener = function(obj, evt, fn) {
			// "false" is for not to use event capturing. Event capturing
			// is not very useful, since its implementation vastly deviates
			// among different browser vendors.
			// See: http://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-flow
			obj.addEventListener(evt, fn, false);
		};

		o2.EventHandler.addEventListener(obj, evt, fn);

		return;
	}

	if(obj.attachEvent) {
		o2.EventHandler.addEventListener = function(obj, evt, fn) {
			var onEvent = ['on',evt].join('');
			obj.attachEvent(onEvent, fn);
		};

		o2.EventHandler.addEventListener(obj, evt, fn);

		return;
	}

	o2.EventHandler.addEventListener = function(obj, evt, fn) {
		var onEvent = ['on',evt].join('');
		obj[onEvent] = fn;
	};

	o2.EventHandler.addEventListener(obj, evt, fn);
},

Removing an Event Listener

Removing an event handler is equally easy; the only thing too keep in mid is to cache a reference to the original event handling function, because we’ll need it when removing the listener.

/**
 * @function {static} o2.EventHandler.removeEventListener
 *
 * <p>Removes an already-added new event listener from the DOM Node.</p>
 *
 * @param {DomNode} obj - the object the evet shall be removed.
 * @param {String} evt - the name of the event (like "click", "mousemove"...)
 * @param {Function} fn - a reference to the on[event] callback action.
 */
removeEventListener: function(obj, evt, fn) {
	if(obj.removeEventListener) {
		o2.EventHandler.removeEventListener = function(obj, evt, fn) {
			obj.removeEventListener(evt, fn, false);
		};

		o2.EventHandler.removeEventListener(obj, evt, fn);

		return;
	}

	if(obj.detachEvent) {
		o2.EventHandler.removeEventListener = function(obj, evt, fn) {
			var onEvent = ['on',evt].join('');
			obj.detachEvent(onEvent, fn);
		};

		o2.EventHandler.removeEventListener(obj, evt, fn);

		return;
	}

	o2.EventHandler.removeEventListener = function(obj, evt, fn) {
		var onEvent = ['on',evt].join('');
		obj[onEvent] = o2.nill;
	};

	o2.EventHandler.removeEventListener(obj, evt, fn);
},

Getting the Target of the Event

An important thing to consider is that any event propagates upwards in the DOM hierarchy, unless you stop the propagation explicitly (we’ll come to that in a second).

The target of the event is the initial DOM Node that the event starts propagating.

/**
 * @function {static} o2.EventHandler.getTarget
 *
 * <p>Gets the originating source of the event.</p>
 *
 * @param {Event} evt - the actual <code>DOM Event</code> object used internally
 * in {@link o2.EventHandler.addEventListener}
 * @return the actual DOM target of the event object.
 */
getTarget: function(evt) {
	var target = window.event ?
	o2.EventHandler.getTarget = function() {
		return window.event.srcElement;
	} :

	o2.EventHandler.getTarget = function(e) {
		return e ? e.target : null;
	};

	return o2.EventHandler.getTarget(evt);
},

We’ll sometimes need to get the original DOMevent object”; here’s how:

/**
 * @function {static} o2.EventHandler.getEventObject
 *
 * <p>Gets the actual event object.</p>
 *
 * @param {Event} evt - the actual <code>DOM Event</code> object used internally
 * in {@link o2.EventHandler.addEventListener}
 * @return the actual <code>DOM Event</code> object.
 */
getEventObject: function(evt) {
	o2.EventHandler.getEventObject = window.event ? function() {
		return window.event;
	}: function(e) {
		return e;
	};

	return o2.EventHandler.getEventObject(evt);
},

Stop Propagation

To stop the event propagation that we’ve mentioned earlier, we need to use cancelBubble for MSIE, and stopPropagation() for the standard-compliant browsers:

/**
 * @function {static} o2.EventHandler.stopPropagation
 *
 * <p>Stops the propagation of the event upwards in the DOM hierarchy.</p>
 * <p>See {@link http://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-flow}
 * for details.</p>
 *
 * @param {Event} evt - the actual <code>DOM Event</code> object used internally
 * in {@link o2.EventHandler.addEventListener}
 */
stopPropagation: function(evt) {
	o2.EventHandler.stopPropagation = window.event ? function() {
		window.event.cancelBubble = true;
	} : function(e) {
		if(!e) {
			return;
		}
		e.stopPropagation();
	};

	o2.EventHandler.stopPropagation(evt);
}

Preventing the Default Action

Each DOM node is associated with a default action for certain events. This is the “default behavior” of the element when no event handler is explicitly atached to it. For instance when you click a link, the browser reloads with the url of th link — that’s the default click event of that link. Or when you type into a textarea, the things you type actually appear inside the textarea — that’s the default keypress action of the textarea object. Or when you press enter in a form field, the form submits — that’s the default action of the form element.

Sometimes those default behaviors happen to be counter-productive to our application and we may need to suppress them. Here’s how:

/**
 * @function {static} o2.EventHandler.preventDefault
 *
 * <p>Prevents the default action. When this method is called inside an even
 * handling
 * callback, the default action associated with that event is not triggered.
 * Like,
 * if it is an <code>onclick</code event on a link, then the browser does not go
 * to
 * the <code>href</code> of that link.</p>
 *
 * @param {Event} evt - the actual <code>DOM Event</code> object used internally
 * in
 * {@link o2.EventHandler.addEventListener}
 */
preventDefault: function(evt) {
	o2.EventHandler.preventDefault = window.event ? function() {
		window.event.returnValue = false;
		return false;
	} : function(e) {
		if(!e) {
			return;
		}
		if(e.preventDefault) {
			e.preventDefault();
		}
		return false;
	};

	o2.EventHandler.preventDefault(evt);
},

These are the core methods, that we’ll almost always need.

And here are some extension methods:

Getting the Mouse Coordinates

/**
 * @function {static} o2.EventHandler.getMouseCoordinates
 *
 * <p>Gets the current mouse coordinates.</p>
 *
 * @param {Event} evt - the actual <code>DOM Event</code> object used internally
 * in {@link o2.EventHandler.addEventListener}
 * @return the coordinates in the form of <code>{x: mouseX, y: mouseY}</code>
 * where <code>x</code> is the distance from the top of the screen, and
 * <code>y</code> is the distance from the left of the screen.
 */
me.getMouseCoordinates = function(evt) {
	var e = getEventObject(evt);

	if(!e) {
		return {
			x:0,
			y:0
		};
	}

	var posx = 0;
	var posy = 0;

	if (e.pageX) {
		me.getMouseCoordinates = function(e) {
			if(!e) {
				return {
					x:0,
					y:0
				};
			}
			posx = e.pageX || 0;
			posy = e.pageY || 0;
			return {
				x:posx,
				y:posy
			};
		};

		return me.getMouseCoordinates(evt);
	}

	if (e.clientX) {
		me.getMouseCoordinates = function(e) {
			if(!e) {
				return {
					x:0,
					y:0
				};
			}

			var clientX = e.clientX || 0;
			var clientY = e.clientY || 0;

			posx = clientX + document.body.scrollLeft +
			document.documentElement.scrollLeft;
			posy = clientY + document.body.scrollTop +
			document.documentElement.scrollTop;

			return {
				x:posx,
				y:posy
			};
		};

		return me.getMouseCoordinates(evt);
	}

	//the current event object neither has pageX, nor clientX defined.
	return {
		x:0,
		y:0
	};
};

Getting the Code of the Pressed Keyboard Key

/**
 * @function {static} o2.EventHandler.getKeyCode
 *
 * <p>Gets the key code of the key-related event (keydown, keyup, keypress
 * etc.).</p>
 *
 * @param {Event} evt - the actual <code>DOM Event</code> object used internally
 * in {@link o2.EventHandler.addEventListener}
 * @return the <code>keyCode</code> associated with the event as an
 * <code>Integer</code>
 */
me.getKeyCode = function(evt) {
	var e = getEventObject(evt);

	if(!e) {
		return null;
	}

	if(e.charCode) {
		me.getKeyCode = function(e) {
			return e.charCode;
		};

		return me.getKeyCode(evt);
	}

	if(e.keyCode) {
		me.getKeyCode = function(e) {
			return e.keyCode;
		};

		return me.getKeyCode(evt);
	}

	if(e.which) {
		me.getKeyCode = function(e) {
			return e.which;
		};

		return me.getKeyCode(evt);
	}

	return null;
};

Is it a Right Click?

/**
 * @function {static} o2.EventHandler.isRightClick
 *
 * <p>Checks whether or not the curent action is a right click action.</p>
 *
 * @param {Event} evt - the actual <code>DOM Event</code> object used internally
 * in {@link o2.EventHandler.addEventListener}
 * @return <code>true</code> if the event is a right click event,
 * <code>false</code> otherwise.
 */
me.isRightClick = function(evt) {
	var e = getEventObject(evt);

	/*
	 * According to W3C
	 * Left Button: 0
	 * Middle Button: 1
	 * Right Button: 2 (!)
	 * 
	 * According to M$
	 * Lef Button: 1
	 * Middle Button: 4
	 * Right Button: 2 (!)
	 * Left and Right: 3
	 * Left and Middle: 5
	 * Right and Middle: 6
	 * All three: 7
	 * http://msdn.microsoft.com/en-us/library/ms533544(v=vs.85).aspx
	 */

	if(!e) {
		return false;
	}

	if(e.which) {
		me.isRightClick = function(e) {
			return e.which == 3;
		};

		return me.isRightClick(evt);
	}

	if(e.button) {
		me.isRightClick = function(e) {
			return e.button == 2;
		};

		return me.isRightClick(evt);
	}

	return false;
};

Demo

Here’s a sample demo to test all these:

<html>
<head>
	<title>o2.EventHandler Demo</title>
</head>
<body>
<div>
	<a href="/" title="click me" id="ClickTest">click me</a>
	<a href="/" title="click me" id="RemoveClickTest">remove handler</a>
</div>
<div id="Output">
	output
</div>
<div id="MouseCoordinates">
	mouse coordinates.
</div>
<div id="KeyCodes">
	key codes.
</div>
<script type="text/javascript" src="js/o2.js"></script>
<script type="text/javascript" src="js/o2.domhelper.ready.js"></script>
<script type="text/javascript" src="js/o2.eventhandler.core.js"></script>
<script type="text/javascript" src="js/o2.eventhandler.extend.js"></script>
<script type="text/javascript" src="js/o2.debugger.js"></script>
<script type="text/javascript" src="js/eventhandler/main.js"></script>
</body>
</html>

And the main.js is:

/*global window, o2*/

o2.ready(function(){
	var listen = o2.EventHandler.addEventListener;
	var forget = o2.EventHandler.removeEventListener;
	var prevent = o2.EventHandler.preventDefault;
	var getCoords = o2.EventHandler.getMouseCoordinates;
	var getCode = o2.EventHandler.getKeyCode;
	var isRightClick = o2.EventHandler.isRightClick;
	
	var clickTest = o2.$('ClickTest');
	var removeClickTest = o2.$('RemoveClickTest');
	var output = o2.$('Output');
	var mouseCoordinates = o2.$('MouseCoordinates');
	var keyCodes = o2.$('KeyCodes');
	
	var listenClick = function(evt){
		output.innerHTML = 'Hello';
		
		prevent(evt);		
	};
	
	listen(clickTest, 'click', listenClick);
	
	listen(removeClickTest, 'click', function(evt){
		prevent(evt);
		
		output.innerHTML = 'Removed handler';
		
		forget(clickTest, 'click', listenClick);
	});
	
	listen(document, 'mousemove', function(evt){
		var coords = getCoords(evt);
		mouseCoordinates.innerHTML = coords.x + ' ' + coords.y;
	});
	
	listen(document, 'keydown', function(evt){
		keyCodes.innerHTML = getCode(evt);
	});
		
	listen(document, 'mousedown', function(evt){
		prevent(evt);
		
		if(isRightClick(evt)){
			output.innerHTML += ' - is right click!';
		}
	});	
});

That’s all for our o2.EventHandler object.

Now that our EventHandler is ready, we’re one step closer to creating a “mobile chat application“. Stay tuned for the next tutorials ;)

And, as always, feel fee to add your comments and suggestions :)

It's me: Volkan! Jack of all Trades, Samurai of JavaScript ;) Since 2003, I’ve been doing front-end development on client-heavy AJAX web applications. Currently, I'm a JavaScript Hacker at "SocialWire". Before that I was a VP of Technology at "GROU.PS", a well-known do-it-yourself social networking platform that allows people to come together and form interactive communities around a shared interest or affiliation. Before that, I was a JavaScript Engineer at "LiveGO", a globally known social mash-up. Before that, I was the CTO of a business network ("cember.net") which was acquired by Xing AG for around 4.2M Euros. See my linkedin profile to find more about me; I also share worth-following bits an pieces on twitter.

VIsit Volkan Özçelik's website

6 Comments

Leave a Reply





o2.js _
Fork Ribbon