/*
This file contains all commonly used functions by Solid Cactus
version: 2.0
last modified: Jacob Swartwood
modified date: 02/25/2008
*/

function SC() {
	/*
		SC - Public - The main Class (namespace) for this framework
		Public functions can be called by using the example syntax: SC.funcName( param0, param1, ...);
	*/
	
	//////////////////// Debugging functions below ////////////////////
	
	function startDebug( code ) {
		if (code === 9814) {
			var elm = genElem('div', {id:'ScDeBuGgErWiN'});
			with (elm.style) {
				position = 'fixed !important';
				left = top = '0 !important';
				width = '260px !important';
				backgroundColor = '#000 !important';
				color = '#0f0 !important';
				zIndex = '99999999 !important';
			}
			elm.innerHTML = '<br>';
			appElem(elm);
		}
	}
	this.startDebug = startDebug; // Make a public copy
	
	function debugMe() {
		var dbWin = $('ScDeBuGgErWiN');
		if (dbWin) {
			for (var i = 0, j = arguments.length; i < j; i++) {
				dbWin.innerHTML += arguments[i] + '<br><br>';
			}
		}
	}
	this.debugMe = debugMe; // Make a public copy
	
	function debugClear() {
		var dbWin = $('ScDeBuGgErWiN');
		if (dbWin) {
			dbWin.innerHTML = '<br>';
		}
	}
	this.debugClear = debugClear; // Make a public copy

	//////////////////// Javascript functions below ////////////////////
	
	function typeOf( jsVal ) {
		/*
			typeOf - Private - A more specific version of the standard typeof operator
			PARAMETERS:	jsVal - the javascript value to test
			RETURN:	a string value of the object type; refined 'object' to return 'array', 'date', 'html', or 'null' if possible
		*/
		var str = typeof jsVal;
		if (str === 'object') {
			if (jsVal) {
				if (jsVal.nodeType === 1) {
					str = 'html';
				} else if (jsVal instanceof Array) {
					str = 'array';
				} else if (jsVal instanceof Date) {
					str = 'date';
				}
			} else {
				str = 'null';
			}
		}
		return str;
	}
	this.typeOf = typeOf; // Make a public copy
	
	function forceArray( arr ) {/*
		TODO document
	*/
		var newArr = [];
		/* try {
			newArr = Array.prototype.slice(arr, 0, arr.length);
		} catch (err) {} */
		if (!newArr.length) {
			for (var i = 0, j = arr.length; i < j; i++) {
				newArr.push(arr[i]);
			}
		}
		return newArr;
	}
	this.forceArray = forceArray; // Make a public copy
	
	function getQuery( queryVal ) {/*
		TODO document
	*/
		queryVal = queryVal || document.location.search;
		if (queryVal) {
			var queryStart = queryVal.indexOf('?');
			if (queryStart !== -1) {
				queryVal = queryVal.substr(queryStart + 1);
				if (!queryVal) {
					return null;
				}
			} else {
				return null;
			}
			var data = {};
			var sets = queryVal.split('&');
			for (var i = 0, j = sets.length; i < j; i++) {
				var set = sets[i].split('=');
				if (set) {
					data[decodeURIComponent(set[0])] = (set[1])? decodeURIComponent(set[1]) : '';
				}
				delete set;
			}
			delete sets;
			return data;
		}
		delete queryVal;
		return null;
		
	}
	this.getQuery = getQuery; // Make a public copy
	
	function genQueryStr( queryVal, locationVal ) {/*
		TODO document
	*/
		if (queryVal) {
			if (!locationVal) {
				locationVal = document.location.href;
			}
			var queryStart = locationVal.indexOf('?'), prevQuery = '';
			if (queryStart !== -1) {
				prevQuery = locationVal.substr(queryStart + 1);
				if (prevQuery && (prevQuery[prevQuery.length - 1] !== '&')) {
					locationVal += '&';
				}
			} else {
				locationVal += '?';
			}
			delete queryStart, prevQuery;
			var queryType = typeOf(queryVal);
			if (queryType == 'string') {
				locationVal += encodeURI(queryVal);
			} else if (queryType == 'object') {
				for (var i in queryVal) {
					locationVal += encodeURIComponent(i) + '=' + encodeURIComponent(queryVal[i]) + '&';
				}
				var lastCharPos = locationVal.length - 1;
				if (locationVal[lastCharPos] === '&') {
					locationVal = locationVal.substr(0, lastCharPos);
				}
				delete lastCharPos;
			}
			delete queryType;
			return locationVal;
		}
		return '';
	}
	this.genQueryStr = genQueryStr; // Make a public copy
	
	function setQuery( queryVal, locationVal ) {/*
		TODO document
	*/
		if (locationVal = genQueryStr(queryVal, locationVal)) {
			return document.location.href = locationVal;
		}
		return '';
	}
	this.setQuery = setQuery; // Make a public copy
	
	function getHostName( str ) {/*
		TODO document
	*/
		if (!str) {
			return document.location.hostname;
		} else if (typeOf(str) === 'string') {
			var mtch = str.match(/^(?:(?:[\w]+\:)?\/\/)?([^\/]+)/);
			if (mtch) {
				return mtch[1];
			}
		}
		return '';
	}
	this.getHostName = getHostName; // Make a public copy
	
	function stripHTML( str ) {
		/*
			stripHTML - Private - Strips HTML code from a string
			PARAMETERS:	str - a string value to parse
			RETURN:	a string of without tags
		*/
		var tmp = '';
		if (typeOf(str) === 'html') {
			str = str.innerHTML;
		}
		if (str && typeOf(str) === 'string') {
			tmp = str.replace(/\<\/?[^\>]+\>/g, '');
		}
		return tmp;
	}
	this.stripHTML = stripHTML; // Make a public copy

	function hexEncode( str ) {
		/*
			hexEncode - Private - Converts a string into it's hexadecimal equivalent
			PARAMETERS:	str - a string value to convert
			RETURN:	a string of hexadecimal values
		*/
		var tmp = '';
		if (typeOf(str) === 'string') {
			var hxMp = '0123456789ABCDEF';
			for (var i = 0, j = str.length; i < j; i++) {
				tmp +=	hxMp.charAt(str.charCodeAt(i) / 16);
				tmp += hxMp.charAt(str.charCodeAt(i) % 16);
			}
			delete hxMp;
		}
		return tmp;
	}
	this.hexEncode = hexEncode; // Make a public copy

	//////////////////// Functions for cookie manipulation ////////////////////
	
	function expWhen( eDate ) {
		/*
			expWhen - Private - Generates a string stating when a cookie will expire
			PARAMETERS:	eDate - a date object set to the cookie expiration
			RETURN:	the cookie expiration as a string
		*/
		return 'expires=' + eDate.toGMTString();
	}
	
	function expTime( eTime ) {
		/*
			expTime - Private - Generates a string stating when a cookie will expire based on an amount of time
			PARAMETERS:	eTime - the amount of milliseconds from now at which the cookie will expire as an integer
			RETURN:	the cookie expiration as a string
		*/
		var eDate = new Date();
		eDate.setTime(eDate.getTime() + eTime)
		return eDate;
	}
	this.expTime = expTime; // Make a public copy
	
	function expDays( eDays ) {
		/*
			expTime - Private - Generates a string stating when a cookie will expire based on an amount of days
			PARAMETERS:	eDays - the amount of days from now at which the cookie will expire as an integer
			RETURN:	the cookie expiration as a string
		*/
		var eDate = new Date();
		eDate.setDate(eDate.getDate() + eDays)
		return eDate;
	}
	this.expDays = expDays; // Make a public copy
	
	function setCookie( cookieName, cookieInfo, cookiePath, cookieDomain, cookieExpire ) {
		/*
			setCookie - Private - Sets a browser cookie
			PARAMETERS:	cookieName - the name of the cookie as a string
						cookieInfo - the value of the cookie as a string
						cookiePath - the path of the cookie as a string
						cookieDomain - the domain of the cookie as a string
						cookieExpire - the amount of days before the cookie expires as an integer
			RETURN:	none
		*/
		if (!cookieInfo) {
			cookieInfo = '';
			cookieExpire = 0;
		}
		var cookieVal = (cookieName)? (cookieName + '=' + cookieInfo) : null;
		if (cookiePath) {
			cookieVal += ';path=' + cookiePath;
		}
		if (cookieDomain) {
			cookieVal += ';domain=' + cookieDomain;
		}
		if (cookieExpire || !cookieInfo) {
			if (typeOf(cookieExpire) === 'date') {
				cookieExpire = expWhen(cookieExpire);
			} else {
				cookieExpire = expWhen(expDays(cookieExpire));
			}
			cookieVal += ';' + cookieExpire;
		}
		if (cookieVal) {
			document.cookie = cookieVal;
			delete cookieVal;
		}
	}
	this.setCookie = setCookie; // Make a public copy
	
	function getCookie( cookieName ) {
		/*
			getCookie - Private - Gets the value of a specific cookie
			PARAMETERS:	cookieName - the name of the cookie as a string
			RETURN:	the cookie value as a string
		*/
		if ((document.cookie.length > 0) && cookieName){
			var cBegin = document.cookie.indexOf(cookieName += '=');
			if (cBegin != -1){
				cBegin = cBegin + cookieName.length;
				var cEnd = document.cookie.indexOf(';', cBegin);
				if (cEnd == -1) cEnd = document.cookie.length;
				return document.cookie.substring(cBegin, cEnd);
			}
		}
		return '';
	}
	this.getCookie = getCookie; // Make a public copy
	
	//////////////////// Functions for DOM manipulation ////////////////////
	
	function getById( idVal ) {
		/*
			getById - Private - Gets an HTML element by id
			PARAMETERS:	idVal - the id of the element as a string
			RETURN:	an HTML element
		*/
		return document.getElementById(idVal);
	}
	this.$ = this.getById = $ = getById; // Make a public copy
	
	function getByTag( tagVal, scope ) {
		/*
			getByTag - Private - Gets the set of HTML elements that match a specific tagName
			PARAMETERS:	tagVal - the tagName of the element to find as a string
						scope - the HTML element to check within; if not set, document is used
			RETURN:	an array of HTML elements
		*/
		if (!scope || !scope.getElementsByTagName) {
			scope = document;
		}
		var tags = forceArray(scope.getElementsByTagName(tagVal));
		return tags;
	}
	this.getByTag = getByTag; // Make a public copy
	
	function filterByTag( objs, tagVal ) {
		/*
			filterByTag - Private - Filters out a set of HTML elements that match a specific tagName
			PARAMETERS:	objs - the set of HTML elements to check through
						tagVal - the tagName of the element to find as a string
			RETURN:	an array of HTML elements
		*/
		tagVal = (typeOf(tagVal) === 'string')? tagVal.toLowerCase() : '';
		var tagSet = [];
		for (var i = 0, j = objs.length; i < j; i++) {
			if ((objs[i].nodeType === 1) && ((objs[i].tagName.toLowerCase() == tagVal) || !tagVal)) {
				tagSet.push(objs[i]);
			}
		}
		return tagSet;
	}
	this.filterByTag = filterByTag; // Make a public copy
	
	function hasClass( obj, classVal ) {
		/*
			hasClass - Private - Determines whether an HTML element has a specific className
			PARAMETERS:	obj - the HTML element to check
						classVal - the className to match as a string
			RETURN:	whether the element has the class as a boolean
		*/
		if (obj.nodeType === 1) {
			if (classVal && (typeOf(classVal) === 'string')) {
				var regex = new RegExp('(^|\\s)' + classVal + '(\\s|$)');
				if (regex.test(obj.className)) {
					return true;
				}
				delete regex;
			} else if (obj.className) {
				return true;
			}
		}
		return false;
	}
	this.hasClass = hasClass; // Make a public copy
	
	function addClass( obj, classVal ) {
		/*
			addClass - Private - Adds a specific className to an HTML element
			PARAMETERS:	obj - the HTML element that gets the class
						classVal - the className to add as a string
			RETURN:	none
		*/
		if (obj.nodeType === 1) {
			if (typeOf(classVal) !== 'string') {
				classVal = '';
			}
			if (classVal) {
				obj.className += ' ' + classVal;
			}
		}
	}
	this.addClass = addClass; // Make a public copy
	
	function removeClass( obj, classVal ) {
		/*
			removeClass - Private - Removes a specific className from an HTML element
			PARAMETERS:	obj - the HTML element that gets the class removed
						classVal - the className to remove as a string
			RETURN:	none
		*/
		if (obj.nodeType === 1) {
			if (typeOf(classVal) !== 'string') {
				classVal = '';
			}
			if (obj.className) {
				var regex = new RegExp('(^|\\s)' + classVal + '(\\s|$)');
				obj.className = obj.className.replace(regex, '$2');
			}
		}
	}
	this.removeClass = removeClass; // Make a public copy
	
	function filterByClass( objs, classVal ) {
		/*
			filterByClass - Private - Filters out a set of HTML elements that match a specific className
			PARAMETERS:	objs - the set of HTML elements to check through
						classVal - the className of the element to find as a string
			RETURN:	an array of HTML elements
		*/
		if (typeOf(classVal) !== 'string') {
			classVal = '';
		}
		var tagSet = [];
		for (var i = 0, j = objs.length; i < j; i++) {
			if ((objs[i].nodeType === 1) && (!classVal || hasClass(objs[i], classVal))) {
				tagSet.push(objs[i]);
			}
		}
		return tagSet;
	}
	this.filterByClass = filterByClass; // Make a public copy
	
	function elsOfClass( scope, classVal ) {
		/*
			elsOfClass - Private - Used to check (recursively) an HTML element and each node within it; these nodes will be tested against a specific className
			PARAMETERS:	scope - the HTML element to check and check within
						classVal - the className to check as a string
			RETURN:	an array of HTML elements
		*/
		if (scope && (scope.nodeType == 1) && classVal) {
			var scopeNodes = [];
			if (hasClass(scope, classVal)) {
				scopeNodes.push(scope);
			}
			if (scope.hasChildNodes()) {
				var tstChild = scope.firstChild;
				do {
					scopeNodes = scopeNodes.concat(elsOfClass(tstChild, classVal));
					tstChild = tstChild.nextSibling;
				} while (tstChild);
				delete tstChild;
			}
			return scopeNodes;
		} else {
			return [];
		}
	}
	
	function getByClass( classVal, scope ) {
		/*
			getByClass - Private - Gets the set of HTML elements that match a specific className
			PARAMETERS:	classVal - the className of the element to find as a string
						scope - the HTML element to check within; if not set, document.documentElement is used
			RETURN:	an array of HTML elements
		*/
		if (classVal) {
			if (!scope || (scope.nodeType != 1)) {
				scope = document.documentElement;
			}
			return elsOfClass(scope, classVal);
		} else {
			return [];
		}
	}
	this.getByClass = getByClass; // Make a public copy
	
	function filterByName( objs, nameVal ) {
		/*
			filterByName - Private - Filters out a set of HTML elements that match a specific name
			PARAMETERS:	objs - the set of HTML elements to check through
						nameVal - the name of the element to find as a string
			RETURN:	an array of HTML elements
		*/
		if (typeOf(nameVal) !== 'string') {
			nameVal = '';
		}
		var tagSet = [];
		for (var i = 0, j = objs.length; i < j; i++) {
			if ((objs[i].nodeType === 1) && (!nameVal || (objs[i].name == nameVal))) {
				tagSet.push(objs[i]);
			}
		}
		return tagSet;
	}
	this.filterByName = filterByName; // Make a public copy
	
	function elsOfName( scope, nameVal ) {
		/*
			elsOfName - Private - Used to check (recursively) an HTML element and each node within it; these nodes will be tested against a specific name
			PARAMETERS:	scope - the HTML element to check and check within
						nameVal - the name to check as a string
			RETURN:	an array of HTML elements
		*/
		if (scope && (scope.nodeType == 1) && nameVal) {
			var scopeNodes = [];
			if (scope.name === nameVal) {
				scopeNodes.push(scope);
			}
			if (scope.hasChildNodes()) {
				var tstChild = scope.firstChild;
				do {
					scopeNodes = scopeNodes.concat(elsOfName(tstChild, nameVal));
					tstChild = tstChild.nextSibling;
				} while (tstChild);
				delete tstChild;
			}
			return scopeNodes;
		} else {
			return [];
		}
	}
	
	function getByName( nameVal, scope ) {
		/*
			getByName - Private - Gets the set of HTML elements that match a specific name
			PARAMETERS:	nameVal - the name of the element to find as a string
						scope - the HTML element to check within; if not set, document.documentElement is used
			RETURN:	an array of HTML elements
		*/
		if (nameVal) {
			if (!scope || (scope.nodeType != 1)) {
				scope = document.documentElement;
			}
			return elsOfName(scope, nameVal);
		} else {
			return [];
		}
	}
	this.getByName = getByName; // Make a public copy
	
	function filterBy( objs, filterAttr, filterVal ) {/*
		TODO fix documentation
	*/
		/*
			filterBy - Private - Filters out a set of HTML elements that match a specific attribute
			PARAMETERS:	objs - the set of HTML elements to check through
						nameVal - the name of the element to find as a string
			RETURN:	an array of HTML elements
		*/
		if (typeOf(filterVal) !== 'string') {
			filterVal = '';
		}
		var tagSet = [];
		for (var i = 0, j = objs.length; i < j; i++) {
			if ((objs[i].nodeType === 1) && (!eval('objs[i].' + filterAttr) || !filterVal || (eval('objs[i].' + filterAttr) == filterVal))) {
				tagSet.push(objs[i]);
			}
		}
		return tagSet;
	}
	this.filterBy = filterBy; // Make a public copy
	
	function setAttr( obj, attr ) {
		/*
			setAttr - Private - Adds attributes to an HTML element
			PARAMETERS:	obj - the HTML element to modify
						attr - an enumerated object containing each of the attributes to set and their values; 'innerTxt' can be used to create a text node within the tag
			RETURN:	none
		*/
		function addOne( i ) {
			if (i === 'innerTxt') {
				appElem(document.createTextNode(attr[i]), obj);
			} else {
				try {
					eval('obj.' + i + '=attr.' + i + ';');
					// Could use both to satisfy IE bugs
					// eval('obj.setAttribute("' + i + '", attr.' + i + ');');
				} catch (err) {}
			}
		}
		
		if (obj && attr) {
			/*if (typeOf(attr) === 'array') { // untested
				for (var i in attr) {
					if (eval('([]).' + i) !== undefined) {
						iaddOne(i);
					}
				}
			} else { */
				for (var i in attr) {
					addOne(i);
				}
			//}
		}
	}
	this.setAttr = setAttr; // Make a public copy
	
	function genElem( tName, attr ) {
		/*
			genElem - Private - Creates a new HTML element as specified by the parameters
			PARAMETERS:	tName - the name of the new element (tagName) as a string
						attr - an enumerated object containing each of the attributes to set and their values; 'innerTxt' can be used to create a text node within the tag; the attr object can also be replaced by the string 'isTxt' to create a text node in place of a tag
						(attr example: {'src': '/lib/yhst-xxx/test.jpg', 'alt': 'This is an image'})
			RETURN:	the recently created HTML element
		*/
		var obj = null;
		if (tName) {
			if (attr === 'isTxt') {
				obj = document.createTextNode(tName);
			} else {
				obj = document.createElement(tName);
				if (attr) {
					setAttr(obj, attr);
					try { // IE hack to get name to set
						var ieNameErr = (attr.name !== undefined) && (obj.outerHTML.indexOf(' name=') === -1);
						if ((attr.name !== undefined) && (obj.outerHTML.indexOf(' name=') === -1)) {
							obj = document.createElement('<' + tName + ' name="' + attr.name + ((attr.border !== undefined)? ('" frameborder="' + attr.border) : '') + '">');
							setAttr(obj, attr);
						}
					} catch (err) {}
				}
			}
		}
		return obj;
	}
	this.genElem = genElem; // Make a public copy
	
	function appElem( obj, scope ) {
		/*
			appElem - Private - Appends an HTML element (or set of elements) to another element
			PARAMETERS:	obj - either an HTML element or an array of HTML elements to append
						scope - an HTML element to append to
			RETURN:	the recently appended HTML element(s)
		*/
		if (!obj) {
			return null;
		}
		if (!scope || (scope.nodeType != 1)) {
			scope = document.body; // changed from document.documentElement for IE
		}
		if (typeOf(obj) === 'array') {
			for (var i = 0, j = obj.length; i < j; i++) {
				scope.appendChild(obj[i]);
			}
			return obj;
		} else {
			return scope.appendChild(obj);
		}
		return null;
	}
	this.appElem = appElem; // Make a public copy
	
	function insertElem( obj, scope ) {/*
		TODO document
	*/
		if (!scope || (scope.nodeType != 1)) {
			scope = document.body; // changed from document.documentElement for IE
		}
		var scopeChild = scope.firstChild;
		if (typeOf(obj) === 'array') {
			for (var i = 0, j = obj.length; i < j; i++) {
				scope.insertBefore(obj[i], scopeChild);
			}
			return obj;
		} else {
			return scope.insertBefore(obj, scopeChild);
		}
		delete scopeChild;
		return null;
	}
	this.insertElem = insertElem; // Make a public copy
	
	function addElem( tName, scope, attr ) {
		/*
			addElem - Private - Creates a new HTML element as specified by the parameters and appends this new element to another existing element
			PARAMETERS:	tName - the name of the new element (tagName) as a string
						scope - an HTML element to append to
						attr - an enumerated object containing each of the attributes to set and their values; 'innerTxt' can be used to create a text node within the tag; the attr object can also be replaced by the string 'isTxt' to create a text node in place of a tag
						(attr example: {'src': '/lib/yhst-xxx/test.jpg', 'alt': 'This is an image'})
			RETURN:	the recently created HTML element
		*/
		return appElem(genElem(tName, attr), scope);
	}
	this.addElem = addElem; // Make a public copy
	
	function remElem( obj ) {/*
		TODO document
	*/
		if (obj && obj.parentNode) {
			return obj.parentNode.removeChild(obj);
		}
	}
	this.remElem = remElem; // Make a public copy
	
	function orphan( obj, kill ) {
		/*
			orphan - Private - Removes all child nodes from within a specific HTML element
			PARAMETERS:	obj - the HTML element to empty
						kill - a boolean value that determines whether or not to destroy the children as they are removed
			RETURN:	the removed nodes in an array
		*/
		var children = [];
		if (obj) {
			while (obj.firstChild) {
				if (kill) {
					delete remElem(obj.firstChild);
				} else {
					children.push(remElem(obj.firstChild));
				}
			}
		}
		return children;
	}
	this.orphan = orphan; // Make a public copy
	
	function getValue( obj ) {
		/*
			getValue - Private - Gets the value of an HTML element (for use with 'textarea', 'input', and 'select')
			PARAMETERS:	obj - the HTML element to check
			RETURN:	the value of the element
		*/
		if (obj) {
			var objNt = obj.nodeType;
			if (objNt === 1) {
				switch (obj.tagName.toLowerCase()) {
					case 'textarea':
					case 'input': /*
						TODO fix for radio buttons
					*/
						return obj.value;
					case 'select':
						return obj.options[obj.selectedIndex].value;
					default:
						if (obj.value) {
							return obj.value;
						} else {
							return stripHTML(obj.innerHTML);
						}
				}
			} else if ((objNt === 2) || (objNt === 3)) {
				return obj.nodeValue;
			}
		}
		return '';
	}
	this.getValue = getValue; // Make a public copy
	
	function ViewSize() {/*
		TODO document
	*/
		var dDe = document.documentElement, dB = document.body, viewW = 0, viewH = 0;
		if (self.innerHeight) {
			viewW = self.innerWidth;
			viewH = self.innerHeight;
		} else if (dDe && dDe.clientHeight) {
			viewW = dDe.clientWidth;
			viewH = dDe.clientHeight;
		} else if (dB) {
			viewW = dB.clientWidth;
			viewH = dB.clientHeight;
		}
		this.x = this.X = this.w = this.W = viewW;
		this.y = this.Y = this.h = this.H = viewH;
	}
	
	function getViewSize() {
		return new ViewSize();
	}
	this.getViewSize = getViewSize; // Make a public copy
	
	function PageSize() {/*
		TODO document
	*/
		var dB = document.body, pageW = 0, pageH = 0;
		if (dB.scrollHeight > dB.offsetHeight) {
			pageW = dB.scrollWidth;
			pageH = dB.scrollHeight;
		} else {
			pageW = dB.offsetWidth;
			pageH = dB.offsetHeight;
		}
		this.x = this.X = this.w = this.W = pageW;
		this.y = this.Y = this.h = this.H = pageH;
	}
	
	function getPageSize() {
		return new PageSize();
	}
	this.getPageSize = getPageSize; // Make a public copy
	
	function PageOffset() {/*
		TODO document
	*/
		var dDe = document.documentElement, dB = document.body, scrollW, scrollH;
		if (self.pageYOffset) {
			scrollW = self.pageXOffset;
			scrollH = self.pageYOffset;
		} else if (dDe && dDe.scrollTop) {
			scrollW = dDe.scrollLeft;
			scrollH = dDe.scrollTop;
		} else if (dB) {
			scrollW = dB.scrollLeft;
			scrollH = dB.scrollTop;
		}
		this.x = this.X = this.w = this.W = scrollW;
		this.y = this.Y = this.h = this.H = scrollH;
	}
	
	function getPageOffset() {
		return new PageOffset();
	}
	this.getPageOffset = getPageOffset; // Make a public copy
	
	function centerObj( obj, pad ) {
		if (obj.nodeType === 1) {
			var suffix = 'px', sizes = getViewSize(), offsets = getPageOffset();
			pad = forceInt(pad);
			var yVal = Math.round(sizes.h / 2 - (obj.offsetHeight / 2) + offsets.h);
			var xVal = Math.round(sizes.w / 2 - (obj.offsetWidth / 2) + offsets.w);
			obj.style.top = (((yVal > pad) || (pad < 0))? yVal : pad) + suffix;
			obj.style.left = (((xVal > pad) || (pad < 0))? xVal : pad) + suffix;
		}
	}
	this.centerObj = centerObj; // Make a public copy
	
	function stretchObj( obj, pad ) {/*
		TODO document
	*/
		if (obj.nodeType === 1) {
			var suffix = 'px', pSizes = getPageSize(), vSizes = getViewSize();
			var hVal = (pSizes.h > vSizes.h)? pSizes.h : vSizes.h;
			var wVal = (pSizes.w > vSizes.w)? pSizes.w : vSizes.w;
			pad = forceInt(pad);
			obj.style.top = pad + suffix;
			obj.style.left = pad + suffix;
			obj.style.height = (hVal - (pad * 2)) + suffix;
			obj.style.width = (wVal - (pad * 2)) + suffix;
			delete suffix, hVal, wVal, pSizes, vSizes;
		}
	}
	this.stretchObj = stretchObj; // Make a public copy
	
	var ie6selectFix = function() {/*
		TODO document
	*/
		if ((navigator.appName == 'Microsoft Internet Explorer') && navigator.appVersion.match('MSIE 6.0')) {
			document.write('<style type=\'text/css\'>select.scHideSelect{visibility:hidden !important;}</style>');
			return function( sFlag, scope ) {
				var aSelectObj = SC.getByTag('select', scope);
				var classVal = 'scHideSelect';
				for (var i = 0, j = aSelectObj.length; i < j; i++) {
					if ((sFlag == 'hide') && !SC.hasClass(aSelectObj[i], classVal)) {
						SC.addClass(aSelectObj[i], classVal);
					} else if(SC.hasClass(aSelectObj[i], classVal)) {
						SC.removeClass(aSelectObj[i], classVal);
					}
				}
			};
		}
		return function() {};
	}();
	this.ie6selectFix = ie6selectFix; // Make a public copy
	
	//////////////////// Functions for Add to Cart Handling ////////////////////
	
	var yhstVal = '';
	function scrapeStoreId() {/*
		TODO document
	*/
		if (window.csell_page_data && csell_page_data['si']) {
			return csell_page_data['si'].toLowerCase();
		}
		
		var urlMatch = document.location.href.match(/^http\:\/\/([\w\d\-]+?)\.stores.yahoo.net/);
		if (urlMatch) {
			return urlMatch[1].toLowerCase();
		}
		delete urlMatch;
		
		var inTags = getByTag('input');
		var vwCat = filterByName(inTags, 'vwcatalog');
		if (vwCat.length) {
			return vwCat[0].value.toLowerCase();
		}
		delete vwCat;
		
		var aTags = getByTag('input');
		for (var i = 0, j = aTags.length; i < j; i++) {
			if (aTags[i].href) {
				var tempMatch = aTags[i].href.match(/^https?\:\/\/order\.store\.yahoo\.(?:com|net)\/cgi\-bin\/wg\-order\?([\d\w\-]+?)$/);
				if (tempMatch) {
				return tempMatch[1].toLowerCase();
				}
			}
		}
		delete aTags;
		
		var cat = filterByName(inTags, 'catalog');
		if (cat.length) {
			return cat[0].value.toLowerCase();
		}
		delete cat, inTags;
		return '';
	}
	
	function setStoreId( storeId ) {/*
		TODO document
	*/
		if (storeId && (typeOf(storeId) == 'string')) {
			yhstVal = storeId;
		}
	}
	this.setStoreId = setStoreId; // Make a public copy
	
	function getStoreId() {/*
		TODO document
	*/
		return yhstVal || (yhstVal = scrapeStoreId());
	}
	this.getStoreId = getStoreId; // Make a public copy
	
	function inFrame() {/*
		TODO document
	*/
		if (window.parent != window.self) {
			return true;
		}
		return false;
	}
	this.inFrame = inFrame; // Make a public copy
	
	function orderForms() {
		/*
			orderForms - Private - Gets the set of HTML form elements that submit to Yahoo!'s cart
			PARAMETERS:	none
			RETURN:	a set of HTML form elements
		*/
		var allForms = getByTag('form');
		var oForms = [];
		for (var i = 0, j = allForms.length; i < j; i++) {
			if (allForms[i].action.match(/store\.yahoo\.(?:com|net)\/cgi-bin\/wg\-order\?/)) {
				oForms.push(allForms[i]);
			}
		}
		delete allForms;
		return oForms;
	}
	this.orderForms = orderForms; // Make a public copy
	
	var defaultIFname = 'scHddnIFrame';
	var iFrameLock = false;
	var iFrameObj = null;
	function targetForms( iFrameVal, callBackFn, lock ) {
		/*
			hijackForms - Private - Sets the target (to an iFrame) for each of the set of HTML form elements that submit to Yahoo!'s cart
			PARAMETERS:	iFrameVal - the iFrame to set as the target; this can be an HTML element or the id or name of an HTML element
						lock - cancels any future (hijackForms) function calls after this one
			RETURN:	none
		*/
		var oForms = orderForms();
		if (oForms) {
			if (!iFrameLock) {
				if (lock) {
					iFrameLock = true;
				}
				if (iFrameObj) {
					remElem(iFrameObj);
					iFrameObj = null;
				}
				var ifName = '';
				if (iFrameVal) {
					if (typeOf(iFrameVal) === 'html') {
						if (iFrameVal.tagName === 'iframe') {
							iFrameObj = iFrameVal;
						}
					} else if (typeOf(iFrameVal) === 'string') {
						iFrameObj = $(iFrameVal);
						if (!iFrameObj) {
							iFrameObj = (iFrameObj = filterByName(getByTag('iframe'), iFrameVal))? iFrameObj[0] : null;
						}
					}
				}
				if (!iFrameObj) {
					iFrameObj = $(defaultIFname);
					if (!iFrameObj) {
						ifName = defaultIFname;
						iFrameObj = genElem('iframe', {name: ifName, id: ifName, height: '0', width: '0', 'border': '0'});
						// iFrameObj.style.display = 'none';  // Do not do this! Safari 2.0.4 will not read display: none iframes
						appElem(iFrameObj);
					}
				} else {
					ifName = iFrameObj.name;
					if (!ifName) {/*
						TODO look into issues with ie iframe name setting
					*/
						ifName = iFrameObj.name = defaultIFname;
					}
				}
				for (var i = 0, j = oForms.length; i < j; i++) {
					oForms[i].target = ifName;
					addEvt(oForms[i], 'submit', atcSubmitHandler);
				}
				addEvt(iFrameObj, 'load', atcDoneHandler);
				delete ifName;
			} else if (iFrameObj) {
				var ifName = iFrameObj.name;
				for (var i = 0, j = oForms.length; i < j; i++) {
					if (oForms[i].target !== ifName) {
						oForms[i].target = ifName;
						addEvt(oForms[i], 'submit', atcSubmitHandler);
					}
				}
				delete ifName;
			}
			if (iFrameObj && callBackFn) {
				setATCcallback(callBackFn);
			}
		}
	}
	this.targetForms = targetForms; // Make a public copy
	
	//var atcResponseMsgs = [];
	var atcResponseType = 0;
	function setATCresponse( resObj ) {/*
		TODO document
	*/
		atcResponseType = forceInt(resObj.resType);
		atcDoneHandler();
	}
	this.setATCresponse = setATCresponse; // Make a public copy
	
	function createATCresponse( domainVal ) {/*
		TODO document
	*/
		if (SC.$('ys_cartInfo') && SC.inFrame() && SC.cartParser) {
			var cartObj = SC.cartParser();
			var alertMsg = '';
			var iFiF = $('scFrameWorkFrame');
			if (iFiF) {
				SC.addElem('iframe', iFiF, {src:'http://www.scproddev.com/sc-framework-frame.html?resType=' + ((cartObj.errors.length)? '-1' : '1'), height:'0', width:'0', border:'0'});
			}
			for (var i = 0, j = cartObj.errors.length; i < j; i++) {
				alertMsg += cartObj.errors[i] + '\n';
			}
			delete cartObj;
			if (alertMsg) {
				alert(alertMsg);
			}
			delete alertMsg;
		}
	}
	this.createATCresponse = createATCresponse; // Make a public copy
	
	function sendATCresponse( resObj ) {/*
		TODO document
	*/
		if (inFrame()) {
			var targWinTxt = 'window.parent';
			while (eval(targWinTxt) != window.top) { /*
				TODO match domains for SC check too...
			*/
				try {
					if (eval(targWinTxt).window.SC) {
						break;
					} else {
						targWinTxt += '.parent';
					}
				} catch (err) {
					targWinTxt += '.parent';
				}
			}
			try {
				var targWin = eval(targWinTxt + '.window');
				if (targWin.SC) {
					targWin.SC.setATCresponse(resObj);
				}
			} catch (err) {}
		}
	}
	this.sendATCresponse = sendATCresponse; // Make a public copy
	
	var atcMsg = 'Please be patient while we add your product(s) to cart';
	var atcThrbImg = '';
	function setATCmsg( str, imgSrc ) {/*
		TODO document
	*/
		if (str && (typeOf(str) == 'string')) {
			atcMsg = str;
		}
		if (imgSrc && (typeOf(imgSrc) == 'string')) {
			atcThrbImg = imgSrc;
		}
	}
	this.setATCmsg = setATCmsg; // Make a public copy
	
	var atcInProgress = false;
	var atcCallbacks = {};
	function setATCcallback( func ) {
		if (!atcCallbacks[func]) {
			atcCallbacks[func] = func;
		}
	}
	
	function runATCcallbacks() {
		if (atcInProgress) {
			atcInProgress = false;
			for (var i in atcCallbacks) {
				atcCallbacks[i](atcResponseType);
			}
		}
	}
	
	function atcDoneHandler( ev ) {/*
		TODO document
	*/
		try {
			var atcWrap = $('scATCwrapper');
			var atcMsgBox = $('scATCmsg');
			remElem(atcWrap);
			remElem(atcMsgBox);
			ie6selectFix();
		} catch (err){}
		runATCcallbacks(ev);
	}
	
	function atcSubmitHandler( ev ) {/*
		TODO document
	*/
		atcInProgress = true;
		atcResponseType = 0;
		ie6selectFix('hide');
		var atcWrap = addElem('div', null, {id:'scATCwrapper'});
		stretchObj(atcWrap);
		var atcMsgBox = genElem('div', {id:'scATCmsg'});
		atcWrap.style.position = atcMsgBox.style.position = 'absolute';
		atcWrap.style.zIndex = 2000;
		atcMsgBox.style.zIndex = 2001;
		if (!atcThrbImg) {
			atcThrbImg = '//lib.store.yahoo.net/lib/' + getStoreId() + '/scATCthrobber.gif';
		}
		addElem('img', atcMsgBox, {src:atcThrbImg});
		atcMsgBox.innerHTML += '<br />' + atcMsg;
		appElem(atcMsgBox);
		centerObj(atcMsgBox);
	}
	
	//////////////////// Functions for Event Handling ////////////////////
	
	var addEvt = function() {
		/*
			addEvt - Private - Adds an Event listener to a specific HTML element (addEvt usage example: addEvt(obj, 'click', myFunc);)
			PARAMETERS:	el - the HTML element to listen for Events on
						type - the Event to listen for
						fn - the function to add
			RETURN:	none
		*/
		if ( window.addEventListener ) {
			return function(el, type, fn) {
				el.addEventListener(type, fn, false);
			};
		} else if ( window.attachEvent ) {
			return function(el, type, fn) {
				var f = function() {
					fn.call(el, window.event);
				};
				el.attachEvent('on'+type, f);
			};
		} else {
			return function(el, type, fn) {
				el['on'+type] = fn;
			};
		}
	}();
	this.addEvt = addEvt; // Make a public copy
	
	var removeEvt = function() {
		/*
			removeEvt - Private - Removes an Event listener from a specific HTML element; events must be removed EXACTLY as they are created
			PARAMETERS:	el - the HTML element to listen for Events on
						type - the Event to listen for
						fn - the function to remove
			RETURN:	none
		*/
		if ( window.addEventListener ) {
			return function(el, type, fn) {
				el.removeEventListener(type, fn, false);
			};
		} else if ( window.attachEvent ) {
			return function(el, type, fn) {
				var f = function() {
					fn.call(el, window.event);
				};
				el.detachEvent('on'+type, f);
			};
		} else {
			return function(el, type, fn) {
				el['on'+type] = null;
			};
		}
	}();
	this.removeEvt = removeEvt; // Make a public copy
	
	var dispatchEvt = function() {
		/*
			dispatchEvt - Private - Dispatch an Event on a specific HTML element
			PARAMETERS:	el - the HTML element to dispatch the Event on
						type - the Event to dispatch
						module - the Event module
			RETURN:	none
		*/
		if (document.createEvent) {
			return function(el, type, module) {
				var evObj = document.createEvent((module)? module : 'Events');
				evObj.initEvent(type, true, true);
				el.dispatchEvent(evObj);
			};
		} else if (document.createEventObject) {
			return function(el, type, module) {
				el.fireEvent('on' + type);
			};
		} else {
			return function(el, type, module) {
				eval('el.on' + type + '();');
			};
		}
	}();
	this.dispatchEvt = dispatchEvt; // Make a public copy
	
	function getTarget( ev ) {
		/*
			getTarget - Private - Gets the Event target (HTML element)
			PARAMETERS:	ev - the Event that was dispatched
			RETURN:	an HTML element
		*/
		var targ = ev.target || ev.srcElement;
		if (targ && ((targ.nodeType == 3) || (targ.nodeType == 4))) {
			targ = targ.parentNode;
		}
		return targ;
	}
	this.getTarget = getTarget; // Make a public copy
	
	function killDefault( ev ) {
		/*
			killDefault - Private - Prevents the default Event action
			PARAMETERS:	ev - the Event that was dispatched
			RETURN:	none
		*/
		if (ev && ev.preventDefault) {
			ev.preventDefault();
		} else {
			ev = ev || window.event;
			ev.returnValue = false;
		}
	}
	this.killDefault = killDefault; // Make a public copy
	
	function killPropagation( ev ) {
		/*
			killPropagation - Private - Stops Event propagation
			PARAMETERS:	ev - the Event that was dispatched
			RETURN:	none
		*/
		if (ev && ev.stopPropagation) {
			ev.stopPropagation();
		} else {
			ev = ev || window.event;
			ev.cancelBubble = true;
		}
	}
	this.killPropagation = killPropagation; // Make a public copy
	
	//////////////////// Functions for Number manipulation ////////////////////
	
	function toPrice( num, currency ) {
		/*
			toPrice - Private - Converts a number value into a formatted price string; commas are added at thousand mark, decimals are forced to 2 digits, and the currency is added to the front of the string.
			PARAMETERS:	num - the price as a number
						currency - the currency as a string
			RETURN:	the price with a specific currency as a string
		*/
		if (num) {
			num = forceFloat(num).toFixed(2).toString();
			var intEnd = num.indexOf('.') - 3;
			for (var i = intEnd, j = 0; i > j; i -= 3) {
				num = num.substring(0, i) + ',' + num.substring(i, num.length);
			}
			num = num;
		} else {
			num = '0.00';
		}
		if (!currency) {
			currency = '';
		} else if (typeOf(currency) !== 'string') {
			currency = currency.toString();
		}
		return currency + num;
	}
	this.toPrice = toPrice; // Make a public copy
	
	function forceInt( num ) {
		/*
			forceInt - Private - Forces data of an undefined type into an integer value
			PARAMETERS:	num - should be an integer value, but may be of a different type
			RETURN:	an integer value
		*/
		return (isNaN(num = parseInt((typeOf(num) === 'string')? num.replace(',','').replace(/^.*?([\+\-]?[\d\.]+).*?$/, '$1') : num)))? 0 : num;
	}
	this.forceInt = forceInt; // Make a public copy
	
	function forceFloat( num ) {
		/*
			forceFloat - Private - Forces data of an undefined type into an floating point value
			PARAMETERS:	num - should be an floating point value, but may be of a different type
			RETURN:	an floating point value
		*/
		return (isNaN(num = parseFloat((typeOf(num) === 'string')? num.replace(',','').replace(/^.*?([\+\-]?[\d\.]+).*?$/, '$1') : num)))? 0.0 : num;
	}
	this.forceFloat = forceFloat; // Make a public copy
	
};
var SC = new SC();

if (!Array.prototype.indexOf) { // Mozilla Dev Center supplied code for JS compatibility
	Array.prototype.indexOf = function(elt /*, from*/) {
		var len = this.length;
		var from = Number(arguments[1]) || 0;
		from = (from < 0)? Math.ceil(from) : Math.floor(from);
		if (from < 0) from += len;
		for (; from < len; from++) {
			if (from in this && this[from] === elt) return from;
		}
		return -1;
	};
}

String.prototype.trimEnd = function() {
	if (this == null) return null;
	return this.replace(/((\s*\S+)*)\s*/, '$1');
};

String.prototype.trimStart = function() {
	if (this == null) return null;
	return this.replace(/\s*((\S+\s*)*)/, '$1');
};

String.prototype.trim = function() {
	if (this == null) return null;
	return this.trimEnd(this.trimStart());
};

String.prototype.startWith = function( str ) {
	if (this.substring(0, str.length) == str) return true;
	return false;	
}

//////////////////// Legacy functions below ////////////////////

SC.N = SC.getTagArray = SC.getByTag;

SC.convertArgSet = function( args, start ) {
	var attributeSet = null;
	if (args.length > start) {
		attributeSet = {};
		for (var i = start, j = args.length; i < j; i += 2) {
			if (args[i] == 'txt') {
				attributeSet['innerTxt'] = args[i+1];
			} else {
				attributeSet[args[i]] = args[i+1];
			}
		}
	}
	return attributeSet;
};

SC.setAttributes = function( oObj ) {
	var attributeSet = null;
	if (arguments.length > 1) {
		attributeSet = SC.convertArgSet(arguments, 1);
	}
	SC.setAttr(oObj, attributeSet)
};

SC.F = SC.getObj = function() {
	if (document.getElementById) {
		return SC.$;
	} else if (document.all) {
		return function(o) {
			return document.all[o];
		};
	} else if (document.layers) {
		return function(o) {
			return document.layers[o];
		};
	}
}();

SC.createElement = function( sTagName ) {
	var attributeSet = null;
	if (arguments.length > 1) {
		attributeSet = SC.convertArgSet(arguments, 1);
	}
	return SC.genElem(sTagName, attributeSet);
};

SC.appendElement = function( scope ){
	scope = scope || document.body;
	if (arguments.length > 1){
		var args = [];
		for (var i = 1, j = arguments.length; i < j; i++) {
			args.push(arguments[i]);
		}
		SC.appElem(args, scope);
	} else {
		SC.appElem(scope);
	}
};

SC.createAppend = function( sTagName, scope ){
	scope = scope || document.body;
	var attributeSet = null;
	if (arguments.length > 2) {
		attributeSet = SC.convertArgSet(arguments, 2);
	}
	SC.appElem(SC.genElem(sTagName, attributeSet), scope);
};

SC.showHide = function( oObj, sDisplay ){
	oObj.style.display = sDisplay;
};

SC.addListener = SC.addEvt;

SC.removeListener = SC.removeEvt;

SC.parseFloat = SC.forceFloat;

SC.getInputByName = function( x, tag, type ) {
	if (x) {
		var tags = (tag)? [tag] : ['input', 'select', 'textarea', 'button'];
		for (var i = 0, j = tags.length; i < j; i++) {
			var els = SC.filterByName(SC.getByTag(tags[i]), x);
			if (els) {
				if (type) {
					for (var g = 0, h = els.length; g < h; g++) {
						if (els[g].type === type) {
							return els[g];
						}
					}
				}
				return els[0];
			}
		}
	}
	return null;
}

SC.getInputValueByName = function( x, tag, type ) {
	return SC.getValue(SC.getInputByName(x,tag,type));
}

SC.getByClassName = function( x, tag, p, first ) {
	var els = SC.filterByClass(SC.getByTag(tag,p), x);
	return (first)? els.slice(0,1) : els;
}

