API Docs for:
Show:

File: latest/events.js

/**
 * Daniel15 JavaScript Framework - By Daniel15, 2011
 * http://dl.vc/jsframework
 * Feel free to use any of this, but please link back to my site (dan.cx)
 */

/**
 * Normalises DOM event handling across browsers
 * @class Events
 * @static
 * @module JSFramework
 * @submodule DOM
 */
var Events = (function()
{
	var eventHandling;
	
	// First do the browser-specific stuff
	// W3C
	if (document.addEventListener)
	{
		eventHandling = {
			/**
			 * Add an event handler.
			 * @method add
			 * @param {HTMLElement} obj  Element to add event handler to
			 * @param {String}      type Type of event handler (eg. "click")
			 * @param {Function}    fn   Function that will handle the event
			 * @example
	var el = document.getElementById('blah');
	Events.add(el, 'click', function() { alert('I was clicked!'); });
			 */
			add: function(obj, type, fn)
			{
				obj.addEventListener(type, fn, false);
			},
			/**
			 * Stop this event from propagating or running the default action
			 * @method stop
			 * @param {Event} e The event
			 */
			stop: function(e)
			{
				e.preventDefault();
				e.stopPropagation();
			}
		};
	}
	// Internet Explorer
	else
	{
		eventHandling = {
			add: function(obj, type, fn)
			{
				/**
				 * @method normalizeEvent
				 * Normalize the event - Convert IE-specific properties into W3C properties. Only used
				 * if the browser is IE
				 * @param {Event} e The event
				 * @return {Event} A modified version of the event
				 * @private
				 */
				function normalizeEvent(e)
				{
					// target - What actually triggered the event
					e.target = e.srcElement;
					// currentTarget - Where the event has bubbled up to (always the object the
					// handler is attached to)
					// Reference: https://developer.mozilla.org/en/DOM/event.currentTarget
					e.currentTarget = obj;
					// Mouse location
					// Reference: http://www.quirksmode.org/js/events_properties.html
					e.pageX = e.clientX + document.body.scrollLeft + document.documentElement.scrollLeft;
					e.pageY = e.clientY + document.body.scrollTop + document.documentElement.scrollTop;
					return e;
				}
				
				// TODO: This can't be an anonymous function if we ever want to remove the handler
				obj.attachEvent('on' + type, function()
				{
					fn.call(obj, normalizeEvent(window.event));
				});
			},
			stop: function(e)
			{
				e.returnValue = false;
				e.cancelBubble = true;
			}
		};
	}
	
	// Now add the custom methods
	return Util.extend(eventHandling, 
	{
		/**
		 * Onload handler for Daniel15 framework. Calls onload functions depending on the current page.
		 * Splits the body ID by hyphen (-), uses first piece as the main object, and other pieces as 
		 * sub-objects. Non-existant init methods are ignored (no error is thrown).
		 *
		 * Example: An ID of "site-projects" would call Page.Site.init() and Page.Site.Projects.init().
		 * site-projects-foo would call Page.Site.init(), Page.Site.Projects.init() and Page.Site.Foo.init().
		 * "blog" would call Page.Blog.init().
		 *
		 * @method initPage
		 * @param [id=document.body.id] Page ID
		 */
		initPage: function(id)
		{
			// Everything here is in the Page object. No Page object, nothing can be done
			if (!window.Page)
				return;
			
			// First call the global page init function
			Page.Global && Page.Global.init();
			
			// Ensure the ID is set
			id || (id = document.body.id);
			
			var id_pieces = id.split('-');
			// First piece is the main object
			var obj_name = id_pieces.shift();
			obj_name = obj_name.charAt(0).toUpperCase() + obj_name.slice(1);
			
			// If we don't have the object, just die
			if (!(obj = Page[obj_name]))
				return;
			
			// Call the base object initialisation function
			obj.init && obj.init();
			// Go through each piece, and call the initialisation on it.
			for (var i = 0, count = id_pieces.length; i < count; i++)
			{
				// Make the first letter uppercase
				var piece = id_pieces[i].charAt(0).toUpperCase() + id_pieces[i].slice(1);
				// Call the init method if it exists
				obj[piece] && obj[piece].init && obj[piece].init();
			}
		}
	});
})();

/**
 * Event delegation - Attach an event handler to a container element to handle events for inner
 * elements. This has several advantages, including improved performance when a lot of elements are
 * involved (as less event handlers are attached), and not having to attach event handlers to 
 * AJAX-loaded content.
 *
 * @class EventDelegation
 * @static
 * @module JSFramework
 * @submodule DOM
 */
var EventDelegation = 
{
	/**
	 * The current delegates
	 * @property delegates
	 * @type Object
	 * @private
	 */
	delegates: {},
	
	/**
	 * Attach a handler to all tagNames with a class of className, by attaching the event 
	 * listener to containerEl. Works for both current elements, and new elements added in the
	 * future.
	 * @method add
	 * @param	{Element}   containerEl Container element to add event listener to
	 * @param	{String}    type        Type of event listener (eg. "click")
	 * @param	{String}    tagName     Tag name to add events to (eg. "a")
	 * @param	{String}    className   Class name to add events to, or null for none
	 * @param	{Function}  fn          Event handler function
	 * @chainable
	 */
	add: function(containerEl, type, tagName, className, fn)
	{
		// Relies on ElementWrappers being cached, will break if they're not cached any more.
		// This is the internal unique ID, *not* the id in the HTML!
		var id = containerEl.getElementId();
		
		// Check if we have delegates for this element already
		if (!this.delegates[id])
		{
			this.delegates[id] = {};
		}
		
		// Check if we have delegates for this type already
		if (!this.delegates[id][type])
		{
			this.delegates[id][type] = [];
			// Add the handler
			containerEl.addEvent(type, this.handle.bind(this));
		}
		
		this.delegates[id][type].push({tagName: tagName.toUpperCase(), className: className, fn: fn});
		return this;
	},
	
	/**
	 * Handle a delegated event
	 * @method handle
	 * @param	{Event} e Event data
	 * @private
	 */
	handle: function(e)
	{
		var container = $(e.currentTarget);
		var containerId = container.getElementId();
		var target = $(e.target);
		
		// Ensure the element actually has delegates for this event type
		if (!this.delegates[containerId] || !this.delegates[containerId][e.type])
		{
			return;
		}
		
		var delegates = this.delegates[containerId][e.type];
		for (var i = 0, count = delegates.length; i < count; i++)
		{
			var del = delegates[i];
			
			// Check that tag name is the same
			if (target.get('nodeName').toUpperCase() == del.tagName 
			// Check that class name is the same, IF a class name was specified
				&& (!del.className || target.hasClass(del.className)))
			{
				del.fn.call(target, e);
			}
		}
	}
};

// Extend the element wrapper prototype
/**
 * @class ElementWrapper
 */
Util.extend(ElementWrapper.prototype, 
{
	/**
	 * Add an event handler to this element.
	 * @method addEvent
	 * @param	{String}   type Type of event handler (eg. "click")
	 * @param	{Function} fn   Function for handling these events
	 * @chainable
	 */
	addEvent: function(type, fn)
	{
		Events.add(this.element, type, fn);
		return this;
	},
	
	/**
	 * Add a delegated event handler to this element. Attach a handler to all tagNames with a class
	 * of className, by attaching the event listener to "this" element. Works for both current 
	 * elements, and new elements added in the future.
	 * @method addDelegate
	 * @param	{String} type      Type of event listener (eg. "click")
	 * @param	{String} tagName   Tag name to add events to (eg. "a")
	 * @param	{String} className Class name to add events to, or null for none
	 * @param	{String} fn        Event handler function
	 * @chainable
	 */
	addDelegate: function(type, tagName, className, fn)
	{
		EventDelegation.add(this, type, tagName, className, fn);
		return this;
	}
});

// Extend the window
/**
 * Add an event handler to the window
 * @param	Type of event handler (eg. "click")
 * @param	Function for handling these events
 */
window.addEvent = function(type, fn)
{
	Events.add(window, type, fn);
}