// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
// Contributors:
//  Justin Palmer (http://encytemedia.com/)
//  Mark Pilgrim (http://diveintomark.org/)
//  Martin Bialasinki
// 
// See scriptaculous.js for full license.  

/* ------------- element ext -------------- */  
 
// converts rgb() and #xxx to #xxxxxx format,  
// returns self (or first argument) if not convertable  
String.prototype.parseColor = function() {  
  var color = '#';  
  if(this.slice(0,4) == 'rgb(') {  
    var cols = this.slice(4,this.length-1).split(',');  
    var i=0; do { color += parseInt(cols[i]).toColorPart() } while (++i<3);  
  } else {  
    if(this.slice(0,1) == '#') {  
      if(this.length==4) for(var i=1;i<4;i++) color += (this.charAt(i) + this.charAt(i)).toLowerCase();  
      if(this.length==7) color = this.toLowerCase();  
    }  
  }  
  return(color.length==7 ? color : (arguments[0] || this));  
}  

Element.collectTextNodesIgnoreClass = function(element, ignoreclass) {  
  var children = $(element).childNodes;  
  var text     = '';  
  var classtest = new RegExp('^([^ ]+ )*' + ignoreclass+ '( [^ ]+)*$','i');  
 
  for (var i = 0; i < children.length; i++) {  
    if(children[i].nodeType==3) {  
      text+=children[i].nodeValue;  
    } else {  
      if((!children[i].className.match(classtest)) && children[i].hasChildNodes())  
        text += Element.collectTextNodesIgnoreClass(children[i], ignoreclass);  
    }  
  }  
 
  return text;
}

Element.setStyle = function(element, style) {
  element = $(element);
  for(k in style) element.style[k.camelize()] = style[k];
}

Element.setContentZoom = function(element, percent) {  
  Element.setStyle(element, {fontSize: (percent/100) + 'em'});   
  if(navigator.appVersion.indexOf('AppleWebKit')>0) window.scrollBy(0,0);  
}

Element.getOpacity = function(element){  
  var opacity;
  if (opacity = Element.getStyle(element, 'opacity'))  
    return parseFloat(opacity);  
  if (opacity = (Element.getStyle(element, 'filter') || '').match(/alpha\(opacity=(.*)\)/))  
    if(opacity[1]) return parseFloat(opacity[1]) / 100;  
  return 1.0;  
}

Element.setOpacity = function(element, value){  
  element= $(element);  
  if (value == 1){
    Element.setStyle(element, { opacity: 
      (/Gecko/.test(navigator.userAgent) && !/Konqueror|Safari|KHTML/.test(navigator.userAgent)) ? 
      0.999999 : null });
    if(/MSIE/.test(navigator.userAgent))  
      Element.setStyle(element, {filter: Element.getStyle(element,'filter').replace(/alpha\([^\)]*\)/gi,'')});  
  } else {  
    if(value < 0.00001) value = 0;  
    Element.setStyle(element, {opacity: value});
    if(/MSIE/.test(navigator.userAgent))  
     Element.setStyle(element, 
       { filter: Element.getStyle(element,'filter').replace(/alpha\([^\)]*\)/gi,'') +
                 'alpha(opacity='+value*100+')' });  
  }   
}  
 
Element.getInlineOpacity = function(element){  
  return $(element).style.opacity || '';
}  

Element.childrenWithClassName = function(element, className) {  
  return $A($(element).getElementsByTagName('*')).select(
    function(c) { return Element.hasClassName(c, className) });
}

Array.prototype.call = function() {
  var args = arguments;
  this.each(function(f){ f.apply(this, args) });
}

/*--------------------------------------------------------------------------*/

var Effect = {
  tagifyText: function(element) {
    var tagifyStyle = 'position:relative';
    if(/MSIE/.test(navigator.userAgent)) tagifyStyle += ';zoom:1';
    element = $(element);
    $A(element.childNodes).each( function(child) {
      if(child.nodeType==3) {
        child.nodeValue.toArray().each( function(character) {
          element.insertBefore(
            Builder.node('span',{style: tagifyStyle},
              character == ' ' ? String.fromCharCode(160) : character), 
              child);
        });
        Element.remove(child);
      }
    });
  },
  multiple: function(element, effect) {
    var elements;
    if(((typeof element == 'object') || 
        (typeof element == 'function')) && 
       (element.length))
      elements = element;
    else
      elements = $(element).childNodes;
      
    var options = Object.extend({
      speed: 0.1,
      delay: 0.0
    }, arguments[2] || {});
    var masterDelay = options.delay;

    $A(elements).each( function(element, index) {
      new effect(element, Object.extend(options, { delay: index * options.speed + masterDelay }));
    });
  }
};

var Effect2 = Effect; // deprecated

/* ------------- transitions ------------- */

Effect.Transitions = {}

Effect.Transitions.linear = function(pos) {
  return pos;
}
Effect.Transitions.sinoidal = function(pos) {
  return (-Math.cos(pos*Math.PI)/2) + 0.5;
}
Effect.Transitions.reverse  = function(pos) {
  return 1-pos;
}
Effect.Transitions.flicker = function(pos) {
  return ((-Math.cos(pos*Math.PI)/4) + 0.75) + Math.random()/4;
}
Effect.Transitions.wobble = function(pos) {
  return (-Math.cos(pos*Math.PI*(9*pos))/2) + 0.5;
}
Effect.Transitions.pulse = function(pos) {
  return (Math.floor(pos*10) % 2 == 0 ? 
    (pos*10-Math.floor(pos*10)) : 1-(pos*10-Math.floor(pos*10)));
}
Effect.Transitions.none = function(pos) {
  return 0;
}
Effect.Transitions.full = function(pos) {
  return 1;
}

/* ------------- core effects ------------- */

Effect.Queue = {
  effects:  [],
  _each: function(iterator) {
    this.effects._each(iterator);
  },
  interval: null,
  add: function(effect) {
    var timestamp = new Date().getTime();
    
    switch(effect.options.queue) {
      case 'front':
        // move unstarted effects after this effect  
        this.effects.findAll(function(e){ return e.state=='idle' }).each( function(e) {
            e.startOn  += effect.finishOn;
            e.finishOn += effect.finishOn;
          });
        break;
      case 'end':
        // start effect after last queued effect has finished
        timestamp = this.effects.pluck('finishOn').max() || timestamp;
        break;
    }
    
    effect.startOn  += timestamp;
    effect.finishOn += timestamp;
    this.effects.push(effect);
    if(!this.interval) 
      this.interval = setInterval(this.loop.bind(this), 40);
  },
  remove: function(effect) {
    this.effects = this.effects.reject(function(e) { return e==effect });
    if(this.effects.length == 0) {
      clearInterval(this.interval);
      this.interval = null;
    }
  },
  loop: function() {
    var timePos = new Date().getTime();
    this.effects.invoke('loop', timePos);
  }
}
Object.extend(Effect.Queue, Enumerable);

Effect.Base = function() {};
Effect.Base.prototype = {
  position: null,
  setOptions: function(options) {
    this.options = Object.extend({
      transition: Effect.Transitions.sinoidal,
      duration:   1.0,   // seconds
      fps:        25.0,  // max. 25fps due to Effect.Queue implementation
      sync:       false, // true for combining
      from:       0.0,
      to:         1.0,
      delay:      0.0,
      queue:      'parallel'
    }, options || {});
  },
  start: function(options) {
    this.setOptions(options || {});
    this.currentFrame = 0;
    this.state        = 'idle';
    this.startOn      = this.options.delay*1000;
    this.finishOn     = this.startOn + (this.options.duration*1000);
    this.event('beforeStart');
    if(!this.options.sync) Effect.Queue.add(this);
  },
  loop: function(timePos) {
    if(timePos >= this.startOn) {
      if(timePos >= this.finishOn) {
        this.render(1.0);
        this.cancel();
        this.event('beforeFinish');
        if(this.finish) this.finish(); 
        this.event('afterFinish');
        return;  
      }
      var pos   = (timePos - this.startOn) / (this.finishOn - this.startOn);
      var frame = Math.round(pos * this.options.fps * this.options.duration);
      if(frame > this.currentFrame) {
        this.render(pos);
        this.currentFrame = frame;
      }
    }
  },
  render: function(pos) {
    if(this.state == 'idle') {
      this.state = 'running';
      this.event('beforeSetup');
      if(this.setup) this.setup();
      this.event('afterSetup');
    }
    if(this.state == 'running') {
      if(this.options.transition) pos = this.options.transition(pos);
      pos *= (this.options.to-this.options.from);
      pos += this.options.from;
      this.position = pos;
      this.event('beforeUpdate');
      if(this.update) this.update(pos);
      this.event('afterUpdate');
    }
  },
  cancel: function() {
    if(!this.options.sync) Effect.Queue.remove(this);
    this.state = 'finished';
  },
  event: function(eventName) {
    if(this.options[eventName + 'Internal']) this.options[eventName + 'Internal'](this);
    if(this.options[eventName]) this.options[eventName](this);
  },
  inspect: function() {
    return '#<Effect:' + $H(this).inspect() + ',options:' + $H(this.options).inspect() + '>';
  }
}


Effect.Scale = Class.create();
Object.extend(Object.extend(Effect.Scale.prototype, Effect.Base.prototype), {
  initialize: function(element, percent) {
    this.element = $(element)
    var options = Object.extend({
      scaleX: true,
      scaleY: true,
      scaleContent: true,
      scaleFromCenter: false,
      scaleMode: 'box',        // 'box' or 'contents' or {} with provided values
      scaleFrom: 100.0,
      scaleTo:   percent
    }, arguments[2] || {});
    this.start(options);
  },
  setup: function() {
    this.restoreAfterFinish = this.options.restoreAfterFinish || false;
    this.elementPositioning = Element.getStyle(this.element,'position');
    
    this.originalStyle = {};
    ['top','left','width','height','fontSize'].each( function(k) {
      this.originalStyle[k] = this.element.style[k];
    }.bind(this));
      
    this.originalTop  = this.element.offsetTop;
    this.originalLeft = this.element.offsetLeft;
    
    var fontSize = Element.getStyle(this.element,'font-size') || '100%';
    ['em','px','%'].each( function(fontSizeType) {
      if(fontSize.indexOf(fontSizeType)>0) {
        this.fontSize     = parseFloat(fontSize);
        this.fontSizeType = fontSizeType;
      }
    }.bind(this));
    
    this.factor = (this.options.scaleTo - this.options.scaleFrom)/100;
    
    this.dims = null;
    if(this.options.scaleMode=='box')
      this.dims = [this.element.offsetHeight, this.element.offsetWidth];
    if(/^content/.test(this.options.scaleMode))
      this.dims = [this.element.scrollHeight, this.element.scrollWidth];
    if(!this.dims)
      this.dims = [this.options.scaleMode.originalHeight,
                   this.options.scaleMode.originalWidth];
  },
  update: function(position) {
    var currentScale = (this.options.scaleFrom/100.0) + (this.factor * position);
    if(this.options.scaleContent && this.fontSize)
      Element.setStyle(this.element, {fontSize: this.fontSize * currentScale + this.fontSizeType });
    this.setDimensions(this.dims[0] * currentScale, this.dims[1] * currentScale);
  },
  finish: function(position) {
    if (this.restoreAfterFinish) Element.setStyle(this.element, this.originalStyle);
  },
  setDimensions: function(height, width) {
    var d = {};
    if(this.options.scaleX) d.width = width + 'px';
    if(this.options.scaleY) d.height = height + 'px';
    if(this.options.scaleFromCenter) {
      var topd  = (height - this.dims[0])/2;
      var leftd = (width  - this.dims[1])/2;
      if(this.elementPositioning == 'absolute') {
        if(this.options.scaleY) d.top = this.originalTop-topd + 'px';
        if(this.options.scaleX) d.left = this.originalLeft-leftd + 'px';
      } else {
        if(this.options.scaleY) d.top = -topd + 'px';
        if(this.options.scaleX) d.left = -leftd + 'px';
      }
    }
    Element.setStyle(this.element, d);
  }
});

Effect.BlindDown = function(element) {
  element = $(element);
  var oldHeight = Element.getStyle(element, 'height');
  var elementDimensions = Element.getDimensions(element);
  return new Effect.Scale(element, 100, 
    Object.extend({ scaleContent: false, 
      scaleX: false,
      scaleFrom: 0,
      scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
      restoreAfterFinish: true,
      afterSetup: function(effect) { with(Element) {
        makeClipping(effect.element);
        setStyle(effect.element, {height: '0px'});
        show(effect.element); 
      }},  
      afterFinishInternal: function(effect) { with(Element) {
        undoClipping(effect.element);
        setStyle(effect.element, {height: oldHeight});
      }}
    }, arguments[1] || {})
  );
}


/* Extensions to native JS objects
--------------------------------------------------------------------------*/
Object.extend(String.prototype, {
	/**
	 * Extends the .substring() method to allow negative numbers to
	 * reference indices from the end of the string rather than the beginning.
	 */
	/*substring: function(start, end) {
		if (start < 0) start = this.length + start;
		if (end < 0) end = this.length + end;
		if (end !== 0 && !end) end = this.length;
		var newString = '';
		for (var ssi=start; ssi<end; ssi++) {
			newString += this.charAt(ssi);
		}
		return newString;
	},*/

	/**
	 * Converts special characters to their HTML entities
	 */
	 htmlEntities: function() {
		var chars = {
			'&': 'amp',	'<': 'lt', '>': 'gt', '\"': 'quot'
		};

		var newString = this;
		for (var chacacter in chars) {
			var regExp = new RegExp();
			regExp.compile(chacacter,'g');
			newString = newString.replace(regExp, '&'+chars[chacacter]+';');
		}
		return newString;
	},

	/**
	 * Get Url Argument
	 * Turns a URL into an argument for links which function
	 * purely as event launchers.
	 */
	getUrlArgument: function() {
		// Parses a URL like "javascript:void(42);" and returns "42"
		if (val = this.match(/[Jj]avascript:void\(\'?(.*?)\'?\);?/i)) {
			if (val[1]) return val[1];
		}
		return this;
	},

	/**
	 * Trims a string
	 */
	trim: function() {
		return this.replace(/^\s+/g, '').replace(/\s+$/g, '');
	},

	/**
	 * Is Numeric
	 * checks if a string is a number
	 */
	isNumeric: function () {
		return !this.match(/\D/);
	}
});

Object.extend(Number.prototype, {
	/**
	 * Pads a number with zeroes until it is the desired length (digits)
	 * (Caution: Returns a string, not a float, out of necessity)
	 * Example: (7).zeroPad(3) == '007'
	 */
	zeroPad: function(digits) {
		var str = this.toString();
		while (str.length < digits) {
			str = '0'+str;
		}
		return str;
	}
});

Object.extend(Array.prototype, {
	/**
	 * Deletes any occurrences of needle from Array
	 * Not recommended for use on multidimensional arrays.
	 */
	deleteVals: function(needle) {
		newValue = new Array();
		if (this.length > 0) {
			for (var n=0; n<this.length; n++) {
				if (this[n] != needle) newValue.push(this[n]);
			}
		}
		return newValue;
	},

	/**
	 * Weeds out duplicate values in an array.
	 * Not recommended for use on multidimensional arrays.
	 */
	unique: function() {
		newArray = new Array();
		for (key in this) {
			if (typeof(this[key]) != 'function') {
				if (newArray.inArray(this[key]) == -1) newArray.push(this[key]);
			}
		}
		return newArray;
	},

	/**
	 * In Array
	 * Finds a value in an array
	 */
	inArray: function(needle) {
		if (this.length > 0) {
			for (var n=0; n<this.length; n++) {
				if (this[n] == needle) return n;
			}
		}
		return -1;
	}
});



/* Patches to Prototype
--------------------------------------------------------------------------*/
Object.extend(Form.Element, {
	setValue: function(inputElement, newValue) {
		inputElement = $(inputElement);
		switch(inputElement.type) {
			case 'text':
			case 'password':
				inputElement.value = newValue;
			break;
			case 'select':
			case 'select-one':
				for (var x=0; x<inputElement.options.length; x++) {
					if (inputElement.options[x].value == newValue) {
						inputElement.selectedIndex = x;
						return true;
					}
				}
				return false;
			break;
			case 'checkbox':
				inputElement.checked = bool(newValue);
			break;
			default:
				alert('setValue() can\'t yet handle input type: '+inputElement.type);
				return false;
			break;
		}
		return true;
	}
});

Object.extend(Position, {
	distanceBetween: function(coords0, coords1) {
		// Determines the straight distance between two points.
		// coords1 = [x: 1, y: 1]
		// or
		// coords1 = [1,1]
		if (coords0 instanceof Array) {
			var x0=coords0[0];
			var y0=coords0[1];
		} else {
			var x0=coords0.x;
			var y0=coords0.y;
		}
		if (coords1 instanceof Array) {
			var x1=coords1[0];
			var y1=coords1[1];
		} else {
			var x1=coords1.x;
			var y1=coords1.y;
		}
		return Math.sqrt((x0-x1)*(x0-x1) + (y0-y1)*(y0-y1))
	}
});

Object.extend(Object, {
	extendProperties: function(originalObject, extendingObject) {
		// Tries to intelligently extend an object's properties based
		// on direction given in the property names of the extending
		// object.
		for (var originalKeyName in extendingObject) {
			var modifier = originalKeyName.substring(0,1);
			var keyName = originalKeyName.substring(1);
			if (extendingObject[originalKeyName] instanceof Array) {
				// - Prune: Remove from it
				// + Merge: Add to it
				// ! Overwrite: Replace it entirely
				switch(modifier) {
					case '-':
						for (var x=0; x<extendingObject[originalKeyName].length; x++) {
							originalObject[keyName] = originalObject[keyName].deleteVals(extendingObject[originalKeyName][x]);
						}
					break;
					case '+':
						originalObject[keyName] = originalObject[keyName].concat(extendingObject[originalKeyName]);
					break;
					default:
						keyName = originalKeyName; // No valid modifier, so we need to restore the wrongly clipped keyName
					case '!':
						originalObject[keyName] = extendingObject[originalKeyName];
					break;
				}
			} else if (extendingObject[originalKeyName] instanceof Object) {
				// - Prune: Remove from it
				// + Merge: Add to it or overwrite preexisting conflicting values.
				// ! Overwrite: Replace it entirely
				switch(modifier) {
					case '-':
						for (var key in extendingObject[originalKeyName]) {
							if (typeof(Object.prototype[key]) == 'undefined') {
								delete(originalObject[keyName][key]);
							}
						}
					break;
					case '+':
						Object.extend(originalObject[keyName], extendingObject[originalKeyName]);
					break;
					default:
						keyName = originalKeyName; // No valid modifier, so we need to restore the wrongly clipped keyName
					case '!':
						originalObject[keyName] = extendingObject[originalKeyName];
					break;
				}
			} else {
				originalObject[originalKeyName] = extendingObject[originalKeyName];
			}
		}
	}
});

Object.extend(Element, {
	/**
	 * Tons faster and simpler than Prototype's
	 */
	hasClassName: function(element, className) {
		return Element.manipulateClass(element, className, 'find');
	},

	addClassName: function(element, className) {
		return Element.manipulateClass(element, className, 'add');
	},

	removeClassName: function(element, className) {
		return Element.manipulateClass(element, className, 'remove');
	},

	clearClassNames: function(element) {
		element.className = '';
	},

	manipulateClass: function(element, className, action) {
		//var classes = element.getAttribute('className');
		var classes = element.className;
		var cNames = [];
		if (classes != null) {
			cNames = classes.split(' ');
			if (action == 'remove') {
				cNames = cNames.deleteVals(className)
			} else {
				for (var x=0; x<cNames.length; x++) {
					if (cNames[x] == className) return (action == 'find');
				}
			}
		}
		if (action == 'add') {
			if (typeof(className) == 'array') {
				cNames = cNames.concat(className);
			} else {
				cNames.push(className);
			}
		}
		if (action == 'add' || action == 'remove') {
			//element.setAttribute('className', cNames.join(' ').trim());
			element.className = cNames.join(' ').trim();
		}
		if (action == 'find') return false;
	},

	/**
	 * Element.visible
	 * By Kramer
	 *
	 * I was disappointed that Prototype's Element.visible really only
	 * reports on the status of element.style.display. What I really
	 * wanted to know was "can the user see this element, or is it
	 * contained inside a hidden element?" This extension fixes that with
	 * the new "recursive" option.
	 *
	 * Recursive option added to seek up the tree to make sure
	 * all parent nodes are visible, too. This should effectively
	 * return a true/false indicating whether the given element is
	 * indeed VISIBLE TO THE USER.
	 */
	visible: function(element, recursive) {
		if (!recursive) return $(element).style.display != 'none';
		var search = element;
		while (search = search.parentNode) {
			if ($(search).style.display == 'none') return false;
		}
		return true;
	}
});

Event._observe = Event.observe;
Object.extend(Event, {
	/**
	 * Returns the keycode related to an event, browser-independent
	 */
	keyCode: function(event) {
		if (typeof(event.which) == 'undefined') return event.keyCode;
		return Math.max(event.which, event.keyCode);
	},

	/**
	 * Event.observe now returns an ID which can be sent to Event.stopObserving to terminate that event handler.
	 */
	registry: [],

	id: 0,

	observe: function(element, name, observer, useCapture) {
		Event.id++;
		var regEntry = {
			id: Event.id,
			element: element,
			name: name,
			observer: observer,
			useCapture: useCapture
		};
		Event.registry.push(regEntry);
		Event._observe(element, name, observer, useCapture);
		return regEntry;
	},

	unObserve: function(regEntry) {
		for (var x=0; x<Event.registry.length; x++) {
			var a = Event.registry[x];
			if (a.id == regEntry.id) {
				Event.registry.splice(x, 1);
				Event.stopObserving(a.element, a.name, a.observer, a.useCapture);
				return true;
			}
		}
		return false;
	},

	fire: function(el, name) {
		for(var x=0; x<Event.registry.length; x++){
			if(Event.registry[x].element == el && Event.registry[x].name == name){
				Event.registry[x].observer();
			}
		}
	}
});


/* Plain old functions
--------------------------------------------------------------------------*/
document.getElementsBySelector = function(selectors, withinNodes) {
	if (!(selectors instanceof Array)) selectors = [selectors];
	if (typeof(withinNodes) == 'undefined') {
		var withinNodes = [document.getElementsByTagName('body')[0]];
	} else if (!(withinNodes instanceof Array)) {
		var withinNodes = [withinNodes];
	}
	var resultNodes = [];
	for (var cSel=0; cSel<selectors.length; cSel++) {
		for (var cNodes=0; cNodes<withinNodes.length; cNodes++) {
			var resultNodes = resultNodes.concat(Element.getElementsBySelector(withinNodes[cNodes], selectors[cSel]));
		}
	}
	return resultNodes.unique();
};

function getViewportCenter() {
	return {
		x: getViewportSize().width/2,
		y: getViewportSize().height/2
	};
}
function getViewportSize() {
	return {
		width: self.innerWidth || (document.documentElement.clientWidth || document.body.clientWidth),
		height: self.innerHeight || (document.documentElement.clientHeight || document.body.clientHeight)
	};
}

/**
 * Changes pretty much any variable into its boolean equivalent
 * Strings like yes, no, true, false, y, n
 * Numbers like 0, 1, 2...
 * @return bool
 */
function bool(arg) {
	// Returns true or false
	// Does everything concievable to make arg into a boolean value
	if (typeof(arg) == 'undefined') return false;
	switch(arg.toLowerCase()) {
		case 'yes':
		case 'true':
		case 'y':
			return true;
		break;
		case 'false':
		case 'no':
		case 'n':
			return false;
		break;
		default:
			if (arg === true) return true;
			if (arg === false) return false;
			if (parseInt(arg) > 0) return true;
			if (parseInt(arg) == 0) return false;
		break;
	}
	return null; // Inconclusive
}

/**
 * Converts a UNIX timestamp into a JS Date object
 */
function unixToDate(unixtime) {
	var time = new Date();
	time.setTime(unixtime*1000);
	return time;
}

/**
 * For-each within an object. Recursive if you want it to be.
 **/
function foreach(obj, func, recursive) {
	for (var key in obj) {
		if (!Object.prototype[key]) {
			if (obj[key] instanceof Object && recursive) {
				foreach(obj[key], func);
			}
			func(obj[key], obj);
		}
	}
}

/**
 * Executes the given command within a different thread
 **/
function newThread(toExec) {
	setTimeout(toExec, 1);
}


/**
 * Kramer's DOM Library
 * DOM manipulation functions
 * March 28, 2006
 */
var DOM = {
	empty: function(el) {
		// Removes all children of the given element
		if (DOM.isNode(el)) {
			while (el.hasChildNodes()) {
				el.removeChild(el.firstChild);
			}
			return true;
		} else {
			return false;
		}
	},

	/**
	 * Get Children By Tag Name
	 * Returns immediate children by tag name
	 * Differs from getElementsByTagName() in that this version is not recursive.
	 * @param   DOMELEMENT element which element to start on
	 * @param   STRING     tagName the name of the tags to grab
	 * @return  array of applicable elements
	 */
	getChildrenByTagName: function(element, tagName) {
		if (DOM.isNode(element)) {
			var retval = Array();
			var descendants = element.getElementsByTagName(tagName);
			for (var i=0; i<descendants.length; i++) {
				if (descendants[i].parentNode == element) {
					retval.push(descendants[i]);
				}
			}
			return retval;
		} else {
			return false;
		}
	},

	getSiblingsByTagName: function(element, tagName, nextPrev) {
		if (DOM.isNode(element)) {
			var retval = Array();
			var context = element;
			if (!nextPrev) return DOM.getChildrenByTagName(element.parentNode, tagName);
			var dir = (nextPrev == 'next') ? 'nextSibling' : 'previousSibling';
			while (context = context[dir]) if (context.tagName) if (context.tagName.toLowerCase() == tagName.toLowerCase()) retval.push(context);
			return retval;
		} else {
			return false;
		}
	},

	/**
	 * Returns true if argument is a DOM node
	 */
	isNode: function(node) {
		if (!node) return false;
		return (typeof(node.nodeType) != 'undefined');
	},

	getElementText: function(el) {
		// Seeks out the first text element node contained in el and returns it
		if (typeof(el) != 'undefined' && typeof(el.childNodes) != 'undefined') {
			var nodeTypes = [3,4];                                         // Node types to check for data
			var nodes = {};
			for (var x=0; x<nodeTypes.length; x++) {
				nodes[nodeTypes[x]] = [];
			}
			for (var x=0; x<el.childNodes.length; x++) {
				if (nodeTypes.inArray(el.childNodes[x].nodeType) != -1) {
					nodes[el.childNodes[x].nodeType].push(el.childNodes[x]);
				}
			}
			if (nodes[4].length > 0) return nodes[4][0].nodeValue;
			if (nodes[3].length > 0) return nodes[3][0].nodeValue;
		}
		return false;
	},

	getXMLData: function(startFrom, tagName) {
		// Gets data from inside an XML tag by name. Seeks the first tag inside of startFrom with the correct name,
		// returns the text node from inside it.
		var tags = startFrom.getElementsByTagName(tagName);
		if (tags.length) {
			return DOM.getElementText(tags[0]);
		}
		return false;
	},

	nodeDisplayValue: function(node, replacementValue) {
		// Uses simple logic to drill down into a DOM node element in
		// search of the numeric value it is trying to display to the user,
		// and to return that value in float form.
		// If replacementValue is set, then, once the value has been
		// located; it will be replaced with that value.
		var replace = (typeof(replacementValue) != 'undefined');

		if (DOM.isNode(node)) {
			var value = null;
			switch (node.tagName.toLowerCase()) {
				case 'input':
					if (replace) node.value = replacementValue;
					value = node.value;
				break;
				default:
					// Place transition code here if desired.
					if (replace) node.childNodes[0].nodeValue = replacementValue;
					value = node.childNodes[0].nodeValue;
				break;
			}
			value = this.parseNumber(value);
			if (isNaN(value)) return 0;
			return value;
		} else {
			return false;
		}
	},

	/**
	 * Replaces one DOM node with another
	 */
	replace: function(originalNode, newNode) {
		if (typeof(newNode) == 'string') newNode = document.createTextNode(newNode);
		return originalNode.parentNode.replaceChild(newNode, originalNode);
	},

	/**
	 * Locates a comment with particular text in a document.
	 * Optionally searches only within a given node.
	 */
	findFlag: function(flagText, within) {
		if (!within) within = document.getElementsByTagName('body')[0];
		var list = within.childNodes;
		for (var x=0; x<list.length; x++) {
			if (list[x].nodeType == 8 && list[x].nodeValue == flagText) return list[x];
			if (list[x].childNodes.length) {
				var recursionResult = DOM.findFlag(flagText, list[x]);
				if (recursionResult != false) return recursionResult;
			}
		}
		return false;
	},

	/**
	 * Retrieves data encoded in a class="" attribute
	 *
	 * DOM.getClassData('secret', aNode) where aNode is:
	 * <a href="#" class="nav1 {secret=password}"> would return:
	 * "password"
	 *
	 * Non alphanumeric chars permitted in classnames per spec: http://www.w3.org/TR/CSS21/syndata.html#q6
	 **/
	getClassData: function(flagText, node) {
		var result = null;
		var className = node.className.replace(/.*\{(.*)\}.*/, "$1");

		return this.getEncodedData(flagText, className);
	},

	getCommentData: function(flagText, within) {
		if (!within) within = document.getElementsByTagName('body')[0];
		var list = within.childNodes;
		for (var x=0; x<list.length; x++) {
			if (list[x].nodeType == 8) {
				var result = null;
				if (result = this.getEncodedData(flagText, list[x].nodeValue)) return result;
			}
			if (list[x].childNodes.length) {
				var recursionResult = DOM.getCommentData(flagText, list[x]);
				if (recursionResult != false && recursionResult != null) return recursionResult;
			}
		}
	},

	getElementData: function(flagText, within) {
		if (!within) within = document.getElementsByTagName('body')[0];
		var list = within.childNodes;
		for (var x=0; x<list.length; x++) {
			if (Element.hasClassName(list[x], 'elementData')) {
				var result = null;
				if (result = this.getEncodedData(flagText, list[x].innerHTML)) return result;
			}
			if (list[x].childNodes.length) {
				var recursionResult = DOM.getCommentData(flagText, list[x]);
				if (recursionResult != false && recursionResult != null) return recursionResult;
			}
		}
	},

	getEncodedData: function(flagText, data) {
		var statements = data.split(';');
		for (var y=0; y<statements.length; y++) {
			var eqAt = statements[y].indexOf('=');
			var keyName = statements[y].substr(0, eqAt).trim();
			if (keyName == flagText) {
				return statements[y].substr(eqAt+1).trim();
			}
		}
		return false;
	},

	isDescendant: function(rootNode, childNode) {
		// Returns true if childNode is a descendant of rootNode
		var context = childNode;
		while (!(context.nodeType == 1 && context.tagName == 'BODY')) {
			context = context.parentNode;
			if (context == null) return false;
			if (context == rootNode) return true;
		}
		return false;
	},

	getAncestorsByTagName: function(startNode, tagName) {
		var ancestors = [];
		while (startNode != document.documentElement) {
			startNode = startNode.parentNode;
			if (startNode.tagName.toLowerCase() == tagName.toLowerCase()) ancestors.push(startNode);
		}
		return ancestors;
	},

	insertChild: function(newNode, refNode) {
		// Inserts a child element as first child, not last like appendChild does
		if (refNode.firstChild) {
			newNode = refNode.insertBefore(newNode, refNode.firstChild);
		} else {
			newNode = refNode.appendChild(newNode);
		}
		return newNode;
	},

	cut: function(node) {
		// Removes the given node from the DOM and returns it
		var clone = node.cloneNode(true);
		DOM.remove(node);
		return clone;
	},

	insertAfter: function(newNode, refNode) {
		// Acts like insertBefore, but does so after refNode.
		if (refNode.nextSibling) {
			newNode = refNode.parentNode.insertBefore(newNode, refNode.nextSibling);
		} else {
			newNode = refNode.parentNode.appendChild(newNode);
		}
		return newNode;
	},

	remove: function(node) {
		if (node.parentNode) return node.parentNode.removeChild(node);
	},

	/**
	 * Takes a DOM node and returns an HTML representation of it.
	 * (Not the exact tag as written in the HTML, necessarily)
	 */
	showTag: function(el, contents) {
		var tag = '';
		var att = null;
		var val = null;
		if (el) {
			if (el.nodeType == 1) {
				var tagName = el.tagName.toLowerCase();
				tag = "<"+tagName;
				if (el.attributes) {
					for (var i=0; i<el.attributes.length; i++) {
						att = el.attributes[i].nodeName;
						val = el.attributes[i].nodeValue;
						if (val) tag += ' '+att+'="'+val+'"';
					}
				}
				var innerHTML = (typeof(el.innerHTML) == 'unknown') ? '**innerHTML property undefined**' : el.innerHTML;
				if (contents && innerHTML) {
					tag += '>';
					tag += innerHTML;
					tag += '</'+tagName+'>';
				} else if (!contents && innerHTML) {
					tag += '>';
				} else {
					tag += ' />';
				}
			} else {
				tag = 'Invalid node type: '+el.nodeType;
			}
		} else {
			tag = 'Parameter is not a tag node: '+el;
		}
		return tag;
	},
	makeTag: function(el, deep) {
		return DOM.showTag(el, deep);
	},
	outerHTML: function(el, deep) {
		return DOM.showTag(el, deep);
	},

	/**
	 * Opposite of makeTag, takes HTML and returns a node.
	 * Only works with ONE top-level node in the HTML.
	 */
	makeNode: function(html) {
		var wrapper = document.createElement('div');
		wrapper.innerHTML = html;
		return wrapper.childNodes[0];
	},

	/**
	 * Shows the style of a given element
	 */
	showStyle: function(el) {
		if (el) {
			var st = "Element Style: <"+el.tagName+">\n\n";
			if (el.style) {
				for (afds in el.style) {
					var val = el.style[afds];
					if (val && typeof(val) != 'function') st += afds+': "'+el.style[afds]+'"\n';
				}
			} else {
				st = 'No style';
			}
		} else {
			var st = 'Parameter is not an element: '+el;
		}
		return st;
	}
};

/*
	 The following code is Copyright (C) Simon Willison 2004.

	 document.getElementsBySelector(selector)
	 - returns an array of element objects from the current document
		 matching the CSS selector. Selectors can contain element names,
		 class names and ids and can be nested. For example:

			 elements = document.getElementsBySelect('div#main p a.external')

		 Will return an array of all 'a' elements with 'external' in their
		 class attribute that are contained inside 'p' elements that are
		 contained inside the 'div' element which has id="main"

	 New in version 0.4: Support for CSS2 and CSS3 attribute selectors:
	 See http://www.w3.org/TR/css3-selectors/#attribute-selectors

	 Patched 060712 by Kramer to permit selection within a confined subtree
	 via the "currentContext" argument.

	 Patched 060213 by Kramer to gracefully fail if an ID is not found
	 and to always return an array, even if empty.

	 Version 0.4 - Simon Willison, March 25th 2003
	 -- Works in Phoenix 0.5, Mozilla 1.3, Opera 7, Internet Explorer 6, Internet Explorer 5 on Windows
	 -- Opera 7 fails
*/

function getAllChildren(e) {
	// Returns all children of element. Workaround required for IE5/Windows. Ugh.
	return e.all ? e.all : e.getElementsByTagName('*');
}

document.getElementsBySelector = function(selector, currentContext) {
	// Attempt to fail gracefully in lesser browsers
	if (!document.getElementsByTagName) {
		return new Array();
	}
	// Split selector in to tokens
	var tokens = selector.split(' ');
	if (!currentContext) currentContext = document;
	if (typeof(currentContext) != 'array') currentContext = [currentContext];
	for (var i = 0; i < tokens.length; i++) {
		token = tokens[i].replace(/^\s+/,'').replace(/\s+$/,'');;
		if (token.indexOf('#') > -1) {
			// Token is an ID selector
			var bits = token.split('#');
			var tagName = bits[0];
			var id = bits[1];
			var element = document.getElementById(id);
			if (!element) return new Array(); // Can't find anything with that ID, return false.
			if (tagName && element.nodeName.toLowerCase() != tagName) {
				// tag with that ID not found, return false
				return new Array();
			}
			// Set currentContext to contain just this element
			currentContext = new Array(element);
			continue; // Skip to next token
		}
		if (token.indexOf('.') > -1) {
			// Token contains a class selector
			var bits = token.split('.');
			var tagName = bits[0];
			var className = bits[1];
			if (!tagName) {
				tagName = '*';
			}
			// Get elements matching tag, filter them for class selector
			var found = new Array;
			var foundCount = 0;
			for (var h = 0; h < currentContext.length; h++) {
				var elements;
				if (tagName == '*') {
						elements = getAllChildren(currentContext[h]);
				} else {
						elements = currentContext[h].getElementsByTagName(tagName);
				}
				for (var j = 0; j < elements.length; j++) {
					found[foundCount++] = elements[j];
				}
			}
			currentContext = new Array;
			var currentContextIndex = 0;
			for (var k = 0; k < found.length; k++) {
				if (found[k].className && found[k].className.match(new RegExp('\\b'+className+'\\b'))) {
					currentContext[currentContextIndex++] = found[k];
				}
			}
			continue; // Skip to next token
		}
		// Code to deal with attribute selectors
		if (token.match(/^(\w*)\[(\w+)([=~\|\^\$\*]?)=?"?([^\]"]*)"?\]$/)) {
			var tagName = RegExp.$1;
			var attrName = RegExp.$2;
			var attrOperator = RegExp.$3;
			var attrValue = RegExp.$4;
			if (!tagName) {
				tagName = '*';
			}
			// Grab all of the tagName elements within current context
			var found = new Array;
			var foundCount = 0;
			for (var h = 0; h < currentContext.length; h++) {
				var elements;
				if (tagName == '*') {
						elements = getAllChildren(currentContext[h]);
				} else {
						elements = currentContext[h].getElementsByTagName(tagName);
				}
				for (var j = 0; j < elements.length; j++) {
					found[foundCount++] = elements[j];
				}
			}
			currentContext = new Array;
			var currentContextIndex = 0;
			var checkFunction; // This function will be used to filter the elements
			switch (attrOperator) {
				case '=': // Equality
					checkFunction = function(e) { return (e.getAttribute(attrName) == attrValue); };
					break;
				case '~': // Match one of space seperated words
					checkFunction = function(e) { return (e.getAttribute(attrName).match(new RegExp('\\b'+attrValue+'\\b'))); };
					break;
				case '|': // Match start with value followed by optional hyphen
					checkFunction = function(e) { return (e.getAttribute(attrName).match(new RegExp('^'+attrValue+'-?'))); };
					break;
				case '^': // Match starts with value
					checkFunction = function(e) { return (e.getAttribute(attrName).indexOf(attrValue) == 0); };
					break;
				case '$': // Match ends with value - fails with "Warning" in Opera 7
					checkFunction = function(e) { return (e.getAttribute(attrName).lastIndexOf(attrValue) == e.getAttribute(attrName).length - attrValue.length); };
					break;
				case '*': // Match ends with value
					checkFunction = function(e) { return (e.getAttribute(attrName).indexOf(attrValue) > -1); };
					break;
				default :
					// Just test for existence of attribute
					checkFunction = function(e) { return e.getAttribute(attrName); };
			}
			currentContext = new Array;
			var currentContextIndex = 0;
			for (var k = 0; k < found.length; k++) {
				if (checkFunction(found[k])) {
					currentContext[currentContextIndex++] = found[k];
				}
			}
			// alert('Attribute Selector: '+tagName+' '+attrName+' '+attrOperator+' '+attrValue);
			continue; // Skip to next token
		}

		if (!currentContext[0]){
			return new Array();
		}

		// If we get here, token is JUST an element (not a class or ID selector)
		tagName = token;
		var found = new Array;
		var foundCount = 0;
		for (var h = 0; h < currentContext.length; h++) {
			var elements = currentContext[h].getElementsByTagName(tagName);
			for (var j = 0; j < elements.length; j++) {
				found[foundCount++] = elements[j];
			}
		}
		currentContext = found;
	}
	return currentContext;
}
// And an alias for legacy code and forgetful developers
getElementsBySelector = document.getElementsBySelector;

/* That revolting regular expression explained
/^(\w+)\[(\w+)([=~\|\^\$\*]?)=?"?([^\]"]*)"?\]$/
	\---/  \---/\-------------/    \-------/
		|      |         |               |
		|      |         |           The value
		|      |    ~,|,^,$,* or =
		|   Attribute
	 Tag
*/


// EventSelectors
// Copyright (c) 2005-2006 Justin Palmer (http://encytemedia.com)
// Examples and documentation (http://encytemedia.com/event-selectors)
//
// EventSelectors allow you access to Javascript events using a CSS style syntax.
// It goes one step beyond Javascript events to also give you :loaded, which allows
// you to wait until an item is loaded in the document before you begin to interact
// with it.
//
// Inspired by the work of Ben Nolan's Behaviour (http://bennolan.com/behaviour)
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//

// Modified by Grady and Kramer of SolutionSet
var EventSelectors = {
	version: '1.0_pre',
	cache: [],
	rules: [],
	funcs: [],
	nodes: [],

	register : function(rules) {
		this.rules.push(rules);
	},

	apply : function() {
		this._unloadCache();
		for (var x=0; x<this.rules.length; x++) {
			var rules = this.rules[x];
			this.timer = new Array();
			this.assign(rules);
		}
	},

	addLoadEvent : function(func) {
		this.register({
			'window.domLoad': func
		});
	},

	_addLoadEvent : function(func) {
		var oldonload = window.onload;
		if (typeof window.onload != 'function') {
			window.onload = func;
		} else {
			window.onload = function() {
				oldonload();
				func();
			}
		}
	},

	start: function() {
		this._addLoadEvent(function(){
			EventSelectors.apply();
		}.bind(this));
	},

	assign: function(rules) {
		for (var key in rules) {
			var rule = {
				selector: key,
				func: rules[key]
			};
			switch (rule.selector) {                                       // Special selector tokens
				case 'window:load':
				case 'window.binLoad':
				case 'window.domLoad':
					rule.func();
				break;
				default:                                                     // Default selector operation
					var selectors = $A(rule.selector.split(','));
					for (var x=0; x<selectors.length; x++) {
						var selector = selectors[x];
						var pair = selector.split(':');
						var event = pair[1];
						var elements = $$(pair[0]);
						for (var y=0; y<elements.length; y++) {
							var element = elements[y];
							if(pair[1] == '' || pair.length == 1) {
								var funcId = this.funcs.indexOf(rule.func);
								if(funcId == -1) {
									this.funcs.push(rule.func);
									funcId = this.funcs.length-1;
								}
								if (!this.nodes[funcId])
									this.nodes[funcId] = [];
								if (this.nodes[funcId].indexOf(element) == -1) {
									this.nodes[funcId].push(element);
									rule.func(element);
								}
								continue;
							}
							if(event.toLowerCase() == 'loaded') {
								this.timer[pair[0]] = setInterval(function(element, timer, rule) {
									var node = $(element);
									if(element.tagName != 'undefined') {
										clearInterval(this.timer[timer]);
										rule.func(node);
									}
								}.bind(this, element, pair[0], rule), 15);
							} else {
								var observer = function(event) {
									var element = Event.element(event);
									if (element.nodeType == 3)                             // Safari Bug (Fixed in Webkit)
										element = element.parentNode;
									rule.func($(element), event);
								}
								this.cache.push([element, event, observer]);
								Event.observe(element, event, observer);
							}
						}
					}
				break;
			}
		}
	},

	// Scoped caches would rock. (what does that mean?)
	_unloadCache: function() {
		if (!this.cache) return;
		for (var i = 0; i < this.cache.length; i++) {
			Event.stopObserving.apply(this, this.cache[i]);
			this.cache[i][0] = null;
		}
		this.cache = [];
	}
}

// Remove/Comment this if you do not wish to reapply Rules automatically
// on Ajax request.
//Ajax.Responders.register({
//	onComplete: function() { EventSelectors.apply();}
//});

EventSelectors.start();

Behaviour = EventSelectors; // For old code that expects Behaviour
addLoadEvent = EventSelectors.addLoadEvent;


/*
	Base, version 1.0.2
	Copyright 2006, Dean Edwards
	License: http://creativecommons.org/licenses/LGPL/2.1/
*/

var Base = function() {
	if (arguments.length) {
		if (this == window) { // cast an object to this class
			Base.prototype.extend.call(arguments[0], arguments.callee.prototype);
		} else {
			this.extend(arguments[0]);
		}
	}
};

Base.version = "1.0.2";

Base.prototype = {
	extend: function(source, value) {
		var extend = Base.prototype.extend;
		if (arguments.length == 2) {
			var ancestor = this[source];
			// overriding?
			if ((ancestor instanceof Function) && (value instanceof Function) &&
				ancestor.valueOf() != value.valueOf() && /\bbase\b/.test(value)) {
				var method = value;
			//	var _prototype = this.constructor.prototype;
			//	var fromPrototype = !Base._prototyping && _prototype[source] == ancestor;
				value = function() {
					var previous = this.base;
				//	this.base = fromPrototype ? _prototype[source] : ancestor;
					this.base = ancestor;
					var returnValue = method.apply(this, arguments);
					this.base = previous;
					return returnValue;
				};
				// point to the underlying method
				value.valueOf = function() {
					return method;
				};
				value.toString = function() {
					return String(method);
				};
			}
			return this[source] = value;
		} else if (source) {
			var _prototype = {toSource: null};
			// do the "toString" and other methods manually
			var _protected = ["toString", "valueOf"];
			// if we are prototyping then include the constructor
			if (Base._prototyping) _protected[2] = "constructor";
			for (var i = 0; (name = _protected[i]); i++) {
				if (source[name] != _prototype[name]) {
					extend.call(this, name, source[name]);
				}
			}
			// copy each of the source object's properties to this object
			for (var name in source) {
				if (!_prototype[name]) {
					extend.call(this, name, source[name]);
				}
			}
		}
		return this;
	},

	base: function() {
		// call this method from any other method to invoke that method's ancestor
	}
};

Base.extend = function(_instance, _static) {
	var extend = Base.prototype.extend;
	if (!_instance) _instance = {};
	// build the prototype
	Base._prototyping = true;
	var _prototype = new this;
	extend.call(_prototype, _instance);
	var constructor = _prototype.constructor;
	_prototype.constructor = this;
	delete Base._prototyping;
	// create the wrapper for the constructor function
	var klass = function() {
		if (!Base._prototyping) constructor.apply(this, arguments);
		this.constructor = klass;
	};
	klass.prototype = _prototype;
	// build the class interface
	klass.extend = this.extend;
	klass.implement = this.implement;
	klass.toString = function() {
		return String(constructor);
	};
	extend.call(klass, _static);
	// single instance
	var object = constructor ? klass : _prototype;
	// class initialisation
	if (object.init instanceof Function) object.init();
	return object;
};

Base.implement = function(_interface) {
	if (_interface instanceof Function) _interface = _interface.prototype;
	this.prototype.extend(_interface);
};


/**
 * stdClass
 */
var stdClass = Base.extend({
	settings: function(settings) {
		if (typeof(this.s) == 'undefined') {
			this.s = {};                                                     // Settings
			Object.extend(this.s, settings);
			this.n = {                                                       // Nodes
				_body: document.getElementsByTagName('body')[0]
			};
			this.c = {                                                       // Collections
				events: []
			};
		}
	},

	constructor: function(settings) {
		this.settings(settings);
	},

	eObserve: function(node, eventType, callback) {
		var eventRef = Event.observe(node, eventType, callback);
		this.c.events.push(eventRef);
		return eventRef;
	},

	eUnObserve: function(eventRef) {
		Event.unObserve(eventRef);
	},

	eUnObserveAll: function() {
		this.c.events.each(function (eventRef) {
			this.eUnObserve(eventRef);
		});
	}
});

/*
// Use the following code to create extensible classes:

var ClassName = stdClass.extend({
	// Static properties

	settings: function(settings) {
		if (typeof(this.s) == 'undefined') {
			this.base();

			Object.extendProperties(this.s, {
				whatever: 'whateverElse'
			});
			Object.extendProperties(this.s, settings);
		}
	},

	constructor: function(settings) {
		this.settings(settings);
	}
});
*/

/**
 * Gets a cookie
 **/
function getCookie(name) {
	var cookies = document.cookie.split('; ');
	for (var x=0; x<cookies.length; x++) {
		var cookie = cookies[x].split('=');
		if (cookie[0] == name) return cookie[1];
	}
	return false;
}

/**
 * Gets a GET variable
 **/
function getGet(name) {
	var getvars = document.location.search.substring(1).split('&');
	for (var x=0; x<getvars.length; x++) {
		var getvar = getvars[x].split('=');
		if (getvar[0] == name) return getvar[1];
	}
	return false;
}

/**
 * LookupBase
 *
 * By Kramer of SolutionSet.com
 * 070209
 *
 * A base class on which widgets such as EventLookup are built.
 * This class cannot stand alone - it must be extended.
 *
 * The URL used as a data source is configured in the HTML,
 * specifically in the action="" attribute of the <form> tag.
 * The form given to a LookupBase-derived object thereby dictates
 * the XML data source, as well as the request variables sent
 * to that URL when retrieving XML data. The form does not,
 * however, function as a traditional HTML web form. Form element
 * values are serialized and send in an AJAX request, and the
 * resulting XML is parsed into HTML (not using XSLT, unfortunately)
 * and displayed in the "results" node as dictated by the
 * ajaxIn() method.
 *
 **/
var LookupBase = stdClass.extend({
	// Static properties

	settings: function(settings) {
		if (typeof(this.s) == 'undefined') {
			this.base();

			this.displayItems = [];
			this.displayItemsQtyLimit = 10;
			this.lastSubmittedArguments = {};
			this.URLTokens = {};

			Object.extendProperties(this.s, {
				ajaxUrl: '',
				displayItemTemplate: '',                                     // HTML template which dictates how each display item will be transformed into HTML.
				ignoreEmptyFormData: true,
				noResultsHTML: '<li class="noResults">There are no results to display</li>',
				seeMoreURL: false,
				formElementValues: {},
				formElementVisibility: {},
				n: {
					form: null,
					container: null,
					results: null,
					resultsContainer: null,
					seeMoreResults: null
				}
			});
			Object.extendProperties(this.s, settings);
		}
	},

	constructor: function(settings) {
		this.settings(settings);

		this.s.ajaxUrl = this.s.n.form.action;                           // Get URL to submit to
		Event.observe(this.s.n.form, 'submit', this.onSubmit.bind(this));// Attach to the submit event on the form

		// Set form values as desired
		for (var elementName in this.s.formElementValues) {
			if (typeof(Object.prototype[elementName]) == 'undefined') {
				if (this.s.n.form[elementName]) Form.Element.setValue(this.s.n.form[elementName], this.s.formElementValues[elementName]);
			}
		}
		// Hide form values if desired
		for (var className in this.s.formElementVisibility) {
			if (typeof(Object.prototype[className]) == 'undefined') {
				if (!this.s.formElementVisibility[className]) {
					var el = document.getElementsBySelector('fieldset.'+className, this.s.n.form)[0];
					if (el) Element.hide(el);
				}
			}
		}
		this.hideResults();
	},

	hideResults: function() {
		Element.hide(this.s.n.resultsContainer);
	},

	onSubmit: function(e) {
		Event.stop(e);                                                   // Cancel event
		this.hideResults();
		this.lastSubmittedArguments = Form.serialize(this.s.n.form).toQueryParams(); // Serialize the form
		
		// Assemble the query string
		var qs=[];
		for (x in this.lastSubmittedArguments) {
			qs.push(x+'='+this.lastSubmittedArguments[x]);
		}
		
		//add siteID
		//var theQs = getQueryString();
		//with the URLs simplification project URLs do not have siteIDs anynmore, getting it from meta tags of the page instead
		qs.push('siteID='+document.getElementsByTagName('meta')[3].content);
		
		var queryString = qs.join('&');
		if (this.s.ignoreEmptyFormData) queryString = queryString.replace(/([^&]*)=&/g, ''); // Remove blank elements if desired
		this.ajaxOut(queryString);                                       // Send out a request for this data
	},

	/*ajaxOut: function(args) {
		Element.addClassName(this.s.n.container, 'resellerLoading');
		new Ajax.Request(this.s.ajaxUrl+'?'+args, {                      // AJAX out to the server using the given args
			method: 'get',
			//parameters: args,
			onSuccess: this.ajaxIn.bind(this)
		});
	},

	ajaxIn: function(req) {
		Element.removeClassName(this.s.n.container, 'resellerLoading');
		this.displayItems = [];
	},*/

	display: function() {
		
		this.s.n.results.innerHTML = '';
		if (this.displayItems.length) {
			for (var x=0; x<this.displayItems.length; x++) {
				this.s.n.results.innerHTML += this.parseTemplate(this.s.displayItemTemplate, this.displayItems[x]);
			}
		} else {
			this.s.n.results.innerHTML = this.s.noResultsHTML;
		}
		
		Effect.BlindDown(this.s.n.resultsContainer);
	},

	displayItemsQtyOverflow: function(bool) {                          // Set to true if the number of items exceeds the display limit (will show/hide the "see more" link)
		if (!bool) Element.hide(this.s.n.seeMoreResults);
		if (bool) {
			if (this.s.seeMoreURL) {                                       // If a dynamic "see more events" URL has been specified, then reset the "see more events" link URL accordingly.
				Object.extendProperties(this.URLTokens, this.lastSubmittedArguments);
				var url = this.parseTemplate(this.s.seeMoreURL, this.URLTokens);
				var link = this.s.n.seeMoreResults.getElementsByTagName('a')[0];
				link.href = url;
			}
			Element.show(this.s.n.seeMoreResults);
		}
	},

	parseTemplate: function(template, values) {
		var text = template;
		for (var key in values) {
			if (typeof(Object.prototype[key]) == 'undefined') {
				var regexp = new RegExp('%'+key+'%', "g");
				text = text.replace(regexp, values[key]);
			}
		}
		return text;
	}
});

function validateZIP(field) {
	var valid = "0123456789-";
	var hyphencount = 0;
	
	if (field.length!=5 && field.length!=10) {
		alert("Please enter your 5 digit or 5 digit+4 zip code.");
		return false;
	}
	for (var i=0; i < field.length; i++) {
		temp = "" + field.substring(i, i+1);
		if (temp == "-") hyphencount++;
		if (valid.indexOf(temp) == "-1") {
			alert("Invalid characters in your zip code.");
			return false;
		}
		if ((hyphencount > 1) || ((field.length==10) && ""+field.charAt(5)!="-")) {
			alert("The hyphen character should be used with a properly formatted 5 digit+four zip code, like '12345-6789'.");
			return false;
	  	}
	}
	return true;
}

function validateZIP2(field) {
	var valid = "0123456789-";
	var hyphencount = 0;
	
	if (field.length!=5 && field.length!=10) {
		return false;
	}
	for (var i=0; i < field.length; i++) {
		temp = "" + field.substring(i, i+1);
		if (temp == "-") hyphencount++;
		if (valid.indexOf(temp) == "-1") {
			return false;
		}
		if ((hyphencount > 1) || ((field.length==10) && ""+field.charAt(5)!="-")) {
			return false;
	  	}
	}
	return true;
}

function getQueryString() { 
  var Qs = new Object(); 
  var theSearch = document.location.search.substring(1); 
  if (theSearch.length > 0) { 
    var KeyValues = theSearch.split('&'); 
    var KeyValue = '';
    for(var i = 0; i < KeyValues.length; i++){
      KeyValue = KeyValues[i].split('=');
	  Qs[unescape(KeyValue[0])] = unescape(KeyValue[1]); 
    } 
  }
  return Qs; 
}
