/** * Event Mixins * (c) 2006 Seth Dillingham * * 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( event_data, can_bubble, cancelable, true, targ ); 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(); //added this in to allow for anonymous function handling of an event var eventHandler = this[onEvent_name]; if(typeof(onEvent_name) == 'function') { eventHandler = onEvent_name; } var cb = eventHandler.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( event_data, can_bubble, cancelable, fl_dispatch, target ) { 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_type = 'onclick'; } 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( ( typeof( event.event_type ) != "undefined" ) ? event.event_type : 'onclick', event ); } } );