/*	DomEvents Version 1.0
	Copyright 2005 Mark Wubben

   Implements cross-browser event listener methods with support for specifying
   scope and altering the event object. Also prevents memory leaks.
	See <http://novemberborn.net/javascript/domevents> for more information.
	
	This software is licensed under the CC-GNU LGPL:
	<http://creativecommons.org/licenses/LGPL/2.1/>
*/

var __DomEvents__ = new function()
{
   /*=:private
      Variable which holds the event listeners.
   */
   var registry = {};
   
   /*=:private
      Create valid keys to be used with the registry.
      
      Parameters:
      * string >> id: ID of the HTML element the event is added to.
      * string >> type: the event type
      * function >> fn: the listener
      
      Returns:
      * string: the key
   */
   function registryKey(id, type, fn)
   {
      return id + "#" + type + "#" + fn;
   }

   /*=:private
      Finds out if the key is in the registry already.
      
      Returns:
      * boolean: whether the key is in the registry or not.
   */
   function isInRegistry(key)
   {
      return registry[key] != null;
   }
   
   /*=:private
      Invokes the listener under `key` and passes them the event object. Also
         passes the event object to `__DomEvents__.alterEventObject()`.
      
      Parameters:
      * string >> key: a key generated using `registryKey()`.
      * Event >> evt: the event object (as it comes from the browser).
   */
   function invoke(key, evt)
   {
      if(!isInRegistry(key))
         return null;

      _evt = __DomEvents__.alterEventObject(evt);
      // If the alteration method returned an object, use that as the event
      // object.
      if(typeof _evt == "object")
         evt = _evt;
      
      var handler = registry[key];
      var scope = handler.scope;
       
      // Evade Function#apply()
      scope.__DomEvents_listener__ = handler.listener;
      scope.__DomEvents_listener__(evt);
      scope.__DomEvents_listener__ = null;
   }

   /*=:private
      Counter for `targetId()`.
   */
   var targetIdCount = 0;
   
   /*=:private
      Selects an ID for the target.
      
      If no ID has been set on the target, it looks for `uniqueID`. If this
         is not available either (it's IE only) a new ID is generated. This
         ID is prefixed by "__DomEvents_ID_".
         
      Parameters:
      * target: the event target to get the ID for. `document` and `window` are
         supported as well.
      
      Returns:
      * string: the ID.
   */
   function targetId(target)
   {
      if(target == document)
         return "__DomEvents_ID_document";
      if(target == window)
         return "__DomEvents_ID_window";
      
      var id = target.getAttribute("id") || target.uniqueID;
      
      if(id == null){
         id = "__DomEvents_ID_" + targetIdCount++;
         target.setAttribute("id", id);
      }
      
      return id;
   }
      
   /*=
      Adds the listener.
      
      Parameters:
      * target: the element to which the listener will be added.
         `document` and `window` are supported as well.
      * string >> type: the type of the event, without the "on" prefix.
      * function >> listener: the listener you want to add.
      * object >> scope: the scope this listener should be invoked in.
         Default: `el`.
         
      Returns:
      * boolean: whether the listener has successfully been added or not.
   */
   window.addEvent = function(target, type, listener, scope)
   {
      var key = registryKey(targetId(target), type, listener);
      scope = scope || target;
      
      //    Check if the listener hasn't been registered already.
      
      /*!
         Listeners with the same method but a different scope are treated as
            identical listeners.    
      */
      if(isInRegistry(key))
         return false;
      
      // Hey, it's a new listener!
      handler = {
         listener:   listener,
         scope:      scope,
         invoker:    function(evt)
                     {
                        invoke(key, evt);
                     }
      }
      
      registry[key] = handler;
      
      if(target.addEventListener){
         target.addEventListener(type, handler.invoker, false);
      } else if(target.attachEvent){
         target.attachEvent("on" + type, handler.invoker);
      } else {
         return false;
      }
      
      // Reset variables:
      target = listener = scope = null;
      
      return true;
   }
   
   /*=
      Removes the listener.
      
      Parameters:
      * target: the element from which the listener will be removed.
         `document` and `window` are supported as well.
      * string >> type: the type of the event, without the "on" prefix.
      * function >> listener: the listener you want to remove.
      * [object >> scope]: the scope this listener should be invoked in.
         Default: `el`.
         
      Returns:
      * boolean: whether the listener has successfully been removed or not.
   */  
   window.removeEvent = function(target, type, listener, scope)
   {
      var key = registryKey(targetId(target), type, listener);
      
      if(!isInRegistry(key))
         return false;
         
      var invoker = registry[key].invoker;
      
      registry[key] = null;

      if(target.removeEventListener){
         target.removeEventListener(type, invoker, false);
      } else if(target.detachEvent) {
         target.detachEvent("on" + type, invoker);
      } else {
         return false;
      }
      
      return true;
   }

   /*=
      A function which can alter the event object. Override this to use your
         own.
         
      Parameters:
      * evt: Event object to alter.
      
      Returns:
      * [object]: the modified event object. You can also modify `evt` 
         directly.
   */
   this.alterEventObject = function(){};
}
