﻿/**
  *  Event Mixins
  *  (c) 2006 Seth Dillingham <seth.dillingham@gmail.com>
  *
  *  This software is hereby released into the public domain. Do with it as
  *  you please, but with the understanding that it is provided "AS IS" and 
  *  without any warranty of any kind.
  *  
  *  (But I'd love to be told about where and how this code is being used.)
  **/
  
/**
  *  Description:
  *    add support (to any object or class) by mixing this class into your own
  *  
  *  Requires prototype.js
  *  
  *  Usage:
  *    To publish custom events:
  *      1. mix this class with your own via
  *         Object.extend( [your class or prototype], Event.Publisher )
  *      2. post events by calling
  *         this.dispatchEvent( [event name], [data for event] )
  *   
  *    To activate and deactivate the event-tracing feature, just call 
  *      this.toggleEventsTrace()
  **/
Event.Publisher = Class.create();
Object.extend( Event.Publisher, 
{
 _ls_event_targets: null,
 
 _event_source_id: null,
 
 _fl_trace_events: false,
 
 getEventSourceId: function()
 {
  if ( typeof this._event_source_id == 'function' )
   return this._event_source_id();
  else
   return this._event_source_id;
 },
 
 getEventTarget: function( event_name )
 {
  if ( ! this._ls_event_targets )
   this._ls_event_targets = new Array();
  
  if ( ! this._ls_event_targets[ event_name ] )
   document.body.appendChild(
    this._ls_event_targets[ event_name ] = document.createElement( 'A' )
   );
  
  return this._ls_event_targets[ event_name ];
 },
 
 addEventListener: function( event_name, callback_func, capturing )
 {
  var targ = this.getEventTarget( event_name );
  
  Event.observe( targ, 'click', callback_func, capturing );
  
  if ( this._fl_trace_events )
  {
   var data = 
   {
    publisher: this.getEventSourceId(),
    event_name: event_name,
    listener: callback_func,
    capturing: capturing,
    event_source_proxy: targ
   };
   
   this.dispatchEvent( 'eventListenerAdded', data, true, true );
  }
 },
 
 removeEventListener: function( event_name, callback_func, capturing )
 {
  var targ = this.getEventTarget( event_name );
  
  Event.stopObserving( targ, 'click', callback_func, capturing );
  
  if ( this._fl_trace_events )
  {
   var data = 
   {
    publisher: this.getEventSourceId(),
    event_name: event_name,
    listener: callback_func,
    capturing: capturing,
    event_source_proxy: targ
   };
   
   this.dispatchEvent( 'eventListenerRemoved', data, true, true );
  }
 },
 
 dispatchEvent: function( event_name, data, can_bubble, cancelable )
 {
  var targ = this.getEventTarget( event_name );
  var event_data = {
   event_name: event_name,
   event_target: this,
   data: data ? data : null
  };
  
  if ( ! can_bubble ) can_bubble = false;
  if ( ! cancelable ) cancelable = false;
  
  var event = Event.create( targ, event_data, can_bubble, cancelable, true );
  
  if ( this._fl_trace_events )
  {
   if ( event_name.match( /event(?:ListenerAdded|ListenerRemoved|Dispatched|Received)/ ) )
    return;
   
   var data = 
   {
    publisher: this.getEventSourceId(),
    event_name: event_name,
    event_data: event_data,
    can_bubble: can_bubble,
    cancelable: cancelable,
    event_source_proxy: targ,
    result: event
   };
   
   this.dispatchEvent( 'eventDispatched', data, true, true );
  }
 },
 
 toggleEventsTrace: function()
 {
  var trace = Event.Tracer.findTracer();
  
  if ( ! trace || ! this._fl_trace_events )
  {
   this._fl_trace_events = true;
   
   trace = Event.Tracer.startTrace();
   
   trace.registerPublisher( this );
  }
  else
  {
   this._fl_trace_events = false;
   
   if ( trace )
    trace.unregisterPublisher( this );
  }
  
  return this._fl_trace_events;
 },
 
 isEventsTraceActive: function()
 {
  return this._fl_trace_events;
 }
} );
/**
  *  MIX IN: Event.Listener
  *  Description:
  *    easily add support for receiving totally custom events
  *    (to any object or class) by mixing this class into
  *    your own
  *  Usage:
  *    To receive custom events:
  *      1. mix this class with your own via
  *         Object.extend( [your class or prototype], EventListener )
  *      2. listen for events by calling (from your object)
  *         this.listen()
  *         (see params for this.listen, below)
  **/
Event.Listener = Class.create();
Object.extend( Event.Listener,
{
 _listens: null,
 
 getEventHandlerName: function( event_name )
 {
  var onEvent_name = event_name.split( /[ _]/ ).join( '-' ).camelize();
  
  return "on" + onEvent_name.charAt( 0 ).toUpperCase() + onEvent_name.substr( 1 );
 },
 
 /**
   *  Params:
      *    event_source [object]:
      *      the object which will generate the events, and which implements (or
      *      mixes in) the Event.Publisher interface (we need addEventListener)
      *    event_name [string]:
      *      the name of the event for which your object will listen
      *    use_capture [boolean]:
      *      standard DOM Event API param
      *    onEvent_name [string]:
      *      the name of the method in your object which will be called when the
      *      event is received if you omit this param, listen will look for a
      *      function named with the CapitalizedCamelCased name of the event with
      *      "on" at the front. So, if the event is named "message_received",
      *      we'll look for a function named "onMessageReceived" You can override
      *      this behavior by overriding getEventHandlerName in your object.
   **/
 listenForEvent: function( event_source, event_name, use_capture, onEvent_name )
 {
  if ( ! onEvent_name )
   onEvent_name = this.getEventHandlerName( event_name );
  
  if ( ! this._listens ) this._listens = new Array();
  
  var cb = this[ onEvent_name ].bindAsEventListener( this );
  this._listens.push( [ event_source, event_name, use_capture, onEvent_name, cb ] )
  
  event_source.addEventListener( event_name, cb, use_capture );
 },
 
 stopListeningForEvent: function( event_source, event_name, use_capture, onEvent_name )
 {
  if ( ! this._listens ) return false;
  
  if ( ! onEvent_name )
   onEvent_name = this.getEventHandlerName( event_name );
  
  var ix_item = -1;
  var ls = this._listens.detect( function( val, ix )
  {
   if ( ( val[ 0 ] == event_source )
     && ( val[ 1 ] == event_name )
     && ( val[ 2 ] == use_capture )
     && ( val[ 3 ] == onEvent_name ) )
   {
    ix_item = ix;
    return true;
   }
  } );
  
  if ( ix_item >= 0 )
  {
   this._listens.splice( ix_item, 1 );
   
   event_source.removeEventListener( event_name, ls[ 4 ], use_capture );
   
   return true;
  }
  
  return false;
 }
} );
/**
  *  Extensions to Prototype's Event object,
  *  for cleanly creating and dispatching custom events
  *  
  *  Called from Event.Publisher
  **/
Object.extend( Event,
{
 create: function( target, event_data, can_bubble, cancelable, fl_dispatch )
 {
  var event;
  
  if ( document.createEvent )  // gecko, safari
  {
   if ( ! can_bubble ) can_bubble = false;
   if ( ! cancelable ) cancelable = false;
   
   if ( /Konqueror|Safari|KHTML/.test( navigator.userAgent ) )
   {
    event = document.createEvent( 'HTMLEvents' )
    
    event.initEvent( 'click', can_bubble, cancelable );
   }
   else  // gecko uses MouseEvents
   {
    event = document.createEvent( 'MouseEvents' )
    
    event.initMouseEvent( "click", can_bubble, cancelable,
                          window, 0, 0, 0, 0, 0,
                          false, false, false, false, 0, null );
   }
  }
  else  // msie
  {
   event = document.createEventObject();
  }
  
  event.event_data = event_data;
  
  if ( fl_dispatch )
   Event.dispatch( target, event );
  
  return event;
 },
 
 dispatch: function( target, event )
 {
  if ( document.createEvent )
   return target.dispatchEvent( event );
  else
   return target.fireEvent( 'onclick', event );
 }
} );