/**
 * uCode / Javascript
 * rev. 0 (1969-12-31)
 *
 * Copyright (c) 2005-2007 Pause Productions (http://www.pause.ca/)
 * This code may not be distributed or reproduced without a license.
 *
 * Packaged on 2009-05-04
 * 
 * DO NOT EDIT THIS FILE! If you find bugs in uCode, or wish to add 
 * functionality, edit the originals and repackage them.
 * 
 * The person who customized this uCode package selected:
 * - uAddress.class.php rev. UNKNOWN (dated UNKNOWN)
 * - uCoupon.class.php rev. UNKNOWN (dated UNKNOWN)
 * - uCouponBenefits.class.php rev. UNKNOWN (dated UNKNOWN)
 * - uCouponRequirements.class.php rev. UNKNOWN (dated UNKNOWN)
 * - uDb.class.php rev. UNKNOWN (dated UNKNOWN)
 * - uImage.class.php rev. UNKNOWN (dated UNKNOWN)
 * - uImage_gd2.class.php rev. UNKNOWN (dated UNKNOWN)
 * - uMail.class.php rev. UNKNOWN (dated UNKNOWN)
 * - uOrder.class.php rev. UNKNOWN (dated UNKNOWN)
 * - uPayment.class.php rev. UNKNOWN (dated UNKNOWN)
 * - uPayment_mirapayHpp.class.php rev. UNKNOWN (dated UNKNOWN)
 * - uQuery.class.php rev. UNKNOWN (dated UNKNOWN)
 * - uShip.class.php rev. UNKNOWN (dated UNKNOWN)
 * - uTax.class.php rev. UNKNOWN (dated UNKNOWN)
 * - uTemplate.class.php rev. UNKNOWN (dated UNKNOWN)
 * - uUser.class.php rev. UNKNOWN (dated UNKNOWN)
 * - uView.class.php rev. UNKNOWN (dated UNKNOWN)
 * - uXml.class.php rev. UNKNOWN (dated UNKNOWN)
 * - uCommon.js rev. UNKNOWN (dated UNKNOWN)
 * - uDropIt.class.js rev. UNKNOWN (dated UNKNOWN)
 *
 * Use this quick select string when re-packaging this file:
 * php.add|php.cpn|php.cpt|php.cpr|php.db|php.img|php.igd|php.mal|php.ord|php.pay|php.mih|php.qry|php.shp|php.tax|php.tpl|php.usr|php.viw|php.xml|js.com|js.drp
 *    ...or click here:
 * http://ucode.pause.ca/?modules=php.add+php.cpn+php.cpt+php.cpr+php.db+php.img+php.igd+php.mal+php.ord+php.pay+php.mih+php.qry+php.shp+php.tax+php.tpl+php.usr+php.viw+php.xml+js.com+js.drp
 *
 * The following were also included due to requirements:
 * - uCommon.php rev. UNKNOWN (dated UNKNOWN)
 * - uCountry.class.php rev. UNKNOWN (dated UNKNOWN)
 * - uArrayObject.class.php rev. UNKNOWN (dated UNKNOWN)
 * - uDebugInstance.class.php rev. UNKNOWN (dated UNKNOWN)
 * - uErrorInstance.class.php rev. UNKNOWN (dated UNKNOWN)
 * - uError.class.php rev. UNKNOWN (dated UNKNOWN)
 * - uValidate.php rev. UNKNOWN (dated UNKNOWN)
 * - uOrderItem.class.php rev. UNKNOWN (dated UNKNOWN)
 * - uHttp.class.php rev. UNKNOWN (dated UNKNOWN)
 * - uPayment_hpp.class.php rev. UNKNOWN (dated UNKNOWN)
 * - uSqlite.class.php rev. UNKNOWN (dated UNKNOWN)
 * - uXmlObjectList.class.php rev. UNKNOWN (dated UNKNOWN)
 * - uXmlNode.class.php rev. UNKNOWN (dated UNKNOWN)
 * - uSanitize.php rev. UNKNOWN (dated UNKNOWN)
 * - uCouponTargets.class.php rev. UNKNOWN (dated UNKNOWN)
 * - uErrorHandler.class.php rev. UNKNOWN (dated UNKNOWN)
 *
 * Make sure these sections appear in your site.ini and contain the
 * appropriate variables (you can download a pre-configured site.ini
 * file from the Package Server):
 * - [DB]
 * - [IMAGE]
 * - [MAIL]
 * - [PAYMENT]
 * - [TAX]
 * - [TEMPLATE]
 * - [USER]
 * - [XML]
 * - [ERROR]
 *
 * One or more of the included modules have supporting Javascript files.
 * To make sure these modules function properly please make sure uCode.js
 * is included in your resulting HTML. If you do not have uCode.js you can
 * generate one from the package server using the same options above.
 *
 * Some of the modules you included also have external system requirements:
 * - Does nothing on it's own, included by uCoupon
 * - Either uImage_gd2 or uImage_magick must be included
 * - GD2 Libarary compiled into PHP
 * - PHP mail(), or SMTP servers must be specified in site.ini
 * - One of the actual payment moduled (eg: Moneris) must be included
 * - Requires SQLite to be installed on the server
 * - Requires SQLite to be installed on the server, and the uCode.db file
 * - Smarty needs to be installed where uCode.php can access it
 * - Requires one of uXmlNode or uXml
 * - Document Object Model must be compiled (and enabled) in PHP
 */
/**
 * @package Pause-uCodeJS
 * @subpackage uTools
 */


/**
 * IE's (6 and 7) document.getElementById is broken horribly.
 * see: http://www.sixteensmallstones.org/ie-javascript-bugs-overriding-internet-explorers-documentgetelementbyid-to-be-w3c-compliant-exposes-an-additional-bug-in-getattributes
 * for details. This overrides it with a function that actually works
 * when running on IE browsers.
 */
if (document && document.all) //only override IE
{
	document.nativeGetElementById = document.getElementById;
	document.getElementById = function(id)
	{
		var elem = document.nativeGetElementById(id);
		if(elem)
		{
			//make sure that it is a valid match on id
			if(elem.attributes['id'].value == id)
			{
				return elem;
			}
			else
			{
				//otherwise find the correct element
				for(var i=1;i<document.all[id].length;i++)
				{
					if(document.all[id][i].attributes['id'].value == id)
					{
						return document.all[id][i];
					}
				}
			}
		}
		return null;
	};
}

/**
 * Prevents accidental errors with console.* lines left in the source
 */
if (!window.console || !console.firebug)
{
    var names = ["log", "debug", "info", "warn", "error", "assert", "dir", "dirxml",
    "group", "groupEnd", "time", "timeEnd", "count", "trace", "profile", "profileEnd"];

    window.console = {};
    for (var i = 0; i < names.length; ++i)
        window.console[names[i]] = function() {}
}


/**
 * Checks to see if the element specified exists in the array provided
 * 
 * @author Travis Richardson <travis@pause.ca>
 * @param mixed needle The value to look for
 * @param array haystack The array to search in
 * @return boolean True if the value exists, false if it was not found
 */
function inArray(needle,haystack) {
  if (!isArray(haystack)) return false;
	for (i in haystack) {
		if (haystack[i]==needle) return true;
	}
	return false;
}

/**
 * Provides similar functionality to the PHP number_format function,
 * except without the commas (,). There's a PHP function in uCommon.php
 * that provides this exact functionality in PHP.
 * 
 * Numbers are rounded to the nearest number of decimal places requested.
 * 
 * @author Travis Richardson <travis@pause.ca>
 * @param mixed value A string, integer, or decimal value
 * @param int decimals The number of decimal places to show
 * @return string value formatted with the specified number of decimal places
 */
function decimals(value,decimals) {
  if (value==='') return '';
  value = value * 1;
  if (value==NaN) return '';

  // round to that many places
  var power = Math.pow(10,decimals);
  value = Math.round(value * power) / power;
  
  // add 0's if necessary
  value = value.toString().split('.');
  if (value.length==1) value[1] = '';
  
  for (var t=value[1].length; t<decimals; t++) value[1] += '0';
  
  value = value[0]+'.'+value[1];
  
  return value;

}

function isNumeric(value) {
	if (isNaN(parseFloat(value))) return false;
	return true;
}

function isArray(value) {
  if (!isObject(value) || value.constructor.toString().indexOf("Array") == -1) return false;
  return true;
}

function isInt(value) {
	if (typeof value=='number' && Math.floor(value)==value) return true;
	return false;
}

function isString(value) {
  return typeof value == 'string';
}

function isFunction(value) {
  return typeof value == 'function';
}

function isObject(value) {
  return (typeof value == 'object' && !!value) || isFunction(value);
}

function isRegExp(value) {
  if (!isObject(value) || value.constructor.toString().indexOf("RegExp") == -1) return false;
  return true;
}

function isBool(value) {
  return typeof value == 'boolean';
}

function indexOf(obj,key) {
	for (i in obj) {
		if (obj[i]==key) return i;
	}
	return -1;
}

function hasClass(obj,className) {
	var classes = obj.className.split(' ');
	if (indexOf(classes,className)==-1) return(false);
	return(true);
}

function addClass(obj,className) {
	if (hasClass(obj,className)) return;
	var classes = obj.className.split(' ');
	classes.push(className);
	obj.className = classes.join(' ');
}

function removeClass(obj,className) {
	if (!hasClass(obj,className)) return;
	var classes = obj.className.split(' ');
	var newClasses = [];
	for (var t=0; t<classes.length; t++) {
		if (classes[t]==className) continue;
		newClasses.push(classes[t]);
	}
	obj.className = newClasses.join(' ');
}


/**
 * Used from the Prototype Libaray (with minor modifications)
 */
Function.prototype.bindAsEventListener = function(object) {
  var __method = this;
  var args = [];
  for (var t=0; t<arguments.length; t++) args.push(arguments[t]);
  var object = args.shift();
  return function(event) {
    return __method.apply(object, [event || window.event].concat(args));
  }
}

/*
Function.prototype.bindAsEventListener = function(object) {
  var __method = this;
  return function(event) {
    return __method.call(object, event || window.event);
  }
}
*/

function ObserveEvent(element, name, observer) {
  element = $(element);
  if (element.addEventListener) {
    element.addEventListener(name, observer, true);
  } else if (element.attachEvent) {
    element.attachEvent('on' + name, observer);
  }
}

function StopObservingEvent(element,name,observer) {
  if (element.removeEventListener) {
    element.removeEventListener(name, observer, false);
  } else if (element.detachEvent) {
    element.detachEvent('on' + name, observer);
  }
}

var _CACHEDIDELEMENTS = {};

/**
 * Duplicates the functionality of document.getElementById in an easy to use form
 * 
 * This code was originally borrowed from the Prototype library and modified with
 * caching abilties. Since documnet.getElementById is slow (it searches the entire
 * DOM tree everytime) the results are cached in a local hash (_CACHEDIDELEMENTS)
 * for quick access the next time. Care is taken to remove links to elements that
 * no longer exist in the DOM tree.
 * 
 * @param mixed Any number of elements or element ids (eg: element1,'id2',element3)
 * @return mixed If multiple elements or ids were passed to $, an array of matching
 * elements is returned (elements that were not found will not be in the array). If
 * a single paramter was passed a single HTMLElement is returned. If nothing is
 * found, the return value is undefined.
 */
function $() {
  var elements = new Array();

  for (var i = 0; i < arguments.length; i++) {
    var element = arguments[i];
    if (typeof element == 'string') {
    	// check to see if the element has been cached (document.getElementById is slow, so
    	// we cache the results in _CACHEDIDELEMENTS to make it faster after the first time).
    	// since we cache we also need to check to see if the element is still part of the
    	// DOM, we do this by checking for a parent node. If it's cached, but doesn't have
    	// a parent node, then it's probably been removed from the dom and we need to re-
    	// fetch it (it might be re-created, not exist anymore, etc).
    	if (!_CACHEDIDELEMENTS[element] || !_CACHEDIDELEMENTS[element].parentNode) _CACHEDIDELEMENTS[element] = document.getElementById(element);
    	element = _CACHEDIDELEMENTS[element];
    }

    if (arguments.length == 1)
      return element;

    elements.push(element);
  }

  return elements;
};

function getPosition(element)
{
    return [DL_GetElementLeft(element),DL_GetElementTop(element)];
}

function getStyle(element, style) {
    element = $(element);
    var value = element.style[camelize(style)];
    if (!value) {
      if (document.defaultView && document.defaultView.getComputedStyle) {
        var css = document.defaultView.getComputedStyle(element, null);
        value = css ? css.getPropertyValue(style) : null;
      } else if (element.currentStyle) {
        value = element.currentStyle[camelize(style)];
      }
    }

    if (window.opera && ['left', 'top', 'right', 'bottom'].include(style))
      if (Element.getStyle(element, 'position') == 'static') value = 'auto';

    return value == 'auto' ? null : value;
  }

  
  function positionedOffset(element) {
    var valueT = 0, valueL = 0;
    do {
      valueT += element.offsetTop  || 0;
      valueL += element.offsetLeft || 0;
      element = element.offsetParent;
      if (element) {
        p = getStyle(element, 'position');
        if (p == 'relative' || p == 'absolute') break;
      }
    } while (element);
    return [valueL, valueT];
  }
  
  function camelize(value) {
    var oStringList = value.split('-');
    if (oStringList.length == 1) return oStringList[0];

    var camelizedString = value.indexOf('-') == 0
      ? oStringList[0].charAt(0).toUpperCase() + oStringList[0].substring(1)
      : oStringList[0];

    for (var i = 1, len = oStringList.length; i < len; i++) {
      var s = oStringList[i];
      camelizedString += s.charAt(0).toUpperCase() + s.substring(1);
    }

    return camelizedString;
  };
  
  /*
  
function getXY(el){ // this initially used Position.cumulativeOffset but it is not accurate enough
        var p, pe, b, scroll, bd = document.body;
        el = $(el);

        if(el.getBoundingClientRect){ // IE
            b = el.getBoundingClientRect();
            scroll = fly(document).getScroll();
            return [b.left + scroll.left, b.top + scroll.top];
        } else{
            var x = el.offsetLeft, y = el.offsetTop;
            p = el.offsetParent;

            // ** flag if a parent is positioned for Safari
            var hasAbsolute = false;

            if(p != el){
                while(p){
                    x += p.offsetLeft;
                    y += p.offsetTop;

                    // ** flag Safari abs position bug - only check if needed
                    if(Ext.isSafari && !hasAbsolute && fly(p).getStyle("position") == "absolute"){
                        hasAbsolute = true;
                    }

                    // ** Fix gecko borders measurements
                    // Credit jQuery dimensions plugin for the workaround
                    if(Ext.isGecko){
                        pe = fly(p);
                        var bt = parseInt(pe.getStyle("borderTopWidth"), 10) || 0;
                        var bl = parseInt(pe.getStyle("borderLeftWidth"), 10) || 0;

                        // add borders to offset
                        x += bl;
                        y += bt;

                        // Mozilla removes the border if the parent has overflow property other than visible
                        if(p != el && pe.getStyle('overflow') != 'visible'){
                            x += bl;
                            y += bt;
                        }
                    }
                    p = p.offsetParent;
                }
            }
            // ** safari doubles in some cases, use flag from offsetParent's as well
            if(Ext.isSafari && (hasAbsolute || fly(el).getStyle("position") == "absolute")){
                x -= bd.offsetLeft;
                y -= bd.offsetTop;
            }
        }

        p = el.parentNode;

        while(p && p != bd){
            // ** opera TR has bad scroll values, so filter them jvs
            if(!Ext.isOpera || (Ext.isOpera && p.tagName != 'TR' && fly(p).getStyle("display") != "inline")){
                x -= p.scrollLeft;
                y -= p.scrollTop;
            }
            p = p.parentNode;
        }
        return [x, y];
    },

    setXY : function(el, xy){ // this initially used Position.cumulativeOffset but it is not accurate enough
        el = Ext.fly(el, '_setXY');
        el.position();
        var pts = el.translatePoints(xy);
        if(xy[0] !== false){
            el.dom.style.left = pts.left + "px";
        }
        if(xy[1] !== false){
            el.dom.style.top = pts.top + "px";
        }
    },

    setX : function(el, x){
        this.setXY(el, [x, false]);
    },

    setY : function(el, y){
        this.setXY(el, [false, y]);
    }
};

*/


/**
 * Used from http://www.webreference.com/dhtml/diner/realpos4/9.html
 */
function DL_GetElementLeft(eElement)
{
   if (!eElement && this)                    // if argument is invalid
   {                                         // (not specified, is null or is 0)
      eElement = this;                       // and function is a method
   }                                         // identify the element as the method owner

   var DL_bIE = document.all ? true : false; // initialize var to identify IE

   var nLeftPos = eElement.offsetLeft;       // initialize var to store calculations
   var eParElement = eElement.offsetParent;  // identify first offset parent element

   while (eParElement != null)
   {                                         // move up through element hierarchy

      if(DL_bIE)                             // if browser is IE, then...
      {
         if( (eParElement.tagName != "TABLE") && (eParElement.tagName != "BODY") )
         {                                   // if parent is not a table or the body, then...
            nLeftPos += eParElement.clientLeft; // append cell border width to calcs
         }
      }
      else                                   // if browser is Gecko, then...
      {
         if(eParElement.tagName == "TABLE")  // if parent is a table, then...
         {                                   // get its border as a number
            var nParBorder = parseInt(eParElement.border);
            if(isNaN(nParBorder))            // if no valid border attribute, then...
            {                                // check the table's frame attribute
               var nParFrame = eParElement.getAttribute('frame');
               if(nParFrame != null)         // if frame has ANY value, then...
               {
                  nLeftPos += 1;             // append one pixel to counter
               }
            }
            else if(nParBorder > 0)          // if a border width is specified, then...
            {
               nLeftPos += nParBorder;       // append the border width to counter
            }
         }
      }
      nLeftPos += eParElement.offsetLeft;    // append left offset of parent
      eParElement = eParElement.offsetParent; // and move up the element hierarchy
   }                                         // until no more offset parents exist
   return nLeftPos;                          // return the number calculated
}

function DL_GetElementTop(eElement)
{
   if (!eElement && this)                    // if argument is invalid
   {                                         // (not specified, is null or is 0)
      eElement = this;                       // and function is a method
   }                                         // identify the element as the method owner

   var DL_bIE = document.all ? true : false; // initialize var to identify IE

   var nTopPos = eElement.offsetTop;         // initialize var to store calculations
   var eParElement = eElement.offsetParent;  // identify first offset parent element

   while (eParElement != null)
   {                                         // move up through element hierarchy
      if(DL_bIE)                             // if browser is IE, then...
      {
         if( (eParElement.tagName != "TABLE") && (eParElement.tagName != "BODY") )
         {                                   // if parent a table cell, then...
            nTopPos += eParElement.clientTop; // append cell border width to calcs
         }
      }
      else                                   // if browser is Gecko, then...
      {
         if(eParElement.tagName == "TABLE")  // if parent is a table, then...
         {                                   // get its border as a number
            var nParBorder = parseInt(eParElement.border);
            if(isNaN(nParBorder))            // if no valid border attribute, then...
            {                                // check the table's frame attribute
               var nParFrame = eParElement.getAttribute('frame');
               if(nParFrame != null)         // if frame has ANY value, then...
               {
                  nTopPos += 1;              // append one pixel to counter
               }
            }
            else if(nParBorder > 0)          // if a border width is specified, then...
            {
               nTopPos += nParBorder;        // append the border width to counter
            }
         }
      }

      nTopPos += eParElement.offsetTop;      // append top offset of parent
      eParElement = eParElement.offsetParent; // and move up the element hierarchy
   }                                         // until no more offset parents exist
   return nTopPos;                           // return the number calculated
}


function toggle(id) {
	id = $(id);
	if (id.style.display=='none') show(id);
	else hide(id);
}

function show(id) {
	id = $(id);
	if (id) id.style.display = '';
}

function hide(id) {
	id = $(id);
	if (id) id.style.display = 'none';
}




/**
 * Used from http://jquery.com/ (jquery dimensions plugin)
 * with heavy modifications to make it (mostly) standalone
 */
/*
	var b = navigator.userAgent.toLowerCase();

	// Figure out what browser is being used
	var _browser = {};
	_browser.safari = b.match(/webkit/);
	_browser.opera = b.match(/opera/);
	_browser.msie = b.match(/msie/);
	_browser.mozilla = (b.match(/mozilla/) && !b.match(/compatible|webkit/));
	_browser.boxModel = !_browser.msie || document.compatMode == "CSS1Compat";


	function offset(elem) {
		var x = 0, y = 0, sl = 0, st = 0,
		    parent = elem, op, parPos, elemPos = getStyle(elem, 'position'),
		    mo = _browser.mozilla, ie = _browser.msie, sf = _browser.safari, oa = _browser.opera,
		    absparent = false, relparent = false, 
		    options = { margin: true, border: true, padding: false, scroll: true, lite: false };
		
		if (elem.tagName.toLowerCase() == 'body') {
			// Safari is the only one to get offsetLeft and offsetTop properties of the body "correct"
			// Except they all mess up when the body is positioned absolute or relative
			x = elem.offsetLeft;
			y = elem.offsetTop;
			// Mozilla ignores margin and subtracts border from body element
			if (mo) {
				x += parseInt(getStyle(elem, 'marginLeft')) + (parseInt(getStyle(elem, 'borderLeftWidth'))*2);
				y += parseInt(getStyle(elem, 'marginTop'))  + (parseInt(getStyle(elem, 'borderTopWidth')) *2);
			} else
			// Opera ignores margin
			if (oa) {
				x += parseInt(getStyle(elem, 'marginLeft'));
				y += parseInt(getStyle(elem, 'marginTop'));
			} else
			// IE does not add the border in Standards Mode
			if (ie && jQuery.boxModel) {
				x += parseInt(getStyle(elem, 'borderLeftWidth'));
				y += parseInt(getStyle(elem, 'borderTopWidth'));
			}
		} else {
			do {
				parPos = getStyle(parent, 'position');
			
				x += parent.offsetLeft;
				y += parent.offsetTop;

				// Mozilla and IE do not add the border
				if (mo || ie) {
					// add borders to offset
					x += parseInt(getStyle(parent, 'borderLeftWidth'));
					y += parseInt(getStyle(parent, 'borderTopWidth'));

					// Mozilla does not include the border on body if an element isn't positioned absolute and is without an absolute parent
					if (mo && parPos == 'absolute') absparent = true;
					// IE does not include the border on the body if an element is position static and without an absolute or relative parent
					if (ie && parPos == 'relative') relparent = true;
				}

				op = parent.offsetParent;
				do {
					if (options.scroll) {
						sl += parent.scrollLeft;
						st += parent.scrollTop;
					}
				
					if (mo && parent != elem && getStyle(parent, 'overflow') != 'visible') {
						x += parseInt(getStyle(parent, 'borderLeftWidth'));
						y += parseInt(getStyle(parent, 'borderTopWidth'));
					}
				
					parent = parent.parentNode;
				} while (parent != op);
				parent = op;

				if (parent.tagName.toLowerCase() == 'body' || parent.tagName.toLowerCase() == 'html') {
					// Safari and IE Standards Mode doesn't add the body margin for elments positioned with static or relative
					if ((sf || (ie && _browser.boxModel)) && elemPos != 'absolute' && elemPos != 'fixed') {
						x += parseInt(getStyle(parent, 'marginLeft'));
						y += parseInt(getStyle(parent, 'marginTop'));
					}
					// Mozilla does not include the border on body if an element isn't positioned absolute and is without an absolute parent
					// IE does not include the border on the body if an element is positioned static and without an absolute or relative parent
					if ( (mo && !absparent && elemPos != 'fixed') || 
					     (ie && elemPos == 'static' && !relparent) ) {
						x += parseInt(getStyle(parent, 'borderLeftWidth'));
						y += parseInt(getStyle(parent, 'borderTopWidth'));
					}
					break; // Exit the loop
				}
			} while (parent);
		}

		var returnValue = handleOffsetReturn(elem, options, x, y, sl, st);

		return returnValue;
	}
	
function handleOffsetReturn(elem, options, x, y, sl, st) {
	if ( !options.margin ) {
		x -= parseInt(getStyle(elem, 'marginLeft'));
		y -= parseInt(getStyle(elem, 'marginTop'));
	}

	// Safari and Opera do not add the border for the element
	if ( options.border && (_browser.safari || _browser.opera) ) {
		x += parseInt(getStyle(elem, 'borderLeftWidth'));
		y += parseInt(getStyle(elem, 'borderTopWidth'));
	} else if ( !options.border && !(_browser.safari || _browser.opera) ) {
		x -= parseInt(getStyle(elem, 'borderLeftWidth'));
		y -= parseInt(getStyle(elem, 'borderTopWidth'));
	}

	if ( options.padding ) {
		x += parseInt(getStyle(elem, 'paddingLeft'));
		y += parseInt(getStyle(elem, 'paddingTop'));
	}
	
	// do not include scroll offset on the element
	if ( options.scroll ) {
		sl -= elem.scrollLeft;
		st -= elem.scrollTop;
	}

	return options.scroll ? { top: y - st, left: x - sl, scrollTop:  st, scrollLeft: sl }
	                      : { top: y, left: x };
};
	*/
	
	
/**
 * Duplicates the functionality of the PHP setcookie function in javascript
 * 
 * @author Travis Richardson <travis@pause.ca>
 * @param string name The name of the cookie to set
 * @param string value The value of the cookie
 * @param int expires The timestamp (GMT) when the cookie will expire (default is to set a session cookie that 
 * @param string path The path to limit the cookie to (default is /)
 * @return boolean True
 */
function setCookie(name,value,expires,path) {
	value = escape(value);
	path = path || '/';
	if (expires) {
		var date = new Date();
		date.setTime(date.getTime()+(expires*1000));
		var expires = "; expires="+date.toGMTString();
	}
	else var expires = "";
	document.cookie = name+"="+value+expires+"; path="+path;
	return true;
}

/**
 * Retrieves a cookie value
 * 
 * @author Travis Richardson <travis@pause.ca>
 * @param string name The name of the cookie to retrieve
 * @return string The value of the cookie specified, or null if not found
 */
function getCookie(name) {
	var nameEQ = name + "=";
	var ca = document.cookie.split(';');
	for(var i=0;i < ca.length;i++) {
		var c = ca[i];
		while (c.charAt(0)==' ') c = c.substring(1,c.length);
		if (c.indexOf(nameEQ) == 0) {
			var value = unescape(c.substring(nameEQ.length,c.length));
			return value;
		}
	}
	return null;
}

/**
 * Removes the cookie specified
 * 
 * Exactly the same as calling setCookie(name,'',-1);
 * 
 * @author Travis Richardson <travis@pause.ca>
 * @param string name The name of the cookie to delete
 * @return boolean True
 * @see setCookie()
 */
function deleteCookie(name) {
	return setCookie(name,"",-1);
}


/**
 * Marks one or more options in a select box as "selected"
 * 
 * @param mixed element The ID of a SELECT box, or the SELECT element itself
 * @param mixed values A single string or an array of strings to be selected
 * @return boolean True on success, false if the ID you passed was not found
 * or was not a SELECT element.
 */
function setSelect(element,values) {
  element = $(element);
  if (!element) return false;
  if (element.tagName!='SELECT') return false;
  
	if (!isArray(values)) values = [values];
	
	for (var t=0; t<element.options.length; t++) {
		var value = element.options[t].value;
		if (inArray(value,values)) element.options[t].selected = true;
		else element.options[t].selected = false;
	}
	
	return true;
}

function setInputValue(element,values) {
	element = $(element);
	if (!element) return false;
	if (element.tagName=='SELECT') {
		return setSelect(element,values);
	}
	else {
		element.value = values;
	}
	return true;
}
	
/**
 * Merges and returns supplied NodeLists.  
 * @author Shajinder Padda <shajinder@pause.ca>
 * @param mixed arguments Node lists seperated by '...'
 * @return mixed Returns false if no arguments are supplied or the merged array list on success
 */
function nodeMerge(){
	if (arguments.length < 1){ 
		return false;
	}

	var allNodes = Array();

	for (t = 0; t < arguments.length; t++){
		//Combine the inputs and selects 
		for (x = 0; x < arguments[t].length; x++){
			allNodes.push( arguments[t][x] );
		} 
	}

	return allNodes;	
}


function array_unique( array ) {
	var newArray = [];
	for (i in array) {
		var val = array[i];
		if (inArray(val,newArray)) {
			continue;
		}
		newArray.push(val);
	}
	return newArray;
}

/**
 * Stop an event cold in it's tracks
 */
function cancelEvent(e){
	e = e ? e : window.event;
	if(e.stopPropagation)
	  e.stopPropagation();
	if(e.preventDefault)
	  e.preventDefault();
	e.cancelBubble = true;
	e.cancel = true;
	e.returnValue = false;
	return false;
}

/*
  Developed by Robert Nyman, http://www.robertnyman.com
  v. 1.0.1 May 27, 2008
  Utilizes native getElementsByClassName and XPath support, with a fallback to regular looping. Returns a real array instead of a node list. 
  Supports IE 5.5+ and basically any other web browser being used today.
  Code/licensing: http://code.google.com/p/getelementsbyclassname/
  MIT License
  className = One or several class names, separated by space. Multiple class names demands that each match have all of the classes specified. Mandatory.
  tag = Specifies the tag name of the elements to match. Optional.
  elm = Reference to a DOM element to look amongst its children for matches. Recommended for better performance in larger documents. Optional.
*/
var getElementsByClassName = function (className, tag, elm){
	if (document.getElementsByClassName) {
		getElementsByClassName = function (className, tag, elm) {
			elm = elm || document;
			var elements = elm.getElementsByClassName(className),
				nodeName = (tag)? new RegExp("\\b" + tag + "\\b", "i") : null,
				returnElements = [],
				current;
			for(var i=0, il=elements.length; i<il; i+=1){
				current = elements[i];
				if(!nodeName || nodeName.test(current.nodeName)) {
					returnElements.push(current);
				}
			}
			return returnElements;
		};
	}
	else if (document.evaluate) {
		getElementsByClassName = function (className, tag, elm) {
			tag = tag || "*";
			elm = elm || document;
			var classes = className.split(" "),
				classesToCheck = "",
				xhtmlNamespace = "http://www.w3.org/1999/xhtml",
				namespaceResolver = (document.documentElement.namespaceURI === xhtmlNamespace)? xhtmlNamespace : null,
				returnElements = [],
				elements,
				node;
			for(var j=0, jl=classes.length; j<jl; j+=1){
				classesToCheck += "[contains(concat(' ', @class, ' '), ' " + classes[j] + " ')]";
			}
			try	{
				elements = document.evaluate(".//" + tag + classesToCheck, elm, namespaceResolver, 0, null);
			}
			catch (e) {
				elements = document.evaluate(".//" + tag + classesToCheck, elm, null, 0, null);
			}
			while ((node = elements.iterateNext())) {
				returnElements.push(node);
			}
			return returnElements;
		};
	}
	else {
		getElementsByClassName = function (className, tag, elm) {
			tag = tag || "*";
			elm = elm || document;
			var classes = className.split(" "),
				classesToCheck = [],
				elements = (tag === "*" && elm.all)? elm.all : elm.getElementsByTagName(tag),
				current,
				returnElements = [],
				match;
			for(var k=0, kl=classes.length; k<kl; k+=1){
				classesToCheck.push(new RegExp("(^|\\s)" + classes[k] + "(\\s|$)"));
			}
			for(var l=0, ll=elements.length; l<ll; l+=1){
				current = elements[l];
				match = false;
				for(var m=0, ml=classesToCheck.length; m<ml; m+=1){
					match = classesToCheck[m].test(current.className);
					if (!match) {
						break;
					}
				}
				if (match) {
					returnElements.push(current);
				}
			}
			return returnElements;
		};
	}
	return getElementsByClassName(className, tag, elm);
};

/**
 * @package Pause-uCodeJS
 * @subpackage uTools
 */
var uDropIt = function(ids,data) {
  this.initialize(ids,data);
}
uDropIt.prototype = {

	initialize: function(ids,data) {

		if (ids instanceof Array) {
			this.id1 = ids[0];
			this.id2 = ids[1];
		}
		else if (typeof ids == 'string') {
			this.id1 = ids;
		}
		else {
			alert("Passed bad parameter 1 to uDropIt (string or array accepted)");
			return false;
		}

		this.element1 = document.getElementById(this.id1);
		if (!this.element1) {
			alert(this.id1+' not found!');
			return false;
		}
		if (this.element1.tagName!='SELECT') {
			alert(this.id1+' is not a select box!');
			return false;
		}

		if (this.id2) {
			this.element2 = document.getElementById(this.id2);
			if (!this.element2) {
				alert(this.id2+' not found!');
				return false;
			}
			if (this.element2.tagName!='SELECT') {
				alert(this.id2+' is not a select box!');
				return false;
			}
		}
		else {
			// if a second dropdown isn't specified, create one beside the first
			if (!this.element2) {
				this.element2 = document.createElement('select');
				this.element1.parentNode.insertBefore(this.element2,this.element1);
				this.element1.parentNode.removeChild(this.element1);
				this.element2.parentNode.insertBefore(this.element1,this.element2);
				this.element2.setAttribute('tabindex',this.element1.getAttribute('tabindex'));
				this.element2.id = this.element1.getAttribute('id')+"_2";
			}
		}

		var success = this.analyze();
		if (success) this.split();

	},

	analyze: function() {
		// find all elements in id2 and place them in an array
		this.options1 = {};
		this.options2 = {};

		var nodes = this.element1.getElementsByTagName('*');

		// if there aren't any optgroups in the select, simply leave everything the way it is...
		if (nodes.length==0) return true;

		// loop through each, getting values
		for (var t=0; t<nodes.length; t++) {
			var node = nodes[t];

			if (node.tagName=='OPTGROUP') {
				var value = node.label;
				var text = node.label;
				this.options1[value] = text;
				this.options2[value] = {};
			}

			if (node.tagName=='OPTION') {
				var value = node.value;
				var text = node.text;

				if (node.parentNode.tagName=='OPTGROUP') {
					// second level
					var parentValue = node.parentNode.label;
					this.options2[parentValue][value] = text;
					if (node.selected) {
						this.selected1 = parentValue;
						this.selected2 = value;
					}
				}
				else {
					this.options1[value] = text;
				}
			}

		}

    return true;

	},

	split: function() {
		// redraw the original dropdown with only first level attributes
		this.element1.innerHTML = '';
		for (var i in this.options1) {
			// ignore functions (this would not be necessary if the array object wasn't extended)
			if (typeof this.options1[i] != 'string') continue;
			var value = i;
			var text = this.options1[i];
			var option = new Option(text,value);
			if (this.selected1==value) option.selected = true;
			this.element1.options[this.element1.options.length] = option;
		}

		// copy the name of the first to the second and clear the first
		// (the second element is now our 'real' value)
  	this.element2.name = this.element1.name;
  	this.element1.name = '';

		// attach an onchange event so we can draw the second dropdown
		ObserveEvent(this.element1,'change',this.update.bindAsEventListener(this));

		this.update();

		// clear pre-selected values
		this.selected1 = null;
		this.selected2 = null;
	},

	update: function() {
		// draw the second dropdown based on the selection of the first
		var value1 = undefined;
		var text1 = undefined;
		for (var t=0; t<this.element1.options.length; t++) {
			if (!this.element1.options[t].selected) continue;
			value1 = this.element1.options[t].value;
			text1 = this.element1.options[t].text;
			break;
		}

		this.element2.innerHTML = '';
		var options2 = {};
		options2[value1] = text1;
		if (this.options2[value1]) options2 = this.options2[value1];

		for (i in options2) {
			// ignore functions (this would not be necessary if the array object wasn't extended)
			if (typeof options2[i] != 'string') continue;
			var value2 = i;
			var text2 = options2[i];
			var option = new Option(text2,value2);
			if (this.selected2==value2) option.selected = true;
			this.element2.options[this.element2.options.length] = option;
		}

	}

}



