/********************************/
//  Dynamic Dependant Project
/********************************/

// first, declare the two namespaces if they do not already exist

var ddp;
if(!ddp){ ddp = {};}

/********************************/
//  DDP
/********************************/

ddp.f = {};

(function(){
  var _memorize = {};
  _memorize.elData = {};
  _memorize.guidCounter = 0;
  
  var undef; //this will reliably always be undefined even if the global undefined variable is effed up by someone.

  //define DOM node type constants
  var nodeTypes = {
    ELEMENT_NODE                :  1,
    ATTRIBUTE_NODE              :  2,
    TEXT_NODE                   :  3,
    CDATA_SECTION_NODE          :  4,
    ENTITY_REFERENCE_NODE       :  5,
    ENTITY_NODE                 :  6,
    PROCESSING_INSTRUCTION_NODE :  7,
    COMMENT_NODE                :  8,
    DOCUMENT_NODE               :  9,
    DOCUMENT_TYPE_NODE          : 10,
    DOCUMENT_FRAGMENT_NODE      : 11,
    NOTATION_NODE               : 12
  };
  
  
  /***************************************************************/
  //if they don't exist, add the "forEach", "every" and "filter" methods to all Arrays using prototype
  //(these methods were added in Javascript 1.6, so IE7  and lower doesn't support them)
  if(!Array.prototype.forEach){
    Array.prototype.forEach = function(fn, thisObj){
      var scope = thisObj || window;
      for( var i=0, j=this.length; i < j; ++i ){
        fn.call(scope, this[i], i, this);
      }
    };
  }
  if(!Array.prototype.every){
    Array.prototype.every = function(fn, thisObj){
      var scope = thisObj || window;
      for( var i=0, j=this.length; i < j; ++i ){
        if(i in this && !fn.call(scope, this[i], i, this)){
          return false;
        }
      }
      return true;
    };
  }
  if(!Array.prototype.filter){
    Array.prototype.filter = function(fn, thisObj){
      var scope = thisObj || window;
      var a = [];
      for( var i=0, j=this.length; i < j; ++i ){
        if( !fn.call(scope, this[i], i, this) ){
          continue;
        }
        a.push(this[i]);
      }
      return a;
    };
  }

 
  //Replaces: document.getElementById() 
  //Takes strings and objects
  //var elements = ddp.f.$('id',objname);
  function $()
  {
    var elements = [];
  
    for(var i=0, l=arguments.length; i<l; i++){
      elements.push(isString(arguments[i]) ? document.getElementById(arguments[i]) : arguments[i]);
    }

    return (elements.length==1) ? elements[0] : elements;
  }
  
  /***************************************************************/
  //$c 
  //returns an array of references for any elements with the specified css class name
  function $c(strClassName, elStartElement, strTagName){
    var result = getElementsByClassName(strClassName, elStartElement, strTagName);
    if(isArray(result) && result.length < 2){
      if(result.length === 0){
        return null;
      }
      else if(result.length === 1){
        return result[0];
      }
    }
  
    return result;
  }
  
  
  /***************************************************************/  
  function addLoadEvent(fnFuntionToAdd){
    return addEvent(window, 'load', fnFuntionToAdd);
  }
  
  
  /***************************************************************/
  //addModule
  //dynamically adds the ddp module to the document header 
  function addModule(strFileName, dontUsePath){
    var strScriptPath = '';
    if(ddp.c.scriptPath && !dontUsePath){
      strScriptPath = ddp.c.scriptPath;
    }
      
    var elNewScript = document.createElement('script');
    elNewScript.type = 'text/javascript';
    elNewScript.src = strScriptPath + strFileName;
    //append the new script element to the <head> element
    addEvent(document, 'ready', function(){
      document.getElementsByTagName('head')[0].appendChild(elNewScript);
    });
  }
  
  
  /***************************************************************/
  //makeRealArray 
  //takes an object with 'length' property and returns an actual array
  //(useful for 'arguments' and objects returned from getElementsByTagName(), etc..)
  function makeRealArray(objObject){
    //can't get this approach to work in ie (DOM nodelists throw a "Jscript object expected" error)
    //return Array.prototype.slice.call(objObject, 0);
    //return [].slice.call(objObject, 0);
    
    var arReturn = [];
    var i=objObject.length, l=i-1;
    
    //fills in the array elements backwards...
    while(i--){
      arReturn[l-i]=objObject[l-i];
    }
    
    return arReturn;
  }
  
  
  
  /***************************************************************/
  //randomSortArray
  //randomly sorts the elements of an array
  function randomSortArray(arToSort){
    arToSort.sort(function(){return (Math.round(Math.random())-0.5);});
    return arToSort;
  }

  
  /***************************************************************/
  //isArray 
  //tests for arrays
  function isArray(varTest){
    if(varTest === null){
      return false;
    }
    if(typeof varTest == 'object' && typeof varTest.constructor == 'function' && typeof varTest.length == 'number' ){
      //return (varTest.constructor.toString().match(/function array/i) !== null);
      return !!varTest.constructor.toString().match(/function array/i);
    }

    return false;
  }
    
  
  /***************************************************************/
  //isString 
  //tests for strings and string objects
  function isString(varTest){
    if(varTest === null){
      return false;
    }
    if(typeof varTest == 'string'){
      return true;
    }
    if(typeof varTest == 'object' && typeof varTest.constructor == 'function'){
      //return (varTest.constructor.toString().match(/string/i) != null);
      return !!varTest.constructor.toString().match(/string/i);
    }
    return false;
  }
  
  
  /***************************************************************/
  //isObject
  //tests for objects which are not strings or arrays (and not null)
  function isObject(varTest){
    //it's not null, has typeof "object", isn't a string, isn't an array, and isn't a function
    return (varTest !== null && typeof varTest == 'object' && !isString(varTest) && !isArray(varTest) && !isFunction(varTest));
  }
  
  /***************************************************************/
  //isFunction
  //tests for objects which are not strings or arrays (and not null)
  function isFunction(varTest){
    if(!varTest){
      return false;
    }
    if(typeof varTest == 'function'){
      return true;
    }
    //ie6's stupid broken return value for functions across windows is "object"
    if(typeof varTest == 'object' && typeof varTest.constructor == 'object'){
      //return (varTest.constructor.toString().match(/function Function/) != null);
      return !!varTest.constructor.toString().match(/function Function/);
    }
    return false;
  }
  
  function isDate(varTest){
    return !!(varTest && varTest.constructor && varTest.constructor.toString().match(/Date/));
  }
  
  
  /***************************************************************/
  //cloneObject
  //makes a "copy" of the object passed to it. (actually creates a new object and copies the old object's properties into it)
  //optional parameter "onlyOwnProperties", if set to true, only copies properties not inherited through it's prototype 
  function cloneObject(objToClone, allProperties, blnDeepClone){
    var objNew = {};
    //iterate through the object's properties
    for(var key in objToClone){
      //if "hasOwnProperties" has been passed in as "true", then check to see if the property in question belongs to the object
      if(!allProperties){
        if(objToClone.hasOwnProperty){
          if(!objToClone.hasOwnProperty(key)){
            continue; 
          }
        }
        else{
          if(objToClone.prototype && typeof objToClone.prototype[key] != 'undefined'){
            continue;
          }
        }
      }
      if(blnDeepClone && isObject(objToClone[key])){
        objNew[key] = cloneObject(objToClone[key]);
      }
      else{
        //copy the reference or data to the new object
        objNew[key] = objToClone[key];
      }
    }
    //return the new object
    return objNew;
  }
  
  
  /***************************************************************/
  //arrayCompare
  //tests to see if two arrays are identical
  function arrayCompare(ar1, ar2){
    var i = ar1.length;
    if(i != ar2.length){
      return false;
    }
    while(i--){
      if(ar1[i] !== ar2[i]){
        return false;
      }
    }
    return true;
  }
  
 
  
  /***************************************************************/
 
  function obsEvent() 
  {
    this.fns = [];
  }
  obsEvent.prototype = 
  {
    subscribe : function(fn) 
    {
      this.fns.push(fn);
    },
    unsubscribe : function(fn) 
    {
      this.fns = this.fns.filter(
        function(el) 
        {
          if ( el !== fn ) 
          {
            return el;
          }
        }
      );
    },
    fire : function(o, thisObj) 
    {
      var scope = thisObj || window;
      this.fns.forEach(
        function(el) 
        {
          el.call(scope, o);
        }
        
      );
    }
  };


/***************************************************************/
function mousePosition(objEvent){
  //make sure we have the event object
  objEvent = objEvent || window.event;
  try{
    if(objEvent.pageX || objEvent.pageY){
      return {x:objEvent.pageX, y:objEvent.pageY};
    }
    return {
      x: objEvent.clientX + document.body.scrollLeft - document.body.clientLeft,
      y: objEvent.clientY + document.body.scrollTop  - document.body.clientTop
    };
  }
  catch(e){
    //in IE6 if this method is executed before the page fully loads, it throws an error...
    return {x:0,y:0};
  }
}

/***************************************************************/  
  
//replace anything other than a numerical digit with nothing  
function removeAlpha(torem) 
{
  return torem.replace(/\D/g,'');
}

/***************************************************************/  

//replace anything other than an ASCII word character with nothing
function removeNonAsciiWord(torem){
  return torem.replace(/\W/g,'');
}
 
/***************************************************************/   


//verify a valid phone number 
function checkValidPhoneNumber(phoneNo){
  var phoneRE = /^(\(?[0-9]{3}\)?)?\-?[0-9]{3}\-?[0-9]{4}$/;
  //return (phoneNo.match(phoneRE) == true); //change for jslint
  return (phoneNo.match(phoneRE) !== null); 
}

/***************************************************************/

//verify a valid email address where the address is not an IP address
function checkValidEmailAddress(emailAddress){
 var emailRE = /^[A-Za-z0-9_\-\.]+@(([A-Za-z0-9\-])+\.)+([A-Za-z\-])+$/;
 //return (emailAddress.match(emailRE) == true); //change for jslint
 return (emailAddress.match(emailRE) !== null);
}

//http://blog.stevenlevithan.com/archives/faster-trim-javascript
//trim12
function trimWhitespace(str){
  if(!str.replace){
    return null;
  }
  str = str.replace(/^\s\s*/, '');
  var ws = /\s/, i = str.length;
  while(ws.test(str.charAt(--i))){} //there is nothing within the braces. this is not a typo
  return str.slice(0, i + 1);
}



/***************************************************************/

function getBrowserGeometry(objBrowserWindow){
  if(!objBrowserWindow){
    objBrowserWindow = window;
  }
    
  //these won't necessarily contain valid references, but it
  //will prevent a bunch of scope chain lookups further in.
  var elDocElement = objBrowserWindow.document.documentElement;
  var elDocBody = objBrowserWindow.document.body;
  
  var windowSizeWidth = 0;
  var windowSizeHeight = 0;
  var horizontalScroll = 0;
  var verticalScroll = 0;
  var documentWidth = 0;
  var documentHeight = 0;
  
  if( typeof( objBrowserWindow.innerWidth ) == 'number' ){
    //Non-IE
    windowSizeWidth = objBrowserWindow.innerWidth;
    windowSizeHeight = objBrowserWindow.innerHeight;
    horizontalScroll = objBrowserWindow.pageXOffset;
    verticalScroll = objBrowserWindow.pageYOffset;
  }
  else if( elDocElement && ( elDocElement.clientWidth || elDocElement.clientHeight ) ){
    //IE 6+ in 'standards compliant mode'
    windowSizeWidth = elDocElement.clientWidth;
    windowSizeHeight = elDocElement.clientHeight;
    horizontalScroll = elDocElement.scrollLeft;
    verticalScroll = elDocElement.scrollTop;
  }
  else if( elDocBody && ( elDocBody.clientWidth || elDocBody.clientHeight ) ){
    //IE 4 compatible
    windowSizeWidth = elDocBody.clientWidth;
    windowSizeHeight = elDocBody.clientHeight;
    horizontalScroll = elDocBody.scrollLeft;
    verticalScroll = elDocBody.scrollTop;
  }
  
  if( objBrowserWindow.innerHeight && objBrowserWindow.scrollMaxY ) // Firefox 
  {
    documentWidth = objBrowserWindow.innerWidth + objBrowserWindow.scrollMaxX;
    documentHeight = objBrowserWindow.innerHeight + objBrowserWindow.scrollMaxY;
  }
  else if( elDocBody && elDocBody.scrollHeight > elDocBody.offsetHeight ) // all but Explorer Mac
  {
    documentWidth = elDocBody.scrollWidth;
    documentHeight = elDocBody.scrollHeight;
  }
  else if( elDocBody )// works in Explorer 6 Strict, Mozilla (not FF) and Safari
  { 
    documentWidth = elDocBody.offsetWidth + elDocBody.offsetLeft; 
    documentHeight = elDocBody.offsetHeight + elDocBody.offsetTop; 
  }
  
  windowSizeWidth = parseInt(windowSizeWidth, 10);
  windowSizeHeight = parseInt(windowSizeHeight, 10);
  horizontalScroll = parseInt(horizontalScroll, 10);
  verticalScroll = parseInt(verticalScroll, 10);
  documentWidth = parseInt(documentWidth, 10);
  documentHeight = parseInt(documentHeight, 10);
  
  if(isNaN(windowSizeWidth))  { windowSizeWidth = null; }
  if(isNaN(windowSizeHeight)) { windowSizeHeight = null; }
  if(isNaN(horizontalScroll)) { horizontalScroll = null; }
  if(isNaN(verticalScroll))   { verticalScroll = null; }
  if(isNaN(documentWidth))    { documentWidth = null; }
  if(isNaN(documentHeight))   { documentHeight = null; }
  
  //return an object 
  return {
    "viewportWidth"   : windowSizeWidth,
    "viewportHeight"  : windowSizeHeight,
    "horizontalScroll": horizontalScroll,
    "verticalScroll"  : verticalScroll,
    "documentWidth"   : documentWidth,
    "documentHeight"  : documentHeight
  };
}


/***************************************************************/
function returnMaxZindex()
{
  var allElems = document.getElementsByTagName ? document.getElementsByTagName("*") : document.all; // or test for that too
  var maxZIndex = 0;
  var i = allElems.length;
  var sNum, maxEl;
  while(i--){
    //gh - 31-dec-2010.
    //sNum = Number(getEffectiveStyle(allElems[i], 'z-index'));
    //the "+" here is the unary plus operator which will do implicit type conversion to a number
    sNum = +(getEffectiveStyle(allElems[i], 'z-index'));
    if(!isNaN(sNum) && sNum > maxZIndex){
      maxZIndex = sNum;
      //maxEl = allElems[i]; //if we wanted to return the element which corresponds to the maxZindex
    }
  }
  return maxZIndex;
}


/***************************************************************/
//detects what DOCTYPE this document uses. returns an object.
//accessable properties are:
//detectDoctype.xhtml - XHTML, HTML
//detectDoctype.version - 1.1, 4.0 
//detectDoctype.importance - Strict, Transitional
function detectDoctype(){
  var re=/\s+(X?HTML)\s+([\d\.]+)\s*([^\/]+)*\//gi;
  var result = false;
  if(typeof document.namespaces != "undefined"){
    result = document.all[0].nodeType==8 ? re.test(document.all[0].nodeValue) : false;
  }
  else{
    result = document.doctype !== null ? re.test(document.doctype.publicId) : false;
  }
  if(result){
    return {'xhtml': RegExp.$1.toUpperCase(), 'version': RegExp.$2.toUpperCase(), 'importance': RegExp.$3.toUpperCase()};
  }
  return null;
}



//browser detection class
//accessable properties are:
//BrowserDetect.browser - Explorer, Mozilla, Chrome, etc.
//BrowserDetect.version - 1, 2, 3, 4, etc.
//BrowserDetect.OS - Windows, Linux, Mac, iPhone, etc.
function BrowserDetect(){
  this.init = function () {
    this.browser = this.searchString(this.dataBrowser) || "An unknown browser";
    
    this.version = this.searchVersion(navigator.userAgent) ||
                   this.searchVersion(navigator.appVersion) ||
                   "an unknown version";
                   
    this.OS = this.searchString(this.dataOS) || "an unknown OS";
  };
  
  this.searchString = function (data){
    for(var i=0, l=data.length;i<l;i++){
      var dataString = data[i].string;
      var dataProp = data[i].prop;
      this.versionSearchString = data[i].versionSearch || data[i].identity;
      if(dataString){
        if(dataString.indexOf(data[i].subString) != -1){
          return data[i].identity;
        }
      }
      else if(dataProp){
        return data[i].identity;
      }
    }
  };
  
  this.searchVersion = function(dataString){
    var index = dataString.indexOf(this.versionSearchString);
    if(index == -1){ return; }
    return parseFloat(dataString.substring(index+this.versionSearchString.length+1));
  };
  
  this.dataBrowser = [
    {
      string: navigator.userAgent,
      subString: "Chrome",
      identity: "Chrome"
    },
    {
      string: navigator.userAgent,
      subString: "OmniWeb",
      versionSearch: "OmniWeb/",
      identity: "OmniWeb"
    },
    {
      string: navigator.vendor,
      subString: "Apple",
      identity: "Safari",
      versionSearch: "Version"
    },
    {
      prop: window.opera,
      identity: "Opera"
    },
    {
      string: navigator.vendor,
      subString: "iCab",
      identity: "iCab"
    },
    {
      string: navigator.vendor,
      subString: "KDE",
      identity: "Konqueror"
    },
    {
      string: navigator.userAgent,
      subString: "Firefox",
      identity: "Firefox"
    },
    {
      string: navigator.vendor,
      subString: "Camino",
      identity: "Camino"
    },
    {  // for newer Netscapes (6+)
      string: navigator.userAgent,
      subString: "Netscape",
      identity: "Netscape"
    },
    {
      string: navigator.userAgent,
      subString: "MSIE",
      identity: "Explorer",
      versionSearch: "MSIE"
    },
    {
      string: navigator.userAgent,
      subString: "Gecko",
      identity: "Mozilla",
      versionSearch: "rv"
    },
    {   // for older Netscapes (4-)
      string: navigator.userAgent,
      subString: "Mozilla",
      identity: "Netscape",
      versionSearch: "Mozilla"
    }
  ];
  
  this.dataOS = [
    {
      string: navigator.platform,
      subString: "Win",
      identity: "Windows"
    },
    {
      string: navigator.platform,
      subString: "Mac",
      identity: "Mac"
    },
    {
      string: navigator.userAgent,
      subString: "iPhone",
      identity: "iPhone/iPod"
    },
    {
      string: navigator.platform,
      subString: "Linux",
      identity: "Linux"
    }
  ];

  this.init();
}




/***************************************************************/
function isIE(){
  //detect IE
  if(new BrowserDetect().browser == 'Explorer'){
    //re-write the function (internal and external) so any subsequent calls just return true
    ddp.f.isIE = isIE = function(){ return true; };
    return true;
  }
  else{
    //re-write the function (internal and external) so any subsequent calls just return false
    ddp.f.isIE = isIE = function(){ return false; };
    return false;
  }
}



/***************************************************************/
//setOneTimeEvent
//flags an event on an object as a "one time event"
function setOneTimeEvent(elEventTarget, strEventType){
  //set a flag so that we can identify this element+event as a "one time"
  elEventTarget['ddpOneTimeEvent_' + strEventType] = true;
  //mark it as "not yet fired"
  elEventTarget['ddpOneTimeEvent_' + strEventType + '_fired'] = false;
  //add an handler that marks the object+event as fired and preserves the event object
  return addEvent(elEventTarget, strEventType, function(objEvent){
    elEventTarget['ddpOneTimeEvent_' + strEventType + '_fired'] = new Date().getTime();
    //elEventTarget['ddpOneTimeEvent_' + strEventType + '_eventObject'] = objEvent;
  });
}

//this object contains the events emulated by DDP for browsers that don't support them
var ddpEmulatedEvents = {
  //emulate the IE mouseeenter event for FF/Opera
  "mouseenter":{
    "attachToEvent": "mouseover",
    "fn": function(objEvent){
      //if "relatedTarget"(FF) or "fromElement"(Opera) is not a child of the original event target (this)
      if(!isChild(objEvent.relatedTarget || objEvent.fromElement, this)){
        //clone the browser generated event object (this preserves mouse coordinates, originating time, etc..)
        var obj = cloneObject(objEvent, true);
        //set the bubbles property to false (when passed to the fireEvent function, this will stop the fired event from bubbling)
        //(which we want because we're trying to mimic the IE 'mouseenter' behaviour)
        obj.bubbles = false;
        //fire the mouseleave event manually (using the cloned event object as a parameter)
        fireEvent(this, 'mouseenter', obj);
      }
    }
  },
  
  //emulate the IE mouseeleave event for FF/Opera
  "mouseleave":{
    "attachToEvent": "mouseout",
    "fn": function(objEvent){
      //if "relatedTarget"(FF) or "toElement"(Opera) is not a child of the original event target (this) AND "relatedTarget" is not the original target
      if(!isChild(objEvent.relatedTarget || objEvent.toElement, this) && objEvent.relatedTarget != this){
        //clone the browser generated event object (this preserves mouse coordinates, originating time, etc..)
        var obj = cloneObject(objEvent, true);
        //set the bubbles property to false (when passed to the fireEvent function, this will stop the fired event from bubbling)
        //(which we want because we're trying to mimic the IE 'mouseleave' behaviour)
        obj.bubbles = false;
        //fire the mouseleave event manually (using the cloned event object as a parameter)
        fireEvent(this, 'mouseleave', obj);
      }
    }
  },
  
  //emulate the mozilla/w3c event DOMMouseScroll on ie and opera
  "DOMMouseScroll":{
    "attachToEvent": "mousewheel",
    "fn": function(objEvent){
      //clone the browser generated event object (this preserves mouse coordinates, originating time, etc..)
      //var obj = cloneObject(objEvent, true);
      if(!objEvent.detail){
        //set a default value
        objEvent.detail = 0;
        // IE/Opera
        if(event.wheelDelta){
          //wheelDelta gives us 120 or -120. this calc gives us 3 or -3 to bring the value in sync with the mozilla/w3c value.
          objEvent.detail = objEvent.wheelDelta/120*3;
          //if it's IE (i realize saying "not opera" isn't the same as "is IE", but for all intents and purposes this will have to do.)
          if(!window.opera){
            objEvent.detail = -objEvent.detail;
          }
        }
      }
      //fire the event so that it looks like it came from the event type we're emulating
      fireEvent(this, 'DOMMouseScroll', objEvent, false, true);
      //return the returnValue (in case IE wants to prevent the default action)
      return objEvent.returnValue;
    }
  },
  
  //emulate a new type of event which is fired when the enter key is pressed on an element (e.g. <input>)
  "enterkeypress":{
    "attachToEvent": "keypress",
    "fn": function(objEvent){
      //
      if(ddp.f.getEventKey(objEvent) == 13){
        //clone the browser generated event object (this preserves mouse coordinates, originating time, etc..)
        var obj = cloneObject(objEvent, true);
        obj.bubbles = false;
        //fire the enterkey event manually (using the cloned event object as a parameter)
        //Also, the 5th argument tells the function to update the event parameter object "obj" with the
        //returnValue and cancelBubble results from the fired event (so we can pass it on to the original event object)
        fireEvent(this, 'enterkeypress', obj, false, true);
        if(obj.returnValue === false){
          preventEventDefault(objEvent);
        }
        if(obj.cancelBubble){
          stopEventPropagation(objEvent);
        }
        return obj.returnValue;
      }
    }
  }
};

/***************************************************************/
//allow for putting user specified emulated events into the emulated events object 
function addEmulatedEvent(eventToEmulate, attachToEvent, emulatorFunction){
  if(!ddpEmulatedEvents[eventToEmulate]){
    ddpEmulatedEvents[eventToEmulate] = {
      "attachToEvent": attachToEvent,
      "fn": emulatorFunction
    };
    return true;
  }
  return false;
}


/***************************************************************/
function addEvent(elEventTarget, strEventType, fnEventListener, forceAddOTE){
  var arResults, i, l;
  
  //if strEventType is a space delimited string of multiple event types,
  if(strEventType.indexOf && strEventType.indexOf(' ') > -1){
    strEventType = strEventType.split(' '); //convert it to an array
  }
  
  //an array of event targets can be passed and each target element will have the event type handler added to it
  if(isArray(elEventTarget)){
    arResults = [];
    l=elEventTarget.length;
    for(i=0; i<l; i++){
      arResults.push(addEvent(elEventTarget[i],  strEventType, fnEventListener, forceAddOTE));
    }
    return arResults;
  }
  //an array of event types can be passed and each event type on the target element will have the handler added to it
  if(isArray(strEventType)){
    arResults = [];
    l=strEventType.length;
    for(i=0; i<l; i++){
      arResults.push(addEvent(elEventTarget, strEventType[i], fnEventListener, forceAddOTE));
    }
    return arResults;
  }
  
  //check that the arguments were passed with the proper types
  if(typeof elEventTarget != 'object' || elEventTarget === null){
    throw new Error('ddp.f.addEvent: Parameter "elEventTarget" was not an object');
  }
  if(!isString(strEventType)){
    throw new Error('ddp.f.addEvent: Parameter "strEventType" was not a string');
  }
  if(!isFunction(fnEventListener)){
    throw new Error('ddp.f.addEvent: Parameter "fnEventListener" was not a function');
  }

  //emulate events that are in "ddpEmulatedEvents" if the browser doesn't support them
  if(ddpEmulatedEvents[strEventType] && !isEventSupported(strEventType, elEventTarget) && !elEventTarget['ddpEmulated_' + strEventType]){
    //mark the element as having the emulation set up (so we don't try to do it again)
    elEventTarget['ddpEmulated_' + strEventType] = true;
    //add the handler that performs the emulation to the event that can be used to trigger the emulation
    addEvent(elEventTarget, ddpEmulatedEvents[strEventType].attachToEvent, ddpEmulatedEvents[strEventType].fn);
  }
    
  //if this is a one-time event (such as window 'load'), and it has already been fired,  
  if(elEventTarget['ddpOneTimeEvent_' + strEventType] && elEventTarget['ddpOneTimeEvent_' + strEventType + '_fired'] && !forceAddOTE){
    //then execute the event listener right away (using the event target as "this" and passing a new event object to it).
    //return fnEventListener.call(elEventTarget, _createEventObject(elEventTarget, strEventType));
    addEvent(elEventTarget, strEventType, fnEventListener, true);
    //return fnEventListener.call(elEventTarget, elEventTarget['ddpOneTimeEvent_' + strEventType + '_eventObject']);
    return fnEventListener.call(elEventTarget);
  }
  //w3c standard browsers
  else if(elEventTarget.addEventListener){
    //add the event listener. specify false for the last argument to specify the bubbling phase (to mirror IE's limitation to only bubble)
    elEventTarget.addEventListener(strEventType, fnEventListener, false);
    return 1;
  }
  // Internet Explorer (but use old school approach for resize and scroll)
  else if(elEventTarget.attachEvent &&
          typeof elEventTarget['on' + strEventType] != 'undefined' &&
          (typeof elEventTarget.ddpEvents == 'undefined' || typeof elEventTarget.ddpEvents['AERM_arEventListeners_' + strEventType] == 'undefined') &&
          strEventType!='resize' &&
          strEventType!='scroll'){
    //grab the array stored in the element (if it exists) 
    var arListeners = elEventTarget['arEventListeners_' + strEventType];
    
    //the function that will be called when the actual event is fired.
    var fireEvents = function(evt){
      //make sure the event object has been passed properly
      var objEvent = (typeof evt == 'object') ? evt : window.event;
      //cycle through the listeners in the order they were added 
      for(var i=0, l=arListeners.length; i<l; i++){
        //execute the listeners with "call" so that their "this" will be the element
        arListeners[i].call(elEventTarget, objEvent);
      }
      //for IE, return the returnValue
      return objEvent.returnValue === false ? false : true;
    };
    
    if(typeof arListeners != 'object'){  //if the array of listeners is not already created,
      //create a new array within the target element
      arListeners = elEventTarget['arEventListeners_' + strEventType] = [];  
      //and attach the handler that fires all the events of this type
      elEventTarget.attachEvent('on' + strEventType, fireEvents);
      //add a listener on window unload event that removes fireEvents() and empties the array of added listeners (older versions of IE have issues with memory leaks)  
      window.attachEvent('onunload', function(){ elEventTarget['arEventListeners_' + strEventType] = null; elEventTarget.detachEvent('on' + strEventType, fireEvents); });
    }
    //add the new function to the array of functions to be fired 
    arListeners.push(fnEventListener);
    return 2;
  }
  else{ //if all else fails, use the old school approach
    return AERM_addListener(elEventTarget, strEventType, fnEventListener) ? 3 : 0;
  }
}


/***************************************************************/
function removeEvent(elEventTarget, strEventType, fnEventListener){
  var arResults, i, l;
  //if strEventType is a space delimited string of multiple event types,
  if(strEventType.indexOf && strEventType.indexOf(' ') > -1){
    strEventType = strEventType.split(' '); //convert it to an array
  }
  
  //an array of event targets can be passed and each target element will have the event type handler added to it
  if(isArray(elEventTarget)){
    arResults = [];
    l=elEventTarget.length;
    for(i=0; i<l; i++){
      arResults.push(removeEvent(elEventTarget[i],  strEventType, fnEventListener));
    }
    return arResults;
  }
  //an array of event types can be passed and each event type on the target element will have the handler added to it
  if(isArray(strEventType)){
    arResults = [];
    l=strEventType.length;
    for(i=0; i<l; i++){
      arResults.push(removeEvent(elEventTarget, strEventType[i], fnEventListener));
    }
    return arResults;
  }
      
  //check that the arguments were passed with the proper types
  if(typeof elEventTarget != 'object' || elEventTarget === null){
    throw new Error('ddp.f.removeEvent: Parameter "elEventTarget" was not an object');
  }
  if(!isString(strEventType)){
    throw new Error('ddp.f.removeEvent: Parameter "strEventType" was not a string');
  }
  if(!isFunction(fnEventListener)){
    throw new Error('ddp.f.removeEvent: Parameter "fnEventListener" was not a function');
  }
    
  //w3c compliant browsers
  if(elEventTarget.removeEventListener){
    elEventTarget.removeEventListener(strEventType, fnEventListener, false);
    return 1;
  }
  // Internet Explorer (but use old school approach for resize and scroll)
  else if(elEventTarget.detachEvent && (!elEventTarget.ddpEvents || typeof elEventTarget.ddpEvents['AERM_arEventListeners_' + strEventType] == 'undefined')){
    //grab the array stored in the element (if it exists)
    var arListeners = elEventTarget['arEventListeners_' + strEventType];
    
    if(typeof arListeners == 'object'){
      for(i=arListeners.length-1; i>=0; i++){
        if(arListeners[i] == fnEventListener){ //if the listener is in the array
          arListeners.splice(i,1); //splice the listener out
          return 2;  //return true on success
        }
      }
    }
    return 0;
  }
  else{ //if all else fails, use the old school approach
    return AERM_removeListener(elEventTarget, strEventType, fnEventListener) ? 3 : 0;
  }
}



/***************************************************************/
function fireEvent(elEventTarget, strEventType, objEventProperties, blnManualBubble, modifyObjEventProperties){
  var arResults, i, l;
  //if strEventType is a space delimited string of multiple event types,
  if(strEventType.indexOf && strEventType.indexOf(' ') > -1){
    strEventType = strEventType.split(' '); //convert it to an array
  }
  
  //the default value is undefined
  var returnValue = undefined;

  //an array of event targets can be passed and each target element will have the event type handler added to it
  if(isArray(elEventTarget)){
    arResults = [];
    l=elEventTarget.length;
    for(i=0; i<l; i++){
      arResults.push(fireEvent(elEventTarget[i],  strEventType, objEventProperties, blnManualBubble));
    }
    return arResults;
  }
  //an array of event types can be passed and each event type on the target element will have the handler added to it
  if(isArray(strEventType)){
    arResults = [];
    l=strEventType.length;
    for(i=0; i<l; i++){
      arResults.push(fireEvent(elEventTarget, strEventType[i], objEventProperties, blnManualBubble));
    }
    return arResults;
  }

  //check that the arguments were passed with the proper types
  if(typeof elEventTarget != 'object' || elEventTarget === null){
    throw new Error('ddp.f.fireEvent: Parameter "elEventTarget" was not an object');
  }
  if(!isString(strEventType)){
    throw new Error('ddp.f.fireEvent: Parameter "strEventType" was not a string');
  }
  
  //will hold our new event object
  var objEvent = _createEventObject(elEventTarget, strEventType, objEventProperties);

  //if the event object wasn't created successfuly
  if(!objEvent){
    return false; //return -1 for failure
  }
  


  //now go and fire the event with our new event object
  //if elEventTarget uses the AlternateEventRegistrationModel
  if(elEventTarget.ddpEvents && typeof elEventTarget.ddpEvents['AERM_arEventListeners_' + strEventType] != 'undefined'){
    returnValue = AERM_fireListener(elEventTarget, strEventType, objEvent, blnManualBubble) ? 3 : false;  //fire the event using the AERM
  }
  //w3c compliant browsers
  else{
    try{
      if(elEventTarget.dispatchEvent){
        returnValue = elEventTarget.dispatchEvent(objEvent) ? 2 : false;  //fire the event
      }
      //for IE
      else if(elEventTarget.fireEvent){
        returnValue = elEventTarget.fireEvent('on' + strEventType, objEvent) ? 1 : false; //fire the event
      }
    }
    catch(e){
      //if the event causes an error and fails to fire, default back to the AERM method
      returnValue = AERM_fireListener(elEventTarget, strEventType, objEvent, blnManualBubble) ? 3 : false;  //fire the event using the AERM
    }
  }
  
  if(modifyObjEventProperties){
    if(objEvent.returnValue===false){
      ddp.f.preventEventDefault(objEventProperties);
    }
    if(objEvent.cancelBubble){
      ddp.f.stopEventPropagation(objEventProperties);
    }
  }
  
  return returnValue;
}


/***************************************************************/
//Modified from an original posted April 1st, 2009 by kangax
//additional functionality from diego perini
//http://perfectionkills.com/detecting-event-support-without-browser-sniffing/
var EVENT_TAGNAMES = {'select':'input', 'change':'input', 'submit':'form', 'reset':'form', 'error':'img', 'load':'img', 'abort':'img'};
function isEventSupported(eventName, el){
  var undef;
  //plain objects should return false;
  if(typeof el == 'object' && !el.nodeType && el != window){
    return false;
  }
    
  //if an element wasn't passed in, create an appropriate one for the event type
  el = el || document.createElement(EVENT_TAGNAMES[eventName] || "div");
  
  //check if the event name exists as a property on the element or if the event type is enumerated in the window.Event object
  var isSupported = "on" + eventName in el ||
      "on" + eventName.toLowerCase() in el ||
      top.Event &&
      typeof top.Event == "object" &&
      eventName.toUpperCase() in top.Event;

  if(!isSupported){
    eventName = "on" + eventName;
    if(!el.setAttribute && (el == window || el == document)){
      el = document.createElement("div");
    }
    if(el.setAttribute && el.removeAttribute){
      el.setAttribute(eventName, "");
      isSupported = typeof el[eventName] == "function";
      if(typeof el[eventName] != "undefined"){
        try{
          //el[eventName] = void(0); //stops JSLint from parsing any further
          el[eventName] = undef;
        }
        catch(e){}
      }
      el.removeAttribute(eventName);
    }
  }
  el = null;
  return !!isSupported; //!! ensures "undefined" is not passed back 
} 



/***************************************************************/
function _getEventModule(strEventType){
  var arEventModule = ['HTMLEvents', 'UIEvents', 'MouseEvents', 'MutationEvents'];
  var objEventTypes = {
    //HTMLEvents
    'abort': 0,
    'blur': 0,
    'change': 0,
    'error': 0,
    'focus': 0,
    'load': 0,
    'reset': 0,
    'resize': 0,
    'scroll': 0,
    'select': 0,
    'submit': 0,
    'unload': 0,
    
    //UIEvents
    'DOMActivate': 1,
    'DOMFocusIn': 1,
    'DOMFocusOut': 1,
    'keydown': 1,
    'keypress': 1,
    'keyup': 1,
    
    //MouseEvents
    'click': 2,
    'dblclick': 2,
    'mousedown': 2,
    'mousemove': 2,
    'mouseout': 2,
    'mouseover': 2,
    'mouseup': 2,
    
    //MutationEvents
    'DOMAttrModified': 3,
    'DOMNodeInserted': 3,
    'DOMNodeRemoved': 3,
    'DOMCharacterDataModified': 3,
    'DOMNodeInsertedIntoDocument': 3,
    'DOMNodeRemovedFromDocument': 3,
    'DOMSubtreeModified': 3
  };

  return (objEventTypes[strEventType]) ? arEventModule[objEventTypes[strEventType]] : arEventModule[0];
}


/***************************************************************/
//internal function for creating an event object, cross-browser wise
function _createEventObject(elEventTarget, strEventType, objEventProperties){
  //will hold our new event object
  var objEvent = null;
  //if the objEventProperties object was passed and contains a "cancelable" property which is false, then be false, otherwise true. 
  var blnCancelable = !(objEventProperties && objEventProperties.cancelable===false);
  var blnBubbles = !(objEventProperties && objEventProperties.bubbles===false);
  
  //IE
  if(document.createEventObject){
    objEvent = document.createEventObject();  //create an event object
    objEvent.type = strEventType; //set the proper event type
    objEvent.cancelable = blnCancelable; //set the cancelable property of the event object
  }
  //w3c standard browsers
  else{
    objEvent = document.createEvent(_getEventModule(strEventType)); //create an event object using the proper event module (for FF anyway..)
    objEvent.initEvent(strEventType, blnBubbles, blnCancelable); // arguments are type, bubbling, cancelable respectively
  }
  
  //if the event object was not created successfuly
  if(!objEvent){
    throw new Error('_createEventObject (in ddp.js): Event object could not be created. Incompatible browser?');
  }
  
  //if there was an object passed with properties that should be set on the event object
  if(typeof objEventProperties == 'object'){
    //iterate through the obejct's properties (no particular order)
    for(var key in objEventProperties){
      if(key != 'type' && key != 'cancelable'){
        try{
          objEvent[key] = objEventProperties[key]; //set the event object's property from the passed object's
        }
        catch(e){}
      }
    }
  }
  
  if(elEventTarget){
    objEvent.sourceElement = elEventTarget;
    try{
      // non-writeable in ff
      objEvent.target = elEventTarget;
      objEvent.originalTarget = elEventTarget;
    }
    catch(e){}
  }

  return objEvent;
}


/***************************************************************/
//stopEventPropagation
//stops propagation of an event to the next event handler
//----Arguments----
//objEvent: object  -the event object to be stopped
//cancelBubble is supported by IE - this will kill the bubbling process.
function stopEventPropagation(objEvent){
  if(objEvent.stopPropagation){
    objEvent.stopPropagation();
  }
  objEvent.cancelBubble = true;
}


/***************************************************************/
//preventEventDefault
//prevents the default action of an event
//----Arguments----
//objEvent: object  -the event object to be affected
function preventEventDefault(objEvent){
  if(objEvent.preventDefault){
    objEvent.preventDefault();
  }
  objEvent.returnValue = false;
}


/***************************************************************/
//cancelEvent
//cancels propagation AND the default action of an event
//----Arguments----
//objEvent: object  -the event object to be canceled
function cancelEvent(objEvent){
  stopEventPropagation(objEvent);
  preventEventDefault(objEvent);
}


/***************************************************************/
//getEventTarget
//returns the element that fired the event
//(cross-browser compatible) 
function getEventTarget(objEvent){
  var elTarget = null;
  
  if(objEvent){
    if(objEvent.sourceElement){ //must be first check as we use this property in AERM events
      elTarget = objEvent.sourceElement;
    }
    else if(objEvent.srcElement){
      elTarget = objEvent.srcElement;
    }
    else if(objEvent.target){
      elTarget = objEvent.target;
    }
    
    if(elTarget && elTarget.nodeType == 3 && elTarget.parentNode){ //safari bug
      elTarget = elTarget.parentNode;
    }
  }

  return elTarget;
}


/***************************************************************/
//getEventKey
//tries to return the character code of the key that was pressed to fire the event (cross-browser compatible)
//VERY UNRELIABLE! avoid if at all possible!
//if you have to, only use on 'onkeypress' event (the order in which
//the properties are checked _should_ result in proper char codes)
function getEventKey(objEvent){
  return objEvent.charCode || objEvent.which || objEvent.keyCode || false; // gh - 31-dec-2010
  /*
  if(objEvent.charCode)
    return objEvent.charCode;
  else if(objEvent.which)
    return objEvent.which;
  else if(objEvent.keyCode)
    return objEvent.keyCode;
  return false;
  */
}


/***************************************************************/
//Alternate Event Registration Model - Add an event listener
//HAS NOT BEEN EXPORTED TO DDP NAMESPACE
function AERM_addListener(elEventTarget, strEventType, fnEventListener){
  //make sure all the arguments are of the proper type
  if(typeof elEventTarget == 'object' && elEventTarget !== null && isString(strEventType) && typeof fnEventListener == 'function'){
    if(!elEventTarget.ddpEvents){
      try{
        elEventTarget.ddpEvents = {};
      }
      catch(e){
        //if trying to add an object property failed,
        //we're dealing with something special (activeXObject maybe)
        //resort to trying to assign the handler directly
        elEventTarget['on' + strEventType] = fnEventListener;
        return 2;
      }
    }
      
    var arListeners = elEventTarget.ddpEvents['AERM_arEventListeners_' + strEventType];
    
    if(typeof arListeners != 'object')  //if the array of listeners is not already created,
      arListeners = elEventTarget.ddpEvents['AERM_arEventListeners_' + strEventType] = [];  //create a new array
    
    //the function that will be called when the actual event is fired.
    var fireEvents = function(objEvent){
      if(typeof objEvent == 'undefined' && event)
        objEvent = event;
      //take a copy of the handlers that were effective at the time the event was fired
      var tmpListeners = ddp.f.makeRealArray(arListeners);
      for(var j=0, l=tmpListeners.length; j<l; j++){
        //execute the listeners in the order they were added
        tmpListeners[j].call(elEventTarget, objEvent);
      }
      return objEvent.returnValue === false ? false : true;
    };
    
    if(typeof elEventTarget['on' + strEventType] != 'function')
      elEventTarget['on' + strEventType] = fireEvents;

    //compare the string value of the methods because every "fireEvents" method exists in it's own namespace
    else if(elEventTarget['on' + strEventType].toString() != fireEvents.toString()){
      var fnOldStyleFunction = elEventTarget['on' + strEventType];
      var blnAlreadyInArray = false;
      for(var i=0, l=arListeners.length; i<l; i++){    //look throught the listeners already attached 
        if(arListeners[i].toString() == fnOldStyleFunction.toString())  //and if the old style one we're about to add is already added,
          blnAlreadyInArray = true;                //don't add it again.
      }
      if(!blnAlreadyInArray)
        arListeners[arListeners.length] = fnOldStyleFunction;
      elEventTarget['on' + strEventType] = fireEvents;
    }
    
    for(var ii=0, ll=arListeners.length; ii<ll; ii++){  //look throught the listeners already attached 
      if(arListeners[ii] === fnEventListener){ //and if the one we're about to add is already added,
        return false;                         //don't add it again.
      }
    }
    
    arListeners[arListeners.length] = fnEventListener; //add the new listener to the array.
    return true;  //return true on success
  }
  
  return false;  //return false on failure
}


/***************************************************************/
//Alternate Event Registration Model - Remove an event listener
//HAS NOT BEEN EXPORTED TO DDP NAMESPACE 
function AERM_removeListener(elEventTarget, strEventType, fnEventListener){
  //make sure all the argumants are of the proper type
  if(typeof elEventTarget == 'object' && elEventTarget !== null && isString(strEventType) && typeof fnEventListener == 'function'){
    var arListeners = (elEventTarget.ddpEvents) ? elEventTarget.ddpEvents['AERM_arEventListeners_' + strEventType] : undefined;
  
    if(typeof arListeners == 'object'){
      for(var i=0, l=arListeners.length; i<l; i++){
        if(arListeners[i] == fnEventListener){
          arListeners.splice(i,1); //splice the listener out of the array 
          return true;  //return true on success
        }
      }
    }
  }
  return false;  //return false on failure
}


/***************************************************************/
//Alternate Event Registration Model - Fire an event listener
//HAS NOT BEEN EXPORTED TO DDP NAMESPACE
function AERM_fireListener(elEventTarget, strEventType, objEvent, blnManualBubble){
  //make sure the arguments are all of the appropriate type
  if(typeof elEventTarget['on' + strEventType] == 'function' && typeof objEvent == 'object' && objEvent !== null){
    //fire the event
    var returnValue = elEventTarget['on' + strEventType](objEvent);
    //start bubbling the event up to the parent node (stop at before any pre-defined "one-time" events)
    if(blnManualBubble && objEvent.cancelBubble !== false && elEventTarget.parentNode && typeof elEventTarget.parentNode['ddpOneTimeEvent_' + strEventType] == 'undefined'){
      returnValue = AERM_fireListener(elEventTarget.parentNode, strEventType, objEvent, true);
    }
    return returnValue;
  }
  return; //if there were no events to fire return undefined;
}

//private method. not exported to the public namespace
function _getCSSClass(el){
  //old school method, doesn't work in ie8 (technically that's good as it's not standards compliant)
  //return el.className;
  if(!el || !el.getAttribute){ //if an element wasn't passed, or it doesn't have a getAttribute method
    return null; //return null...
  }
    
  //if classname is null, use class. if class is null as well, return an empty string
  return el.getAttribute('className') || el.getAttribute('class') || '';
}

//private method. not exported to the public namespace
function _setCSSClass(el, strClassName){
  //old school method, doesn't work in ie8 (technically that's good as it's not standards compliant)
  //el.className = strClassName;
  
  //check that the element isn't null and has the setAttribute() method
  if(el && el.setAttribute){
    return el.setAttribute('className', strClassName) || el.setAttribute('class', strClassName);
  }
  return false;
}


/***************************************************************/
//addClass
//adds a CSS class to the element's className attribute (if it's not already there)
//accepts a reference to an element and a string containing the class name
function addClass(el, strClassToAdd){
  //if the element passed is valid and the class isn't already there
  if(el && !hasClass(el, strClassToAdd)){
    var strClassName = _getCSSClass(el);
    if(strClassName===null){
      strClassName = '';
    }
    _setCSSClass(el, strClassName.length ? strClassToAdd + ' ' + strClassName : strClassToAdd);
    
    //add the class to the beginning of the string or just assign it, straight up
    //el.className = el.className.length ? strClassToAdd + ' ' + el.className : strClassToAdd;
  }
}


/***************************************************************/
//removeClass
//removes a CSS class from the element's className attribute
//accepts a reference to an element and a string containing the class name
function removeClass(el, classToRemove){
  var strClass = _getCSSClass(el);
  if(el && strClass){
    //classToRemove = ddp.f.trimWhitespace(classToRemove);
    var re = getRegExp('removeClass_re1' + classToRemove);
    if(!re){
      re = addRegExp('removeClass_re1' + classToRemove, new RegExp('\\b' + classToRemove + '\\b', 'g'));
    }
    //var re = new RegExp('\\b' + classToRemove + '\\b', 'g');
    //el.className = trimWhitespace(el.className.replace(re, ''));  //remove the css class
    //_setCSSClass(el, trimWhitespace(strClass.replace(re, '')));  //remove the css class
    _setCSSClass(el, strClass.replace(re, ''));  //remove the css class
  }
}


/*-----------------
Regular Expression Caching
have yet to determine how much, if any, of a perfomance difference this makes
need to do some benchmarking. 
-----------------*/
var regExpCache = [];
regExpCache.itemsByName = {};

ddp.f.ref = regExpCache;

function getRegExp(reName){
  if(regExpCache.itemsByName[reName]){
    if(regExpCache.length > 1){
      var i = getRegExpCacheIndex(reName);
      if(i<regExpCache.length){
        var tmp = regExpCache[i+1];
        regExpCache[i+1] = regExpCache[i];
        regExpCache[i] = tmp;
        tmp = null;
      }
    }
    return regExpCache.itemsByName[reName];
  }
  return null;
}

function addRegExp(reName, objRE){
  //if it exists (or one by the same name exists)
  if(regExpCache.itemsByName[reName]){
    //if it's exactly the same object
    if(regExpCache.itemsByName[reName] == objRE){
      return objRE;
    }
    else{
      regExpCache.splice(getRegExpCacheIndex(reName),1);
    }
  }
  
  if(regExpCache.length > 100){
    delete regExpCache.itemsByName[regExpCache.shift()];
  }

  regExpCache.push(reName);
  return (regExpCache.itemsByName[reName] = objRE);
}

function getRegExpCacheIndex(reName){
  var i = regExpCache.length;
  while(--i){
    if(regExpCache[i] == reName)
      return i;
  }
  return null;
}

/***************************************************************/
//hasClass
//accepts a reference to an element and string containing the class name to search for.
//returns true if the element has the specified class name, otherwise returns false.
function hasClass(el, strClassToCheckFor){
  //if(el && el.className){
  var strClass = _getCSSClass(el);
  if(el && strClass){
    //strClassToCheckFor = ddp.f.trimWhitespace(strClassToCheckFor);
    var re = getRegExp('hasClass_re1_' + strClassToCheckFor);
    if(!re)
      re = addRegExp('hasClass_re1' + strClassToCheckFor, new RegExp('\\b' + strClassToCheckFor + '\\b', 'gi'));
    //return re.test(el.className);
    return re.test(strClass);
  }
  return false;
}

/***************************************************************/
//setClass
//sets an objects class (css) since IE and the Others work differently
function setClass(el,theClass)
{
  this.browserdt = new ddp.f.BrowserDetect();
  if(this.browserdt.browser == 'Explorer' && this.browserdt.version < 8)
    el.setAttribute('className', theClass);
  else
    el.setAttribute('class', theClass);
}



/***************************************************************/
//setAlternatingClasses
//gets elements by tag name (defaults to TR) and assigns the class names provided in arClasses in sequence
//mostly usefull for coloring columns or rows of a table
//e.g  setAlternatingClasses(ddp.f.$('theTable'), ['rowRed','rowGreen','rowBlue']);
//will result in a table with rows that are red, green, blue, red, green, blue, and repeat for all rows of the table
function setAlternatingClasses(parentElementOrArray, arClasses, strTagName){
  if(!parentElementOrArray)
    parentElementOrArray = document;
  if(ddp.f.isString(arClasses))
    arClasses = ['', arClasses];
  if(!strTagName)
    strTagName = 'tr';
  //if it's an array, just assign it directly, if it's a DOMElement, get it's TR elements (or other specified tagName
  var arRows = ddp.f.isArray(parentElementOrArray) ? parentElementOrArray : parentElementOrArray.getElementsByTagName(strTagName);
  
  //iterate through the elements of the array and assign classnames from the arClasses array
  for(var i=0, il=arRows.length; i<il; i++){
    for(var j=0, jl=arClasses.length; j <jl; j++){
      if((i+jl)%jl==j)
        ddp.f.addClass(arRows[i], arClasses[j]);
      else
        ddp.f.removeClass(arRows[i], arClasses[j]);
    }
  }

}




/***************************************************************/
//_styleToProperty
//used internally to translate the css style to a DOM property 
function _styleToProperty(strStyle){
  var strResult = '';
  
  var arExceptions = [];
  if(window.document.body && typeof window.document.body.style.styleFloat != 'undefined'){
    arExceptions.push(["float","styleFloat"]);
  }
  else{
    arExceptions.push(["float","cssFloat"]);
  }
  
  //add any other exceptions in the format below 
  //arExceptions.push(["css-style-string","DOMPropertyString"]);
  
  strStyle = strStyle.toLowerCase();
  
  //see if it's one of the exceptions
  for(var i=0, l=arExceptions.length; i<l; i++){
    if(strStyle == arExceptions[i][0])
      return arExceptions[i][1];
  }
  
  //if it's not an exception do the replace operation on it and return the result
  return strStyle.replace(/-([a-z])/g, function (_, ltr) { return ltr.toUpperCase(); });
}

/***************************************************************/
//getEffectiveStyle
//returns the effective CSS style on an element
function getEffectiveStyle(elElement, strStyle){
  if(typeof elElement == 'string')
    elElement = document.getElementById(elElement);

  if(window.getComputedStyle){ //good browsers
    var computedStyle = window.getComputedStyle(elElement, '');
    return computedStyle.getPropertyValue(strStyle);
  }
  else if(elElement.currentStyle){ //IE
    var strProperty = _styleToProperty(strStyle);
    if(strProperty=='cssFloat') strProperty = 'styleFloat';
    var strResult = elElement.currentStyle[strProperty];
    if(typeof strResult != 'undefined')
      return strResult;
  }
  
  return null;
}


/***************************************************************/
//getElementsByClassName
//find elements underneath a parent element by their
//classname and return them in an array 
function getElementsByClassName(strClassName, elStartElement, strTagName) {
  if(!elStartElement)
    elStartElement = document.getElementsByTagName('body')[0];
  if(!strTagName)
    strTagName = '*';
  
  if(elStartElement){
    //try to use the native getElementsByClassName as it's much faster (supported by FF3/webkit engine)
    if(elStartElement.getElementsByClassName) 
      return makeRealArray(elStartElement.getElementsByClassName(strClassName));
    //then try to use the native querySelectorAll as it's also much faster (supported by FF3.5 and is a w3c standard)
    else if(elStartElement.querySelectorAll)
      return makeRealArray(elStartElement.querySelectorAll('.' + strClassName));
    //then default to doing it manually
    else{
      var arElements = []; //create array to return the elements in
      //just get all sub-elements of the start element
      var arAllElements = (elStartElement.all && strTagName=='*') ? elStartElement.all : elStartElement.getElementsByTagName(strTagName);
      
      var i=arAllElements.length;
      while(i--){ //a decrementing "while" in this scenario is faster than the "for" syntax (supposedly).
        if(hasClass(arAllElements[i], strClassName))
          arElements.splice(0, 0, arAllElements[i]);
      }

      return arElements;
    }
  }
  
  return null;
}


/***************************************************************/
//getChildElementById 
//find an element by it's id, which is a child element of "elFromElement"
//returns the element's reference, or null if the element id can't be found 
function getChildElementById(elFromElement, idToFind, findMultipleIds){
  if(elFromElement && elFromElement.getElementsByTagName){
    if(ddp.f.isArray(idToFind))
      return getChildElementsByIds(elFromElement, idToFind, findMultipleIds);
    var arChildElements = elFromElement.getElementsByTagName('*');
    var i = arChildElements.length;
    while(i--)
      if(arChildElements[i].id && arChildElements[i].id == idToFind)
        return arChildElements[i];
  }
  return null;
}


/***************************************************************/
//getChildElementsById 
//finds multiple elements by their ids, which are child elements of "elFromElement"
//returns an object of element references, or null if the element id can't be found
//(this can save a lot of time vs. mutiple getChildElementById() calls as the whole
//DOM structure doesn't have to be search each time)
function getChildElementsByIds(elFromElement, arIdsToFind, findMultipleIds){
  var objReturn = {};
  //arbitrary - gh - 31-dec-2010
  //findMultipleIds = (findMultipleIds == true);
  
  //if we are going to splice elements out when we find them
  if(!findMultipleIds)
    //take a copy of the array
    arIdsToFind = ddp.f.makeRealArray(arIdsToFind);
  //record the length so we can reset the decrementer without going back to the length property
  var idsLength = arIdsToFind.length;
  
  //get all elements under the "from element"
  var arChildElements = elFromElement.getElementsByTagName('*');
  var i = arChildElements.length;
  
  //loop backwards through the elements
  while(i--){
    var j = idsLength; //set the decrementer (or reset it)
    //loop backwards through the ids that were provided
    while(j--){
      //there is a match on the id we're looking for
      if(arChildElements[i].id && arChildElements[i].id == arIdsToFind[j]){
        //if we're searching for multiple elements with the same id within this DOM structure
        if(findMultipleIds){
          //the property doesn't exist yet
          if(typeof objReturn[arIdsToFind[j]] == 'undefined')
            objReturn[arIdsToFind[j]] = arChildElements[i]; //assign the element reference directly
          //it's already an array
          else if(ddp.f.isArray(objReturn[arIdsToFind[j]]))
            objReturn[arIdsToFind[j]].push(arChildElements[i]); //add another element
          //it's just a single element, no array
          else if(objReturn[arIdsToFind[j]])
            //make a new array with the current element being the first index and the new element the second
            objReturn[arIdsToFind[j]] = [objReturn[arIdsToFind[j]], arChildElements[i]];
        }
        else{
          //since we're looping backwards through the elements,
          //if the return object property gets overwritten,
          //it will be with the "first" occurance of the id.
          objReturn[arIdsToFind[j]] = arChildElements[i];
          arIdsToFind.splice(j, 1); //splice out the id we found so we don't have to search for it again.
          j=0; //we're not looking for multiple ids, so don't look for any more matches this round
        }
      }
    }
  }
  
  return objReturn;
}

/***************************************************************/
//getAncestorByClassName
//climbs up the DOM tree from the startElement supplied until an element with
//the className supplied is found or until there aren't any more elements. it might
//return startElement.parentNode.parentNode.parentNode.parentNode for example.
function getAncestorByClassName(strClassName, elStart, blnBridgeIframes){
  return climbDOMTree(elStart, function(){ if(this.className.toLowerCase().indexOf(strClassName) != -1) return this; }, null, blnBridgeIframes);
}

/***************************************************************/
//getElementForm 
//climbs up the node tree until it finds a <FORM> element or
//there are no more nodes (in which case null is returned)
function getElementForm(elElement, blnBridgeIframes){
  if(typeof elElement != 'object' || elElement === null)
    throw 'ddp.f.getElementForm - elElement is not a DOM element.';
  return climbDOMTree(elElement, function(){ if(this.nodeName.toLowerCase() == 'form') return this; }, null, blnBridgeIframes);
}

/***************************************************************/
//getRadioValueByName
//returns the value of a radio group when supplied with the name
//if no options in the rradio group have been selected, returns null.
function getRadioValueByName(strRadioName){
  var elRadioGroup = document.getElementsByName(strRadioName);
  for(var i=0, l=elRadioGroup.length; i<l; i++){
    if(elRadioGroup[i].checked)
      return elRadioGroup[i].value;
  }
  return null;
}

/***************************************************************/
//isChild 
//returns true if the first element supplied is a DOM descendent of the second element supplied, otherwise returns false.
function isChild(elChild, elParent, blnBridgeIframes){
  if(typeof elChild != 'object' || typeof elParent != 'object' || !elChild || !elParent){
    return null;
  }
  //if the browser supports the "contains" method (basically everyone but Firefox)
  if(elParent.contains){
    return elParent.contains(elChild);
  }
  //Firefox
  if(elChild.compareDocumentPosition){
    return Boolean(elParent.compareDocumentPosition(elChild) & 16); //single & is the bitwise AND operator, not a typo (compareDocumentPosition returns a bitmask).
  }
  //fallback to calculating it ourselves
  return (climbDOMTree(elChild, function(nodes, elParent){ if(this==elParent) return true; }, [elParent], blnBridgeIframes) === true);
}


/***************************************************************/
//climbDOMTree
//climbs up the DOM tree one node at a time, starting from the supplied element "elStart"
//with each level, fnEvaluate is executed (with it's "this" value being the currently evaluated node)
//the first argument passed to fnEvaluate will be an array of the previously evaluated nodes, followed by 
//any arguments passed in the arArguments array 
function climbDOMTree(elStart, fnEvaluate, arArguments, blnBridgeIframes){
  var nodesClimbed = [];
  //arbitrary - gh - 31-dec-2010
  //blnBridgeIframes = (blnBridgeIframes==true) ? true : false;
  
  if(ddp.f.isArray(arArguments))
    arArguments.splice(0, 0, nodesClimbed); //slip a zero into the first index
  else
    arArguments = [nodesClimbed];

  for(var elCurrent = elStart; elCurrent; elCurrent = elCurrent.parentNode){
    //if we are allowing this climb to escape from the containing iframe (if there is one)
    if(blnBridgeIframes){
      //check if the node is a DOCUMENT_NODE
      if(elCurrent.nodeType == nodeTypes.DOCUMENT_NODE){
        var objParentWindow = null;
        //find the element's window's parent window 
        if(elCurrent.defaultView && elCurrent.defaultView.parent) //NS / FF
          objParentWindow = elCurrent.defaultView.parent;
        else if(elCurrent.parentWindow && elCurrent.parentWindow.parent) //IE
          objParentWindow = elCurrent.parentWindow.parent;
        else
          return null; //there is no parent window, so we're done
          
        //search through the parent's Iframes to find this element's document
        var elIframes = objParentWindow.document.getElementsByTagName('IFRAME');
        for(var i=0, l=elIframes.length;i<l;i++){
          //catch the error when we try to access the document object for other domains (FF) 
          try{
            //if the iframe's document accessed from the parent window is our document 
            if(elIframes[i].contentWindow.document == elCurrent)
              elCurrent = elIframes[i]; //then start climbing again at the iframe element of the parent window
          }
          catch(e){} //don't need to do anything 'cause if it's not in the same domain, it's not this document anyway
        }
      }
    }
    
    nodesClimbed.push(elCurrent);
    //nodesClimbed.splice(0, 0, elCurrent); //could put them in backwards.. does that make sense?
    var retVal = fnEvaluate.apply(elCurrent, arArguments);
    if(typeof retVal != 'undefined')
      return retVal;
  }
  return null;
}



/***************************************************************/
function getElementPosition(elObject, elRelativeTo, includeScrollValues){
  //will hold the accumulated offsets.
  var offsetLeft = 0;
  var offsetTop = 0;
  //if the object was passed as a string id, get the element reference
  elObject = ddp.f.isObject(elObject) ? elObject : document.getElementById(elObject);
  //make sure if we aren't being relative to something, that we don't try and see if the element is a child of it
  if(!ddp.f.isObject(elRelativeTo))
    elRelativeTo = false;
    
  var isIE7OrLess = false;
  /*
  var bd = new BrowserDetect();
  isIE7OrLess = (bd.browser=='Explorer' && bd.version < 8);
  */
    
  if(includeScrollValues || isIE7OrLess){
    var nextOffsetParent = elObject;
    //climb up the DOM tree by parent nodes
    while(elObject && (!elRelativeTo || elRelativeTo != elObject) ){
      if(elObject == nextOffsetParent || getEffectiveStyle(elObject, 'position') == 'relative'){
        offsetLeft += elObject.offsetLeft;
        offsetTop += elObject.offsetTop;
        nextOffsetParent = elObject.offsetParent;
      }
      
      //stop the HTML tag from interfering with our grand works
      elObject = (elObject.parentNode && elObject.parentNode.nodeName == 'HTML') ? null : elObject.parentNode;
      
      if(elObject && includeScrollValues){
        if(elObject.scrollLeft)
          offsetLeft -= elObject.scrollLeft;
        if(elObject.scrollTop)
          offsetTop -= elObject.scrollTop;
      }
    }
  }
  else{
    //may not work properly under ei5-7 as they have a bug where relatively positioned elements are not considered offsetParents
    while(elObject && (!elRelativeTo || ddp.f.isChild(elObject, elRelativeTo))){
      offsetLeft += elObject.offsetLeft;
      offsetTop += elObject.offsetTop;
      elObject = elObject.offsetParent;
    }
  }

  //return the accumulated offsets
  return {x:offsetLeft, y:offsetTop};
}


/***************************************************************/
//if you need top AND left, don't use both below seperately as it doubles the execution time. use getElementPosition()
function getElementTop(elObject, elRelativeTo, includeScrollValues){ return getElementPosition(elObject, elRelativeTo, includeScrollValues).y; }
function getElementLeft(elObject, elRelativeTo, includeScrollValues){ return getElementPosition(elObject, elRelativeTo, includeScrollValues).x; }


/***************************************************************/
function getElementWidth(elObject){
  //if the object was passed as a string id, get the element reference
  elObject = ddp.f.isObject(elObject) ? elObject : document.getElementById(elObject);
  return parseInt(elObject.offsetWidth, 10);
}


/***************************************************************/
function getElementRight(elObject){
  return getElementLeft(elObject) + getElementWidth(elObject);
}


/***************************************************************/
function getElementHeight(elObject){
  //if the object was passed as a string id, get the element reference
  elObject = ddp.f.isObject(elObject) ? elObject : document.getElementById(elObject);
  return parseInt(elObject.offsetHeight, 10);
}


/***************************************************************/
function getElementBottom(elObject){
  return getElementTop(elObject) + getElementHeight(elObject);
}


/***************************************************************/
//addStyleSheet
//adds an inline stylesheet to the document header
//objProperties is an object with properties which are identical to
//the attributes in the style tag you want to create eg. var objProperties = {type:'text/css',rel:'screen'}
//elDocument is optional
function addStyleSheet(objProperties, strCSSText, elDocument){
  if(typeof elDocument != 'object') elDocument = window.document;

    var elHead = elDocument.getElementsByTagName('head')[0];
    var elStyle = elDocument.createElement('style');
    
    if(typeof objProperties == 'object'){
      for(var key in objProperties){
        if(objProperties.hasOwnProperty && objProperties.hasOwnProperty(key)){
          try{ elStyle.setAttribute(key, objProperties[key]); } 
          catch(e){}
        }
      }
    }
    elStyle.setAttribute('type', 'text/css');
    
    if(elStyle.styleSheet) 
      elStyle.styleSheet.cssText = strCSSText;
    else 
      elStyle.appendChild(elDocument.createTextNode(strCSSText));
    
    elHead.appendChild(elStyle);
}

/***************************************************************/
//Add a <link>ed style sheet
function addLinkStyleSheet(strCSSFile, elDocument, blnInsertFirst){
  if(typeof elDocument != 'object')
    elDocument = window.document;
  blnInsertFirst = (blnInsertFirst === false) ? false : true;
  
  //check to see if the style has already been added.
  var elLinks = elDocument.getElementsByTagName('LINK');
  for(var i=0, l=elLinks.length; i<l;i++)
    if(elLinks[i].href.toLowerCase().indexOf(strCSSFile) > -1)
      return false;
  
  var objLinkedCSS = elDocument.createElement('LINK');
  var elHead = elDocument.getElementsByTagName('head')[0];
  objLinkedCSS.type='text/css';
  objLinkedCSS.rel='stylesheet';
  objLinkedCSS.media='screen';
  objLinkedCSS.href=strCSSFile;
  
  if(blnInsertFirst && elHead.firstChild)
    elHead.insertBefore(objLinkedCSS, elHead.firstChild);
  else
    elHead.appendChild(objLinkedCSS);
  
  return true;
}


/***************************************************************/
//addScriptToHeader
//adds a script tag to the document header
//objProperties is an object with properties which are identical to
//the attributes in the script tag you want to create eg. var objProperties = {type:'text/javascript',language:'javascript'}
//elDocument is optional
function addScriptToHeader(objProperties, strScriptText, elDocument){
  if(typeof elDocument != 'object') elDocument = window.document;
  
  var elHead = elDocument.getElementsByTagName('head')[0];
  var elScript = elDocument.createElement('script');
  
  if(typeof objProperties == 'object'){
    for(var key in objProperties){
      if(objProperties.hasOwnProperty && objProperties.hasOwnProperty(key)){
        try{
          elScript.setAttribute(key, objProperties[key]);
        }
        catch(e){}
      }
    }
  }
  
  elScript.setAttribute('defer', 'defer');
  
  if(elScript.canHaveChildren || elScript.canHaveChildren == null)
    elScript.appendChild(document.createTextNode(strScriptText));
  else
    elScript.text = strScriptText;
  
  elHead.appendChild(elScript);
}


/***************************************************************/
//removeElement
//Removes an element from the DOM 
function removeElement(refOrId, keepData){
  //if an id string was passed, make sure we turn it into a reference
  var el = (ddp.f.isString(refOrId)) ? document.getElementById(refOrId) : refOrId;
  
  if(!keepData && el.nodeType === 1){
    //remove the cached data for all child elements
    cleanElementData(el.getElementsByTagName("*"));
    //and the element to be removed
    cleanElementData([el]);
  }
  
  if(el.parentNode){
    el.parentNode.removeChild(el);
  }
}

function replaceElement(elOld, elNew){
  if(elOld.parentNode){
    elOld.parentNode.replaceChild(elNew);
  }
  cleanElementData(elOld);
}

function detachElement(el){
  removeElement(el, true);
}

function cleanElementData(arEls){
  var i = arEls.length;
  while(i--){
    _memorize.elData[arEls[i].__ddpGUID__] = undef;
    delete _memorize.elData[arEls[i].__ddpGUID__];
    arEls[i].__ddpGUID__ = undef;
    //try{ delete arEls[i].__ddpGUID__; }
    //catch(e){ arEls[i].__ddpGUID__ = undef; }
  }
}

function getData(el, key){
  var uid = el.__ddpGUID__;
  return (uid) ? (key ? _memorize.elData[uid][key] : _memorize.elData[uid]) : undef;
}

function setData(el, key, data, overwrite){
  var uid = el.__ddpGUID__;
  if(!uid){
    uid = el.__ddpGUID__ = GUID();
    _memorize.elData[uid] = {};
  }
  if(!overwrite && isObject(data) && isObject(_memorize.elData[uid][key])){
    console.log(isObject(_memorize.elData[uid][key]));
    var obj = _memorize.elData[uid][key];
    forOwnIn(data, function(val, ownKey){
      obj[ownKey] = val;
    });
  }
  else{
    _memorize.elData[uid][key] = data;
  }
}

function removeData(el, key){
  var uid = el.__ddpGUID__;
  if(!uid){
    return;
  }
  if(_memorize.elData[uid] && _memorize.elData[uid].hasOwnProperty(key)){
    _memorize.elData[uid][key] = undef; //for implementations that won't actually delete the property
    delete _memorize.elData[uid][key];
  }
}

function setHTML(el, html){
  //we do this to clean the data from the child elements we will be overwriting with the html
  cleanElementData(el.getElementsByTagName('*'));
  el.innerHTML = html;
}

function forOwnIn(obj, fn){
  for(var key in obj){
    if(obj.hasOwnProperty && obj.hasOwnProperty(key)){
      fn(obj.hasOwnProperty(key), key, obj);
    }
  }
}



/***************************************************************/
//insertElementAfter
//inserts an element into the DOM tree after the supplied element.
//
function insertElementAfter(elementToInsert, elementToInsertAfter){
  elementToInsertAfter.parentNode.insertBefore(elementToInsert, elementToInsertAfter.nextSibling);
}


function removeChildren(el){
  while(el.lastChild){
    removeElement(el.lastChild);
  }
}


function replaceChildren(el, elNewChild){
  removeChildren(el);
  el.appendChild(elNewChild);
}



/*---------------------------------------------------------------------*/
//arrayOfObjectsToTable
//
function arrayOfObjectsToTable(arObj, arFormatting, arAlternatingRowClasses, strHighlightedRowClass){
  var elTable = document.createElement('table');
  var elHeaderRow = arrayOfObjectsToHeaderRow(arObj, arFormatting);
  if(elHeaderRow){
    var elTHead = document.createElement('thead');
    elTHead.appendChild(elHeaderRow);
    elTable.appendChild(elTHead);
  }
  var elTBody = document.createElement('tbody');
  var elFrag = arrayOfObjectsToTableRows(arObj, arFormatting, arAlternatingRowClasses, strHighlightedRowClass);
  elTBody.appendChild(elFrag);
  elTable.appendChild(elTBody);
  return elTable;
}

/*---------------------------------------------------------------------*/
//arrayOfObjectsToHeaderRow
//
function arrayOfObjectsToHeaderRow(arObj, arFormatting){
  var elTR, elTH, str;
  elTR = document.createElement('TR');
  
  var arToIterate = arFormatting;
  if(!arToIterate){
    arToIterate = [];
    for(var key in arObj[0]){
      if(arObj[0].hasOwnProperty(key)){
        arToIterate.push({"title": key});
      }
    }
  }
  
  if(arToIterate.length){
    for(var i=0, l=arToIterate.length; i<l; i++){
      elTH = document.createElement('TH');
      elTH.appendChild(document.createTextNode(arToIterate[i].title || ''));
      elTR.appendChild(elTH);
    }
    
    return elTR;
  }
  return null;
}


/*---------------------------------------------------------------------*/
//arrayOfObjectsToTableRows
//
function arrayOfObjectsToTableRows(arObj, arFormatting, arAlternatingRowClasses, strHighlightedRowClass){
  var elFrag = document.createDocumentFragment();
  var elTR, elTD, elTH, obj, el, val;
  
  function hlr(){
    ddp.f.addClass(this, strHighlightedRowClass);
  }
  function uhlr(){
    ddp.f.removeClass(this, strHighlightedRowClass);
  }
  
  var arToIterate = arFormatting;
  if(!arToIterate && arObj[0]){
    arToIterate = [];
    for(var key in arObj[0]){
      if(arObj[0].hasOwnProperty(key)){
        arToIterate.push({"name": key});
      }
    }
  }
  //alternatingRowClassesLength
  var ARCL = (arAlternatingRowClasses && arAlternatingRowClasses.length) ? arAlternatingRowClasses.length : 0;
  
  for(var i=0, il=arObj.length; i<il; i++){
    obj = arObj[i];
    elTR = document.createElement('TR');
    if(strHighlightedRowClass){
      ddp.f.addEvent(elTR, 'mouseenter', hlr);
      ddp.f.addEvent(elTR, 'mouseleave', uhlr);
    }
    if(ARCL){
      ddp.f.addClass(elTR, arAlternatingRowClasses[(i+ARCL)%ARCL]);
    }
    
    for(var j=0, jl=arToIterate.length; j<jl; j++){
      elTD = document.createElement('TD');
      val = obj[arToIterate[j].name];
      
      if(arToIterate[j].format){
        val = arToIterate[j].format(val, obj, elTD, elTR, i, arObj);
      }
      else if(isDate(val)){
        val = dateFormat(val, null, null, 5);
      }
      
      if(val === null)
        val = '';
      
      elTD.appendChild((!val || !val.nodeType) ? document.createTextNode(val) : val);
      elTR.appendChild(elTD);
    }
    elFrag.appendChild(elTR);
  }
  return elFrag;
}


/***************************************************************/
//setOpacity 
//sets the percent opacity of an element using IE and FF/NS CSS opacity 
//
function setOpacity(objRefOrId, opacityPercentage){
  var elObj = (isString(objRefOrId)) ? document.getElementById(objRefOrId) : objRefOrId;
  opacityPercentage = parseInt(opacityPercentage, 10);
  if(elObj && elObj.style){
    if(typeof elObj.style.opacity == 'string')
      elObj.style.opacity = opacityPercentage/100;
    else
      elObj.style.filter = 'alpha(opacity=' + opacityPercentage + ')';
    //to coordinate with ddp.fx module, set an expando to store current opacity
    elObj._ddpCurrentOpacity = opacityPercentage;
    //elObj.currentOpacity = opacityPercentage;
  }
}


/***************************************************************/
//toCamelCase (proper case)
//converts the value of textObj to camel case/title case
//(eg. "First Letter Of Every Word Capitalized") 
function toCamelCase(textObj){
  var resultStr = '';
  var strText = textObj.value;
  var intLength = strText.length;
  if(intLength > 0){
    resultStr = strText.substr(0,1).toUpperCase();
    for(x=1; x<intLength; x++){
      if(strText.substr(x-1,1)==' ')
        resultStr+=strText.substr(x,1).toUpperCase();
      else
        resultStr+=strText.substr(x,1);//.toLowerCase();
    }
  }
  return resultStr;
}


/***************************************************************/
//countOccurances
//counts the number of times 'charToCheckFor' is in 'stringToCheck' 
function countOccurances(stringToCheck, charToCheckFor){
  var count = 0;
  if(strToCheck.length && strToCheckFor.length){
    for(var i=0, l=strToCheck.length; i<l; i++){
      if(strToCheck.substr(i,1) == chrToCheckFor){
        count++;
      }
    }
  }
  return count;
}


/***************************************************************/
//removeInvalidChars
//removes invalid characters from a string or element, based on the data type supplied 
function removeInvalidChars(strDataType, strOrEl){
  var reDataType = null;
  var strReturn = null;
  
  switch(strDataType.toLowerCase()){
    case 'date':
      reDataType = /[^0-9/\-janfebmryulgspoctvd\u00e9\u00f4,\. ]/gi;
      break;
    case 'number':
      reDataType = /[^0-9\.]/g;
      break;
    case 'postalcode':
      reDataType = /[^0-9a-z]/gi;
      break;
    case 'coursecode':
      reDataType = /[^0-9a-z]/gi;
      break;
    case 'phonenumber':
      reDataType = /[^0-9]/g;
      break;
    case 'email':
      reDataType = /[^0-9a-z@\._]/gi;
      break;
    case 'filename':
      reDataType = /[^0-9a-zA-Z\._\(\)\+\-\!@#\$%\^&,`~\[\]{} ']/g;
      break;
    case 'text':
      reDataType = /[^0-9a-z\._\(\)\+\-\!@#\$%\^&,`~\[\]{} ':;\|\*<>\\/\?]/gi;
      break;
  }
  
  if(reDataType){
    if(!isString(strOrEl) && typeof strOrEl == 'object'){
      strReturn = strOrEl.value.replace(reDataType, '');
      strOrEl.value = strReturn;
    }
    else
      strReturn = strOrEl.replace(reDataType, '');
  }

  return strReturn;
}


/***************************************************************/
//inputFilter
//define characters allowed in fields with specific data types
//USE WITH ONKEYPRESS EVENT ONLY
function inputFilter(strDataType, objEvent) {
  //allow any non-character keys, backspace etc..
  var charCode = document.all ? objEvent.keyCode : objEvent.which; 
  if(charCode==8 || charCode==9 || charCode==0)
    return true;
  
  //see if the character is removed 
  var strResult = removeInvalidChars(strDataType, String.fromCharCode(charCode));
  if(strResult && strResult.length == 1)
    return true;
  
  cancelEvent(objEvent); //cancel the keypress
  return false;
}


/***************************************************************/
//formatPhoneNumber
//Formats a phone number field into "(xxx) xxx-xxxx" or "xxx-xxxx" format, depending on length.
function formatPhoneNumber(strOrEl){
  //get the actual string we'll be working with
  var strValue = isString(strOrEl) ? strOrEl : strOrEl.value;
  //remove any non-numeric characters
  strValue = removeInvalidChars('phoneNumber', strValue);
  //if it's a ten digit number with a 1 at the beginning, assume it's a long distance indicator and remove it
  if(strValue.length == 11 && strValue.substr(0,1) == '1')
    strValue = strValue.substr(1);
  
  if(strValue.length == 7) //format a 7 digit number: 555-5555
    strValue = strValue.substr(0,3) + '-' + strValue.substr(3);
  else if(strValue.length == 10) //format a 10 digit number: (555) 555-5555
    strValue = '(' + strValue.substr(0,3) + ') ' + strValue.substr(3,3) + '-' + strValue.substr(6);

  //if we got passed an element and the new value is different, set the element's value 
  if(!isString(strOrEl) && typeof strOrEl == 'object' && strOrEl.value != strValue)
    strOrEl.value = strValue;
  
  //return the resultant string
  return strValue;
}


/***************************************************************/
//imposeMaxLength
//reproduces the effect "maxlength" has on a regular text <input> for a <textarea> element 
function imposeMaxLength(intMaxLength, objEvent) {
  //get the object that fired the event
  var elText = getEventTarget(objEvent);
  var intTextLength = elText.value.length;
    
  if(objEvent.type=='keypress'){
    var chrCode = document.all ? objEvent.keyCode : objEvent.which;
    if(chrCode==8 || chrCode==9 || chrCode==0)
      return true;
    if(intTextLength >= intMaxLength){
      cancelEvent(objEvent);
      return false;
    }
  }
  else if(objEvent.type=='blur'){
    if(intTextLength > intMaxLength){
      elText.value = elText.value.substr(0, intMaxLength); //truncate the text
      alert('This field can contain a maximum of ' + intMaxLength + ' characters. The text has been truncated to fit.');
      cancelEvent(objEvent); //don't let the event happen
      if(typeof elText.focus == 'function')
        elText.focus(); //focus the element that just had it's text truncated
      return false;
    }
  }

  return true;
}


/***************************************************************/
//getFromURL
//gets the value of a variable passed to the page in the querystring 
//[equivalent to VB's request.querystring(varName) ]
//optional second parameter is the string you want to search. (defaults to window.loacation.href otherwise)
function getFromURL(varName, strURL, allowNull){
  //use the url string passed or get the URL of this page
  if(!strURL)
    strURL = window.location.href;
    
  //if the URL contains a question mark, then it was loaded with arguments in the URL
  if(strURL.indexOf('?') != -1){
    //grab everything in the URL after the question mark
    var args = strURL.substr(strURL.indexOf('?')+1);
    //split the querystring into an array of strings with each element containing "example=blah"
    var splitArgs = args.split('&');
    //go through all the elements in the array of arguments
    for(var i=0, l=splitArgs.length; i<l; i++){
      //split each array element into an array of 2 strings with the first element containing "example" and the second "blah"
      splitArgs[i] = splitArgs[i].split('=');
      //if the variable passed in the querystring is our requested name
      if(splitArgs[i][0].toLowerCase()==varName.toLowerCase()){
        return decodeURIComponent(splitArgs[i][1]); //get it's contents
      }
    }
  }
     
  //if the variable doesn't exist return a null;
  return (allowNull) ? null : '';
}


/***************************************************************/
//extractServerandPath
//takes a URL and extracts the server and path
//removes any file name or arguments. 
function extractServerandPath(inVar){
  var returnStr = '' + inVar; //make sure a string
  
  if(returnStr.indexOf('?') != -1)
    returnStr = returnStr.substr(0,returnStr.indexOf('?'));
  returnStr = returnStr.substr(0,returnStr.lastIndexOf('/') + 1);
  
  return returnStr;
}


/***************************************************************/
//getServerNameFromURL
//
//
function getServerNameFromURL(strURL){
  var strServer = '' + strURL;
  
  if(strServer.indexOf('//') != -1){
    strServer = strServer.substr(strServer.indexOf('//') + 2);
    strServer = strServer.substr(0, strServer.indexOf('/'));
    
    return strServer.toLowerCase();
  }
  
  return null;
}


/***************************************************************/
//setCookie
//sets a browser cookie
//
function setCookie(name, value, expires, path, domain, secure) {
  document.cookie= name + "=" + escape(value) +
    ((expires) ? "; expires=" + expires.toGMTString() : "") +
    ((path) ? "; path=" + path : "") +
    ((domain) ? "; domain=" + domain : "") +
    ((secure) ? "; secure" : "");
}


/***************************************************************/
//getCookie
//gets the value of a browser cookie by the cookie's name 
//
function getCookie(name){
  var dc = document.cookie;
  var prefix = name + "=";
  var begin = dc.indexOf("; " + prefix);
  
  if(begin == -1){
    begin = dc.indexOf(prefix);
    if(begin != 0)
      return null;
  }
  else
    begin += 2;

  var end = document.cookie.indexOf(";", begin);
  
  if(end == -1){
    end = dc.length;
  }
  
  return decodeURIComponent(dc.substring(begin + prefix.length, end));
}


/***************************************************************/
//deleteCookie
//removes a browser cookie from the browser cache
//
function deleteCookie(name, path, domain){
  if(getCookie(name)) {
    document.cookie = name + "=" +
      ((path) ? "; path=" + path : "") +
      ((domain) ? "; domain=" + domain : "") +
      "; expires=Thu, 01-Jan-70 00:00:01 GMT";
  }
}


/***************************************************************/
//getRandomInRange
//returns a random integer between intMinimum and intMaximum 
function getRandomInRange(intMinimum, intMaximum){
  //return Math.round(intMinimum + (Math.random() * (intMaximum - intMinimum)));
  return Math.floor((intMaximum - (intMinimum-1)) * Math.random()) + intMinimum;
}
  

/***************************************************************/
//isValidDate
//accepts either an element with a value attribute (like <input>) or a string 
//returns true if the date is valid and in "mm/dd/yy" (6/26/81) or "dd-mmm-yy" (26-jun-81) format
function isValidDate(elOrStr){
  try{
    if(parseDateString((typeof elOrStr == 'object') ? elOrStr.value : elOrStr) !== null)
      return true;
  }
  catch(e){}
  
  return false;
}


/***************************************************************/
//parseDateString 
//parses a date string and returns a js date object
function parseDateString(strValue) {
  var strDate = strValue;
  var objReturnDate;
  var dayPart = '';
  var monthPart = '';
  var yearPart = '';
  
  if(!strDate || !strDate.length){
    return null;
  }
    
  objReturnDate = (new Date());
  
  strDate = strDate.replace(/\//g, '-');
  strDate = strDate.replace(/ /g, '-');
  strDate = strDate.replace(/,/g, '-');
  strDate = strDate.replace(/\./g, '-');
  
  var temp = strDate.split('-');
  var parts = [];
  
  for(var x=0; x<temp.length; x++){
    if(temp[x] !== ''){ //changed from "!=" to "!==" - gh - 31-dec-2010
      parts[parts.length] = temp[x];
    }
  }

  if(parts.length!=3){
    return null;
  }
  
  if(isMonth(parts[0])){
    dayPart = parts[1];
    monthPart = isMonth(parts[0]);
  }  
  else if(isMonth(parts[1])){
    dayPart = parts[0];
    monthPart = isMonth(parts[1]);
  }
  else{
    monthPart = parts[0];
    dayPart = parts[1];
  }

  
  yearPart = fourDigitYear(parts[2]);

  if(dayPart <= daysInMonth(yearPart, monthPart) && dayPart > 0 && monthPart < 13 && monthPart > 0 && yearPart > 1900 && yearPart < 2099){
    objReturnDate.setFullYear(yearPart,monthPart-1,dayPart);
    return objReturnDate;
  }
  else{
    return null;
  }
}


/***************************************************************/
//isMonth
//returns the month number from a month name (or short form)
//if the string doesn't contain a month name, returns zero 
function isMonth(inStr){
  if(!isString(inStr))
    return 0;
  inStr = inStr.toLowerCase();
  var monthName = ddp.l.monthName;
  var monthNameShort = ddp.l.monthNameShort;

  for(var key in monthName){
    if(monthName.hasOwnProperty && monthName.hasOwnProperty(key)){
      for(var i=0, l=monthName[key].length; i<l; i++){
        if(inStr==monthName[key][i].toLowerCase() || (monthNameShort[key] && inStr==monthNameShort[key][i].toLowerCase()))
          return i+1;
      }
    }
  }
  
  return 0;
}


/***************************************************************/
//dateFormat
//formats a date in a specified type.
// 1 - dd-mmm-yyyy (26-Jun-1981)
// 2 - mm/dd/yyyy (6/26/1981)
function dateFormat(month, day, year, type, language){
  var objDate = null;
  if(!type)
    type = 1;

  if(isString(language))
    language = language.substring(0,3).toLowerCase();
  else
    language = 'en';
  var monthName = ddp.l.monthName[language];
  var monthNameShort = ddp.l.monthNameShort[language];

  if(typeof month == 'object')
    objDate = month;
  else if(typeof month == 'string' && !day && !year){
     objDate = parseDateString(month);
  }
  else if(month && day && year){
    objDate = new Date(year, month-1, day);
  }
  
  if(objDate){
    month = objDate.getMonth() + 1;
    day = objDate.getDate();
    year = objDate.getFullYear();
    var hour = objDate.getHours();
    var pm = false;
    var minute = objDate.getMinutes();
    var second = objDate.getSeconds();
    
    if(hour === 0)
      hour = 12;
    if(hour > 12){
      hour = hour - 12;
      pm = true;
    }
    
    if(minute<10)
      minute = '0' + minute;
    
    if(second<10)
      second = '0' + second;
    
    if(type==1)
      return day + '-' + monthNameShort[month-1] + '-' + year;
    else if(type==2) //used to be french. must stop from using in future
      return day + '-' + ddp.l.monthNameShort.fr[month-1] + '-' + year;
    else if(type==3)
      return month + '/' + day + '/' + year;
    else if(type==4)
      return year + '-' + month + '-' + day;
    else if(type==5)
      if(hour == 12 && minute == '00' && pm === false)
        return day + '-' + monthName[month-1].substr(0,3) + '-' + year;
      else
        return day + '-' + monthName[month-1].substr(0,3) + '-' + year + ' ' + hour + ':' + minute + (pm ? 'pm' : 'am');
  }
  
  return '';
}


function fourDigitYear(year){
  //implicit conversion to string
  year = '' + year;
  if(year.length == 2){
    return (parseInt(year, 10) < 50) ? '20' + year : '19' + year;
  }
  return (year.length == 4) ? year : -1;
}

function daysInMonth(year, month) {
  //return [31,((!(year % 4 ) && ( (year % 100 ) || !( year % 400 ) ))?29:28),31,30,31,30,31,31,30,31,30,31][month-1];
  return [31,((year % 4 === 0 && (year % 100 || year % 400 === 0) ) ? 29 : 28),31,30,31,30,31,31,30,31,30,31][month-1];
  //return [31,((!(year%4)&&((year%100)||!(year%400)))?29:28),31,30,31,30,31,31,30,31,30,31][month-1];
}


/**************************************************/
//Add or Strip Slashes to/from a String
/**************************************************/

function addSlashes(str){
  str=str.replace(/\\/g,'\\\\');
  str=str.replace(/\'/g,'\\\'');
  str=str.replace(/\"/g,'\\"');
  str=str.replace(/\0/g,'\\0');
  return str;
}

function stripSlashes(str){
  str=str.replace(/\\'/g,'\'');
  str=str.replace(/\\"/g,'"');
  str=str.replace(/\\0/g,'\0');
  str=str.replace(/\\\\/g,'\\');
  return str;
}

/**************************************************/

function linkTextToUrl(str) 
{
  //convert any text that contains http into a link
  var exp = /(\b(https?|ftp|file):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/ig;
  return str.replace(exp,"<a href='$1' target='_blank'>$1</a>");
}

/**************************************************/

function getNodeTree(object)
{  
  var sel = this.getSelection(object);
  var range = this.getRange(sel);  
  
  // get element of range
  var tag = this.getTag(range);
  
  if(tag === null) { return; }
  // get parent of element
  var node = this.getParent(tag);
  
  // init the tree as array with the current selected element
  var nodeTree = [tag];
  // get all parent nodes
  var ii = 1;
  
  while(node != null && node.nodeName != "#document") {
    nodeTree[ii] = node;
    node = this.getParent(node);      
    ii++;
  }
  
  return nodeTree;
}
  
function getSelection(object)
{
  var ifrm = object.contentWindow;
  
  var doc = ifrm.document;
  var sel = null;
  if(ifrm.getSelection){
    sel = ifrm.getSelection();
  }
  else if (doc.getSelection) {
    sel = doc.getSelection();
  }
  else if (doc.selection) {
    sel = doc.selection;
  }
  return sel;
}

function getRange(sel)
{
  return sel.createRange ? sel.createRange() : sel.getRangeAt(0);
}

function getTag(range)
{
    try {
        if(!ddp.f.isIE()) {
        var node = range.startContainer;  
        var pos = range.startOffset;
        if(node.nodeType != 3) { node = node.childNodes[pos]; }
        
        if(node.nodeName && node.nodeName.search(/#/) != -1) {
          return node.parentNode;
        }
        return node;
      }
      else {
        if(range.length > 0) {
          return range.item(0);
        }
        else if(range.parentElement()) {
          return range.parentElement();
        }
      }
      return null;
    }
    catch(e) {
      return null;
    }
}

function getParent(element) {
  if(element.parentNode) {
    return element.parentNode;
  }
  return null;
}


/**************************************************/

function setFloat(obj,where)
{
  var floatValue = obj.style['cssFloat'] || obj.style['styleFloat'];
  obj.style['cssFloat'] = where;
  obj.style['styleFloat'] = where;
}


/**************************************************/
//GUID
//generate a global unique identifier (all numeric)
function GUID(){
  //return (new Date()).getTime() + (Math.random() + '').replace('.', '') + _memorize.guidCounter++;
  return (new Date()).getTime() + _memorize.guidCounter++;
}




/**************************************************/
//Run the active part of the library core
/**************************************************/
  //set the default one time events
  //(when addEvent tries to add more handlers after they've fired once, the "added" handler is executed immediately)
  setOneTimeEvent(window, 'load');
  setOneTimeEvent(document, 'ready');
  
  
  var elTest; //the element we'll be trying to add as a test of document readiness 
  var docReadyInterval; //the interval id we'll use to cancel the check interval later
  docReadyInterval = window.setInterval(function(){
    if(document){
      var elBody = document.getElementsByTagName('body')[0];
      if(typeof elBody != 'undefined'){
        if(!elTest){
          elTest = document.createElement('input');
          elTest.type = 'hidden';
          elTest.style.display = 'none';
        }
        try{
          elBody.appendChild(elTest);
          ddp.f.removeElement(elTest);
          
          //if there was no error thrown, then we can manipulate the DOM
          window.clearInterval(docReadyInterval);
          docReadyInterval = null;
          ddp.f.documentReadyTime = (new Date().getTime());
          fireEvent(document, 'ready', {"cancelable":false});
        }
        catch(e){}
      }
    }
  }, 25);
    
  
/**************************************************/





/**************************************************/
//Export the public functions to a public namespace
//use: ddp.f.function();
/**************************************************/
  
  //shorten the reference to the ddp namespace we want to add to (and avoid scope lookups)
  var n = ddp.f;
  
  n.memorize  = _memorize;
  
  n.nodeTypes = nodeTypes;
  n.$ = $;
  n.$c = $c;
  n.addLoadEvent = addLoadEvent;
  n.addModule = addModule;
  n.obsEvent = obsEvent;
  n.mousePosition = mousePosition;
  n.makeRealArray = makeRealArray;
  n.randomSortArray = randomSortArray;
  n.isArray = isArray;
  n.isString = isString;
  n.isObject = isObject;
  n.isFunction = isFunction;
  n.isDate = isDate;
  n.cloneObject = cloneObject;
  n.arrayCompare = arrayCompare;
  n.removeAlpha = removeAlpha;
  n.removeNonAsciiWord = removeNonAsciiWord;
  n.checkValidPhoneNumber = checkValidPhoneNumber;
  n.checkValidEmailAddress = checkValidEmailAddress;
  n.trimWhitespace = trimWhitespace;
  n.isIE = isIE;
  n.getBrowserGeometry = getBrowserGeometry;
  n.returnMaxZindex = returnMaxZindex;
  n.setOneTimeEvent = setOneTimeEvent;
  n.addEvent = addEvent;
  n.removeEvent = removeEvent;
  n.fireEvent = fireEvent;
  n.isEventSupported = isEventSupported;
  n.stopEventPropagation = stopEventPropagation;
  n.preventEventDefault = preventEventDefault;
  n.cancelEvent = cancelEvent;
  n.getEventTarget = getEventTarget;
  n.getEventKey = getEventKey;
  n.detectDoctype = detectDoctype;
  n.addClass = addClass;
  n.removeClass = removeClass;
  n.hasClass = hasClass;
  n.setClass = setClass;
  n.setAlternatingClasses = setAlternatingClasses;
  n.getRegExp = getRegExp;
  n.addRegExp = addRegExp;
  n.getEffectiveStyle = getEffectiveStyle;
  n.BrowserDetect = BrowserDetect;
  n.getAncestorByClassName = getAncestorByClassName;
  n.getElementsByClassName = getElementsByClassName;
  n.getChildElementById = getChildElementById;
  n.isChild = isChild;
  n.climbDOMTree = climbDOMTree;
  n.getElementForm = getElementForm;
  n.getRadioValueByName = getRadioValueByName;
  n.getElementPosition = getElementPosition;
  n.getElementLeft = getElementLeft;
  n.getElementWidth = getElementWidth;
  n.getElementRight = getElementRight;
  n.getElementTop = getElementTop;
  n.getElementHeight = getElementHeight;
  n.getElementBottom = getElementBottom;
  n.addStyleSheet = addStyleSheet;
  n.addLinkStyleSheet = addLinkStyleSheet;
  n.addScriptToHeader = addScriptToHeader;
  n.removeElement = removeElement;
  n.detachElement = detachElement;
  n.getData = getData;
  n.setData = setData;
  n.removeData = removeData;
  n.setHTML = setHTML;
  n.forOwnIn = forOwnIn;
  n.insertElementAfter = insertElementAfter;
  n.removeChildren = removeChildren;
  n.replaceChildren = replaceChildren;
  n.arrayOfObjectsToTable = arrayOfObjectsToTable;
  n.arrayOfObjectsToTableRows = arrayOfObjectsToTableRows;
  n.arrayOfObjectsToHeaderRow = arrayOfObjectsToHeaderRow;
  n.setOpacity = setOpacity;
  n.toCamelCase = toCamelCase;
  n.countOccurances = countOccurances;
  n.removeInvalidChars = removeInvalidChars;
  n.inputFilter = inputFilter;
  n.formatPhoneNumber = formatPhoneNumber;
  n.imposeMaxLength = imposeMaxLength;
  n.getFromURL = getFromURL;
  n.extractServerandPath = extractServerandPath;
  n.getServerNameFromURL = getServerNameFromURL;
  n.setCookie = setCookie;
  n.getCookie = getCookie;
  n.deleteCookie = deleteCookie;
  n.getRandomInRange = getRandomInRange;
  n.isValidDate = isValidDate;
  n.parseDateString = parseDateString;
  n.dateFormat = dateFormat;
  n.addSlashes = addSlashes;
  n.stripSlashes = stripSlashes;
  n.linkTextToUrl = linkTextToUrl;
  n.getNodeTree = getNodeTree;
  n.getSelection = getSelection;
  n.getRange = getRange;
  n.getTag = getTag;
  n.getParent = getParent;
  n.setFloat = setFloat;
  n.GUID = GUID;
})();


//language object
//strings are encoded with unicode \u0000 escaping to make sure they are interpreted properly
ddp.l = {
  "monthName":{
    "en":['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November','December'],
    "fr":['Janvier', 'F\u00e9vrier', 'Mars', 'Avril', 'Mai', 'Juin', 'Juillet', 'A\u00f4ut','Septembre','Octobre','Novembre','D\u00e9cembre'],
    "es":['enero', 'febrero', 'marzo', 'abril', 'mayo', 'junio', 'julio', 'agosto', 'septiembre', 'octubre', 'noviembre', 'diciembre']
  },
  "monthNameShort":{
    "en":['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov','Dec'],
    "fr":['Jan', 'F\u00e9v', 'Mars', 'Avr', 'Mai', 'Juin', 'Juil', 'A\u00f4ut','Sept','Oct','Nov','D\u00e9c'],
    "es":['ene', 'feb', 'mar', 'abr', 'may', 'jun', 'jul', 'ago', 'sep', 'oct', 'nov', 'dic']
  },
  "dayName":{
    "en":['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday'],
    "fr":['Dimanche','Lundi','Mardi','Mercredi','Jeudi','Vendredi','Samedi'],
    "es":['Domingo','Lunes','Martes','Mi\u00e9rcoles','Jueves','Viernes','S\u00e1bado']
  },
  "ok":{"en":"OK", "fr":"OK", "es":"OK"}, //useless, I guess, other than to confirm the translation
  "save":{"en":"Save","fr":"Valider","es":""}, //incomplete
  "apply":{"en":"Apply","fr":"Appliquez","es":""}, //incomplete
  "submit":{"en":"Submit","fr":"Envoi","es":""}, //incomplete (es)
  "select":{"en":"Select","fr":"S\u00e9lectionner","es":""}, //incomplete
  "selectAll":{"en":"Select All","fr":"S\u00e9lectionner tout","es":""}, //incomplete
  "cancel":{"en":"Cancel","fr":"Annul\u00e9r","es":"Cancelar"},
  "cancelAll":{"en":"Cancel All","fr":"Annul\u00e9r tout","es":""}, //incomplete
  "clear":{"en":"Clear","fr":"D\u00e9gagez??","es":"ES_Clear"}, //incomplete ---------------------------------------------------
  "delete":{"en":"Delete","fr":"Supprimer","es":"Eliminar"},
  "back":{"en":"Back","fr":"Retournez","es":""}, //incomplete
  "close":{"en":"Close","fr":"Fermer","es":"ES_Close"}, //incomplete
  "download":{"en":"Download","fr":"T\u00e9l\u00e9charger","es":""}, //incomplete
  "print":{"en":"Print", "fr":"Imprimer", "es":"Tecla de impresi\u00f3n"},
  "loading":{"en":"Loading","fr":"Chargement","es":"Cargando"},
  "username":{"en":"Username","fr":"Identifiant","es":"Usuario"},
  "password":{"en":"Password","fr":"Mot passe","es":"Contrase\u00f1a"},
  "size":{"en":"Size","fr":"Taille","es":"Tama\u00f1o"},
  "type":{"en":"Type","fr":"Type","es":"Tipo"},
  "skip":{"en":"Skip","fr":"Ignor\u00e9","es":"Omitir"},
  "pause":{"en":"Pause","fr":"Suspendre","es":"Pausar"},
  "properties":{"en":"Properties","fr":"Propri\u00e9t\u00e9s","es":"Propiedades"},
  "remove":{"en":"Remove","fr":"Annuler","es":"Eliminar"},
  "start":{"en":"Start","fr":"D\u00e9marrer","es":"Iniciar"},
  "stop":{"en":"Stop","fr":"Arr\u00eater","es":"Detener"},
  "name":{"en":"Name","fr":"Nom","es":"Nombre"},
  "status":{"en":"Status","fr":"Statut","es":"Estado"},
  "today": {"en": "Today", "fr": "Aujourd'hui", "es": "Hoy"},
  "finished":{"en":"Finished","fr":"Termin\u00e9","es":"Finalizado"}
};

