/* ------------------------------------------------------------------------ *
 * SAS Institute, Inc.                                                      *
 * Copyright (c) 2002 SAS Institute, Inc.  All rights reserved.             *
 *                                                                          *
 * Contains methods that support event handling and registration across     *
 * browsers (IE 5.5+ & NS 6.2+ &IE7 & FF                                    *                                                                        *
 * Dependencies: Requires that dojo library be loaded first                 *
 * ------------------------------------------------------------------------ */

wrsProvide("citation_events");
wrsRequire("citation",  SCRIPT_DIR, "citation_events");

//Cross browser event handler registration method
//
//@parm element - the object on which to register the handler
//@parm eventType - the event type which the handler is registered to handle. Types
//      should *not* include the 'on' prefix (e.g. specify 'click' rather than 'onclick').
//@parm listener - the function or object to register as the event handler. If listener is an
//      object it should have a 'handleEvent' method.
//@parm captures - specifies whether the handler should capture any events it handles
//
// Note: This function assumes the element is in the same window
// as this code.  If the element is in another window (e.g., another dialog),
// it won't work (and neither will dojo.event.connect(...)).
// See citation_RichEditor.js for one way to solve this.

// What is left here in the comments is my abortive attempt to use
// dojo connect for all types of event registration.  There were a number
// of unexpected problems with this approach.  Suffice it to say that
// there is a great deal that can be done with dojo.event.connect(), but
// some of the constructs in dojo code get in the way of the rather simple
// way we have constructed events in most cases.  I leave the remnants as
// stimulus for later research.  Note the concern I have described about
// the mis-match between anonymous functions for registration and unregistration.
function registerEventHandler ( element, eventType, listener, captures )
{
    // var listenerFunc = null;
    var msg = null;
    var retval = true;
    var registrationStyle = (typeof listener == "function") ? "func" : (listener.handleEvent ? "obj" : null);
    if (captures) {
        console.info("NOTE: registerEventHandler capture request! ", 
                        (element.tagName + " " + element.id + " " + eventType));
    }
    if (!registrationStyle) {
        alert("registerEventHandler could not register listener " + listener);
        return null;
    }
    // These functions need to be created here so that the meaning of "this"
    // in many of the object.handleEvent()-type listeners is correct.
    var listenerFunc = (registrationStyle == "obj") 
                        ? function (evnt) { listener.handleEvent(evnt ? evnt : window.event); }
   						: listener; // Changed this to listener for S0404536						
                        
    console.info("registerEventHandler --> "
    	+"{ element id:    " + (element.id ? element.id : "none"
        +"; element tag: " +(element.tagName ? element.tagName : "none"))
    	+"; event type: " + eventType
    	+"; listener: " + (listener.toString().substr(0,20)) + "..."
    	+"; capture: " + captures + " }"); 

    if ("load" == eventType) {
        dojo.addOnLoad(listenerFunc);
    } else if ("unload" == eventType) {
        dojo.addOnUnload(listenerFunc);
    } else if (element) {
        if (element.addEventListener) {
            element.addEventListener( eventType, listenerFunc, captures );
        } else if (element.attachEvent) {
            element.attachEvent( "on" + eventType, listenerFunc);
        } else {
            console.error("registerEventHandler failed to locate native event handler registration function!\n"
                + "  { element: "  + element 
                + ", eventType: " + eventType 
                + ", listener: " + listener 
                + ", capture flag: " + captures + " }" );
            retval = null;
        }
    } else { 
      retval = null;
    }
    return retval;
}
// -------------------------------------------------------------------------------
// Cross browser method to remove event handlers from an object
//
// @parm element - the object on which to the handler was registered
// @parm eventType - the event type which the handler is registered to handle. Types
//      should not include the 'on' prefix (e.g. specify 'click' rather than 'onclick').
// @parm listener - the function to remove as an event handler.
// @parm captures - true if a capturing event handler is to be removed
//
// -------------------------------------------------------------------------------
function unRegisterEventHandler ( element, eventType, listener, captures )
{
	if ( listener == null ||
	        (typeof listener != "function" && listener.handleEvent == null) )
    {
        alert( 'unRegisterEventHandler could not unregister listener' + listener );
        return false;
    }
    
    showDebugMsgs("UN-RegisterEventHandler", "------->\n"
    			+"  [ element:"+(element.id ? element.id : (element.tagName ? element.tagName : element))
    			+"\n  event:"+eventType
				+"\n  listener:"+(listener.toString().substr(0,24))
				+"\n  capture:"+captures+"\n ------------------------------------------------------------- ]" );

    // IMPORTANT: Per S0404536, do the job of unregistering the object the caller explicitly requests.
    if (element.removeEventListener) {
        element.removeEventListener( eventType, listener, captures );
    } else if (element.detachEvent){
        element.detachEvent( "on" + eventType, listener );
    } else {

        var msg = "ERROR: Failed to locate native event handler unregistration function!";
        msg += "\nunregisterEventHandler("+element+", "+eventType+", "
    			+listener.toString().substr(0, 25)+", "+captures+")";
        
        showDebugMsgs(msg);
        alert(msg);
        return false;
    }
    
    return true;
}

//Cross browser event dispatcher. Causes all handlers registered on the element for the particular
//event type to be invoked.
//
//@parm eventTarget - the object whose handlers will receive the dispatched event
//@parm eventType - the event type which the handler is registered to handle. Types
//      should not include the 'on' prefix (e.g. use specify 'click' rather than 'onclick').
//@parm listener - the function or object to register as the event handler. If listener is an
//      it should have a 'handleEvent' method.
//@parm captures - specifies whether the handler should capture any events it handles
//

eventNotify.eventFactory = new cwEventFactory();
function eventNotify ( eventTarget, eventType, isBubbleAllowed, cancelable, preventDefault )
{
    var evt = eventNotify.eventFactory.createEvent(eventTarget, eventType, isBubbleAllowed, cancelable);
    if (evt.cancelable && null != preventDefault && preventDefault && evt.preventDefault) {
		evt.preventDefault();  // S0384487
	}
    eventNotify_lw( eventTarget, evt );
}

// lightweight version of eventNotify that takes a pre-constructed event and
// dispatches to event handlers registered on eventTarget
//
//@parm eventTarget - the object on whose handlers will receive the dispatched event
//@parm evt - the event to dispatch. The evt must have been created using cwEventFactory's createEvent() method
function eventNotify_lw ( eventTarget, evt, isCustomEvent )
{
    if( eventTarget.dispatchEvent) {
        showDebugMsgs("EventNotify (DOM)", "begin event dispatch " + evt.type);
        var retCode = eventTarget.dispatchEvent(evt);
        showDebugMsgs("EventNotify (DOM)", "end event dispatch " + evt.type);
    }
    else if( eventTarget.fireEvent) {
       if( !isCustomEvent ) {
            evt.type = "on" + evt.type;
        }
        showDebugMsgs("EventNotify (IE)", "begin event dispatch " + evt.type);
        eventTarget.fireEvent( evt.type, evt );
        showDebugMsgs("EventNotify (IE)", "end event dispatch " + evt.type);
    } else {
        showDebugMsgs("Unable to fire event of type: " + evt.type + " for object " + eventTarget );
    }
}

// -------------------------------------------------------------------
// cwEventFactory methods
// ----------------------------------------------------------------
cwEventFactory.prototype.createEvent = function (eventTarget, eventType, bubblingAllowed, cancelable, eventModule)
{
    var evt = null;
    var eModule = (eventModule != null) ? eventModule : "HTMLEvents";
    var isBubblingAllowed = (bubblingAllowed != null) ? bubblingAllowed : true;
    var isCancelable = (cancelable != null) ? cancelable : false;

    if( document.createEvent ) {
        //Dom 2 way
        evt = document.createEvent(eModule);
        evt.initEvent(eventType, isBubblingAllowed, isCancelable );
        evt.sas_target = eventTarget;
    }
    else if( document.createEventObject ) {
        evt = document.createEventObject();
        evt.type = eventType;
        evt.srcElement = eventTarget;
        evt.cancelBubble = !isBubblingAllowed;
        evt.sas_target = eventTarget;
    }
    else {
        alert("ERROR: EventFactory could not create event of type: "
        		+ eventType
        		+ " using native event creation mechanism" );
    }
	showDebugMsgs("cwEventFactory..createEvent", "target:" + eventTarget.id + " type:" + eventType);
    return evt;
}

function cwEventFactory()
{
   //return this;
}

//Function which when assigned as an object method, allows that object to register
//listeners who wish to respond to specific types of events dispatched from the object.
//This function is useful for custom javascript objects which are not HTML elements and
//do not have any pre-defined addEventListener method.
//
//@parm eventType - the event type for which the listener is registered to handle
//@parm listener - the object on which will respond to events of the given type from this object
//
// Example: see cwDispatchEvent
//
function cwAddEventListener( eventType, listener )
{
    showDebugMsgs("Entering cwAddEventListener.", " Event type = " + eventType );
    if ( this.eventTypes == null ) {
        //create the array of listener types
        this.eventTypes = new Array();
    }
    if ( this.eventTypes[eventType] == null ) {
        //create the array for the specified listener type
        this.eventTypes[eventType] = new Array();
    }
    var listeners = this.eventTypes[eventType];
    if ( ! dojo.lang.inArray(listeners, listener) ) {
        listeners[listeners.length] = listener;
        return true;
    }
    else {
        return false;
    }
}

//Function which when assigned as an object method, allows that object to remove registered
//event listeners.  This function is useful for custom javascript objects which are not
//HTML elements and do not have any pre-defined addEventListener method.
//
//@parm eventType - the event type for which the listener is registered to handle
//@parm listener - the object which will be removed so that it can no respond to events of
//  type eventType from this object.
//
function cwRemoveEventListener( eventType, listener )
{
    showDebugMsgs("==================================\n"
    				+"Entering cwRemoveEventListener", eventType);
    if( this.eventTypes == null || this.eventTypes[eventType] == null ) {
        return false;
    }
    var listeners = this.eventTypes[eventType];
    for( var i=listeners.length; i>=0; i-- ) {
        if( listeners[i] == listener ) {
            listeners.splice(i, 1); // remove the listener from the list & ensure the list remains contiguous
            return true;
        }
    }

    return false;
}

//Function which when assigned as an object method, allows the object to dispatch events to
//registered event listeners.  This function is useful for custom javascript objects which
//are not HTML elements and do not have any pre-defined dispatchEvent method.
//
//@parm event - the event to dispatch
//
// Example:
//    var myTree = new Tree();
//    myTree.addEventListener = cwAddEventListener;
//    myTree.removeEventListener = cwRemoveEventListener;
//    myTree.dispatchEvent = cwDispatchEvent;
//    registerEventHandler ( myTree,  "TREE_SELECTION", mvRightStateHandler, true );
//
//    window.treeClick = function(nodeId, classPrefix) {
//
//         var node = arrayOfObjects[nodeID];
//         var tree = node.tree;
//         ...
//
//         //assign focus to newly selected node
//         node.navObj.getElementsByTagName("a")[0].focus();
//         eventNotify( tree             /* object on which event occurred */,
//                      "TREE_SELECTION" /* type of event that occurred */,
//                      false            /* event shouldn't bubble since it is specific to the tree */
//                    );
//    }
//
function cwDispatchEvent( event )
{
    showDebugMsgs("Entering cwDispatchEvent", " Event type = " + event.type );
    if( event.type == null ) {
       var exc = new EventException();
       exc.code = EventException.UNSPECIFIED_EVENT_TYPE_ERROR;
       throw exc;
    }

    var bListsExist = (this.eventTypes != null) && (this.eventTypes[event.type] != null);
    if( !bListsExist ) {
        return; // no registered listeners to dispatch to
    }

    var listeners = this.eventTypes[event.type];
    for( var i=0; i<listeners.length; i++ ) {
        showDebugMsgs("cwDispatchEvent", "dispatching event to handler(" + i + ")" );
        listeners[i](event); //execute the handler
    }

    showDebugMsgs("Exiting cwDispatchEvent", "..........");
}

//------------------------------------------------------------------------------
// Returns true if the event parameter is a paste event, false otherwise.
//------------------------------------------------------------------------------
function isPasteEvent(event) {
	if (event.type == "keyup") {
		return (86 == event.keyCode && event.ctrlKey && !event.shiftKey);	// 86 == 0x56 == "V", unshifted
	}
	else if (event.type == "keypress") {
		var keyCode= (event.which) ? event.which : event.keyCode;
		return (118 == keyCode && event.ctrlKey);	// 118 == 0x76 == "v"
	}
	return (event.type == "paste");
}

//------------------------------------------------------------------------------
// Returns true if the event parameter is a cut event, false otherwise.
// Note: Delete and Backspace keys are not considered cut events.
//------------------------------------------------------------------------------
function isCutEvent(event) {
	if (event.type == "keyup") {
		return (event.keyCode == 88 && event.ctrlKey && !event.shiftKey);	// 88 == 0x58 == "X", unshifted
	}
	else if (event.type == "keypress") {
		return (event.keyCode == 120 && event.ctrlKey);	// 120 == 0x78 == "x"
	}
	return (event.type == "cut");
}

//------------------------------------------------------------------------------
// CLASS: ProhibitedCharacterListener

// (S0491865: Added \u201c and \u201d as unicode for 8220 and 8221 decimal (what you get from charCodeAt()), 
// which are the "smart quotes" (curved, not straight) that cause problems when pasted
// from MS-Word 2003 or later documents)
// The characters on the following line are invalid in file names (onkeypress codes on 2nd line):
//       <    >   &   #   /   \   :   ?   *   "   |    @   left-quote right-quote
//       60   62  38  35 47   92  58  63  42  34  124  64  8220        8221
g_regExpInvalidFileChars = /[<>&#\/\\\:\?\*"\|@\u201c\u201d]/g; // "prohibited characters in file name

// Ditto for invalid "web characters":
//       <    >   &   #
//		 60   62  38  35
g_regExpInvalidWebChars =  /[<>&#]/g;	// prohibited characters

// Ditto for invalid "security characters":
//       <    >   &   #   \   (    )
//		 60   62  38  35  92  40   41
g_regExpInvalidSecurityChars =  /[<>&#\\\(\)]/g;	// prohibited characters

// Ditto for invalid "filter characters":
//       <    >
//		 60   62
g_regExpInvalidFilterChars = /[<>]/g;	// prohibited characters

// This class attaches itself to a given <input type="text">
// element when constructed, discarding any characters that
// match the given regular expression as they are typed.
// It also removes the prohibited characters from paste events.

// ProhibitedCharacterListener"Constructor":
// @param textInputField - the <input type="text" ...> element
// to listen to.
// @prarm regExpProhibited - the regular expression for prohibited
// characters (such as one of the g_regExp... values above).
// @param options - optional hash object, {limit:count}, where count
// is the maximum number of characters allowed to be entered.
function ProhibitedCharacterListener(textInputField, regExpProhibited, options) {
    this.textField = textInputField;
    this.regExp = regExpProhibited;
    this.limit = 0;
    if (undefined != options && null != options) {
		if (options.limit) {
			this.limit = options.limit;	
		}
	}
    registerEventHandler(this.textField, 'keypress', this, false );
    registerEventHandler(this.textField, 'paste', this, false );
}

// Note: life of value for this global is very short, only for 0-length timeout duration:
var g_thisProhibitedCharacterListener = null;	// Temp store for instance of this class, for callback

// Call-back function that runs after a paste event on the text field is completed.
// Note: This is a function, not a method!
function ProhibitedCharacterListener_afterPaste() {
	g_thisProhibitedCharacterListener.cleanTextField();
	g_thisProhibitedCharacterListener = null;
}

// Method removes illegal characters from the text field
ProhibitedCharacterListener.prototype.cleanTextField = function() {
	if (this.textField) {
		var theText = this.textField.value;
		theText = theText.replace(this.regExp,"");
		if (this.limit > 0 && theText.length > this.limit) {
			theText = theText.substring(0, this.limit);	
		}
		this.textField.value = theText;
	}
}

// Discards illegal characters as they are typed in the text input field
// Note: For security reasons, Firefox does not allow any access to the
// clipboard at all via Javascript.  Therefore we set up a "callback"
// for paste events that will remove all invalid characters AFTER
// the paste is completed.
ProhibitedCharacterListener.prototype.handleEvent = function(event) {
	if (isPasteEvent(event)) {
		g_thisProhibitedCharacterListener = this;
		setTimeout("ProhibitedCharacterListener_afterPaste();", 0);
	}
	else if (event.type == "keypress") {
		// Must test explicitly for undefined because Firefox returns event.which=0 for Home, End etc.
		var keyCode = (undefined != event.which) ? event.which : event.keyCode;	

		// Preventing tabs in FireFox breaks 508. If you explicitely send in a \t
		// in the regular expression then for FireFox the String.fromCharCode
		// will return nothing. The tab will be discarded as an invalid character
		// and keyboard navigation will be broken.
		if (isBrowserFirefox() && keyCode == 0)
		   return true; 

		var strChar = String.fromCharCode(keyCode);
		if (strChar.search(this.regExp) >= 0) {
			return cwAbsorbEvent(event);	// Discard invalid character
		}
		if (this.limit > 0 && this.textField.value.length >= this.limit) {
			// Set up to truncate if needed, AFTER the character is processed 
			// (because we don't want to discard BS, DEL, etc now).  Following test needed for fast typists:
			if (null == g_thisProhibitedCharacterListener) {	// If no callback is already pending
				g_thisProhibitedCharacterListener = this;
				setTimeout("ProhibitedCharacterListener_afterPaste();", 0);
			}
		}
	}
	return true;	// allow normal event processing
}

// Function that prevents the event being processed from being propagated
// up the document hierarchy (bubbling). Also prevents the default behavior for the event
// being processed from occurring, if it can be prevented.  Kept for backward compatibility.
// cwAbsorbEvent(event) is preferred, because it does not set the IE-proprietary returnValue property. 
function _terminateEventProcessing(eventObj)
{
    if( eventObj.preventDefault != null ) {
        eventObj.preventDefault();
    }
    eventObj.returnValue = false;
    //eventObj.cancelBubble = true;
    if( eventObj.stopPropagation != null ) {
        eventObj.stopPropagation();
    }
    return false;
}

// Stops bubbling and prevents default action (if possible) for the event.
// Note: if in doubt, this is the function you probably want to use!
function cwAbsorbEvent(eventObj)
{
	return(dojo.event.browser.stopEvent(eventObj));
}

// used to keep text fields from beeping if enter is hit in them
// don't use if enter means something else for that particular field
// g_useEnterInDataEntry is a property driven option for sites that use
// an editor like IME to enter values
function handleEnter (field, event) {
  var keyCode = event.keyCode ? event.keyCode : 
                event.which ? event.which : event.charCode;
  if (keyCode == 13 && !g_useEnterInDataEntry) { 
    return cwAbsorbEvent(event);
   }
  else {
    return true;
  }
}


//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
// _BEGIN CLASS DEF'N FOR WRSEventUtil
//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
// Only prevents default action, allows bubbling
function WRSEventUtil() {}

WRSEventUtil.preventDefaultBehavior = function(evt)
{
    if( evt.preventDefault != null )
    {   //dom method
        evt.preventDefault();
    }
    evt.returnValue = false; //ie event model
}

// Stops bubbling and prevents default action
// (Another equivalent to cwAbsorbEvent() above)
WRSEventUtil.terminateEventProcessing = function(eventObj)
{
	return(_terminateEventProcessing(eventObj));
}
//<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
// _END CLASS DEF'N FOR WRSEventUtil
//<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<

//------------------------------------------------------------------------------
// In-line code - this runs each time this file is loaded
//------------------------------------------------------------------------------

// (S0474539) Prevent right-click from bringing up browser's 
// popup menu on all WRS pages (but allow it for Ctrl+right click,
// except when over CDD components (e.g. PopupMenu) that respond to 
// right-click and ctrl+right click). This also disables popup
// menu for text edit fields, except for Ctrl+right-click:
if (undefined != sas_disableBrowserContextMenus) 
{    
    sas_disableBrowserContextMenus();
}

