/*--------------------------------------------------------
 *  Function:  getObject
 *  
 *  Description:
 *  Accepts an object reference or a string, returns an
 *  object reference
 *  
 *  Parameters:
 *  pRef	string or object	ID of HTML DOM entity, or
 *								reference to an object
 *	Exceptions:
 *	ObjectNotFoundException
 *	InvalidObjectRefException
 *  
 *  Return:
 *  object		Reference to named or passed DOM object.
 *-------------------------------------------------------*/

function getObject(pRef) {
	var obj;

	if (typeof(pRef) == "object") {
		obj = pRef;
	} else if (typeof(pRef) == "string") {
		obj = document.getElementById(pRef);
		if (!obj) {
			throw new ObjectNotFoundException(pRef);
		}
	} else {
		throw new InvalidObjectRefException(pRef);
	}
	
	return obj;
}

/*--------------------------------------------------------
 *  Function:  filterByClassName
 *  
 *  Description:
 *  Filters an array of DOM elements, returning only
 *  elements that match a particular class name.
 *  
 *  Parameters:
 *  pObjects	array	Array of DOM elements to filter
 *  pClassName	string	The class name to match
 *  
 *  Return:
 *  array		An array of DOM objects with the matching
 *				class name.
 *-------------------------------------------------------*/

function filterByClassName(pObjects, pClassName) {
	var elements = new Array();

	for (var i = 0; i < pObjects.length; i++) {
		var obj = pObjects[i];
		if (obj.className) {
			var classNames = obj.className.split(' ');
			for (var j = 0; j < classNames.length; j++) {
				if (classNames[j] == pClassName) {
					elements[elements.length] = obj;
					break;
				}
			}
		}
	}
  
	return elements;
}

/*--------------------------------------------------------
 *  Function:  getObjectsByClassName
 *  
 *  Description:
 *  Returns an array of DOM objects that match a particular
 *  class name.
 *  
 *  Parameters:
 *  pClassName	string	The class name to match
 *  
 *  Return:
 *  array		An array of DOM objects with the matching
 *				class name.
 *-------------------------------------------------------*/

function getObjectsByClassName(pClassName) {
	var all = document.getElementsByTagName('*') || document.all;
	
	return filterByClassName(all, pClassName);
}

/*--------------------------------------------------------
 *  Function:  getObjectsByClassName
 *  
 *  Description:
 *  Returns an array of DOM objects that match a particular
 *  class name that are a descendent of pParentId
 *  
 *  Parameters:
 *  pParentId	string	The id of a parent node to start search
 *  pClassName	string	The class name to match
 *  
 *  Return:
 *  array		An array of DOM objects with the matching
 *				class name.
 *-------------------------------------------------------*/

function getChildObjectsByClassName(pParentId,pClassName) {
	var parent = document.getElementById(pParentId);
	var all = parent.getElementsByTagName('*');
	return filterByClassName(all,pClassName);
}

/*--------------------------------------------------------
 *  Function:  getChildrenByClassName
 *  
 *  Description:
 *  Accepts a reference to a DOM object and returns an array
 *  of child elements that match a particular class name.
 *  
 *  Parameters:
 *  pRef		string or object	The parent DOM element
 *									to search
 *  pClassName	string				The class name to match
 *  
 *  Return:
 *  array		An array of DOM objects with the matching
 *				class name.
 *-------------------------------------------------------*/

function getChildrenByClassName(pRef, pClassName) {
	var children = getObject(pRef).childNodes;

	return filterByClassName(children, pClassName);
}

/*--------------------------------------------------------
 *  Function:  getDescendantsByClassName
 *  
 *  Description:
 *  Accepts a reference to a DOM object and returns an array
 *  of all descendant elements that match a particular class
 *  name.
 *  
 *  Parameters:
 *  pRef		string or object	The parent DOM element
 *									to search
 *  pClassName	string				The class name to match
 *  
 *  Return:
 *  array		An array of DOM objects with the matching
 *				class name.
 *-------------------------------------------------------*/

function getDescendantsByClassName(pRef, pClassName) {
	var descendants = getObject(pRef).getElementsByTagName('*');

	return filterByClassName(descendants, pClassName);
}

/*--------------------------------------------------------
 *  Function:  compareById
 *  
 *  Description:
 *  Compares two DOM elements by ID.  Uses standard string
 *  comparison against language character set numeric codes.
 *  
 *  Parameters:
 *  pObjectA	object	The first DOM element to compare
 *  pObjectB	object	The second DOM element to compare
 *  
 *  Return:
 *  integer		-1 if pObjectB is greater than pObjectA,
 *				0 if equal, +1 if pObjectA is greater
 *-------------------------------------------------------*/

function compareById(pObjectA, pObjectB) {
	if (pObjectA.id > pObjectB.id) {
		return 1;
	} else if (pObjectA.id < pObjectB.id) {
		return -1;
	} else {
		return 0;
	}
}

/*--------------------------------------------------------
 *  Function:  createElement
 *  
 *  Description:
 *  Utility function for dynamic generation of DOM
 *  elements.  Sets attributes on the element based on a
 *  hash of attribute names and values.
 *
 *	Example:
 *		var newElement = createElement('a',
 *		{
 *			'class': 'myClass',
 *			'href': 'http://www.mydomain.com',
 *			'onmouseover': 'jsFunction();'
 *		});
 *  
 *  Parameters:
 *  pElement	string		DOM element to create
 *							(e.g. 'div', 'a', 'h1')
 *	pAttributes	hash		Hash of attributes and their
 *							values.
 *  
 *  Return:
 *  object		The newly created element
 *-------------------------------------------------------*/

function createElement(pElement, pAttributes) {
	// Make the element
	var newElement = document.createElement(pElement);
	
	// Set the attributes
	for (var attr in pAttributes) {
		switch (attr) {
			/*
				IE uses 'className' for setAttribute and getAttribute.
				All other browsers use 'class'.
				Setting the className property is cross-browser
			*/
			case 'class':
			case 'className':
				newElement.className = pAttributes[attr];
				break;
			/*
				setAttribute works for event handlers on Firefox, but not
				on IE.  Explicitly set the event handlers as functions
				using new Function constructor.
			*/
			case 'onblur':
				newElement.onblur = new Function(pAttributes[attr]);
				break;
			case 'onclick':
				newElement.onclick = new Function(pAttributes[attr]);
				break;
			case 'ondblclick':
				newElement.ondblclick = new Function(pAttributes[attr]);
				break;
			case 'onfocus':
				newElement.onfocus = new Function(pAttributes[attr]);
				break;
			case 'onkeydown':
				newElement.onkeydown = new Function(pAttributes[attr]);
				break;
			case 'onkeypress':
				newElement.onkeypress = new Function(pAttributes[attr]);
				break;
			case 'onkeyup':
				newElement.onkeyup = new Function(pAttributes[attr]);
				break;
			case 'onmousedown':
				newElement.onmousedown = new Function(pAttributes[attr]);
				break;
			case 'onmousemove':
				newElement.onmousemove = new Function(pAttributes[attr]);
				break;
			case 'onmouseout':
				newElement.onmouseout = new Function(pAttributes[attr]);
				break;
			case 'onmouseover':
				newElement.onmouseover = new Function(pAttributes[attr]);
				break;
			case 'onmouseup':
				newelement.onmouseup = new Function(pAttributes[attr]);
				break;
			case 'onresize':
				newElement.onresize = new Function(pAttributes[attr]);
				break;

			/* Default action is to set the attribute */
			default:
				newElement.setAttribute(attr, pAttributes[attr]);
				break;
		}
	}
	return newElement;
}

/*--------------------------------------------------------
 *  Function:  getWindowWidth
 *  
 *  Description:
 *  Cross browser method for determining total width of
 *	browser window (inside chrome)
 *  
 *  Parameters:
 *  None
 *  
 *  Return:
 *  integer		Width of browser window in pixels
 *-------------------------------------------------------*/

function getWindowWidth() {
	if (isNaN(window.innerWidth)) {
		return document.body.parentElement.clientWidth;
	} else {
		return window.innerWidth;
	}
}

/*--------------------------------------------------------
 *  Function:  getWindowHeight
 *  
 *  Description:
 *  Cross browser method for determining total height of
 *	browser window (inside chrome)
 *  
 *  Parameters:
 *  None
 *  
 *  Return:
 *  integer		Height of browser window in pixels
 *-------------------------------------------------------*/

function getWindowHeight() {
	if (isNaN(window.innerHeight)) {
		return document.body.parentElement.clientHeight;
	} else {
		return window.innerHeight;
	}
}

/*--------------------------------------------------------
 *  Function:  getScrollX
 *  
 *  Description:
 *  Cross browser method for determining horizontal scroll of
 *  browser window
 *  
 *  Parameters:
 *  None
 *  
 *  Return:
 *  integer		Number of pixels scrolled horizontally
 *-------------------------------------------------------*/

function getScrollX() {
	if (isNaN(window.scrollX)) {
		// IE compatibility mode
		return document.body.parentElement.scrollLeft;
	} else {
		return window.scrollX;
	}
}

/*--------------------------------------------------------
 *  Function:  getScrollY
 *  
 *  Description:
 *  Cross browser method for determining vertical scroll of
 *  browser window
 *  
 *  Parameters:
 *  None
 *  
 *  Return:
 *  integer		Number of pixels scrolled vertically
 *-------------------------------------------------------*/

function getScrollY() {
	if (isNaN(window.scrollY)) {
		// IE compatibility mode
		return document.body.parentElement.scrollTop;
	} else {
		return window.scrollY;
	}
}

/*--------------------------------------------------------
 *	Function:  clearFld
 *	
 *	Description:
 *	Clears default text in form field on focus
 *
 *	Paramaters:
 *	pFld		string or object	Reference to the field to clear.
 *
 *	Return:
 *	none
 *------------------------------------------------------*/
 
function clearFld(pFld) {
	getObject(pFld).value = "";
	return false;
}

/*--------------------------------------------------------
 *	Function:  changeClass
 *	
 *	Description:
 *	Changes the class of an element.  Replaces pOldClass
 *  with pNewClass.  If pOldClass does not exist, pNewClass
 *  is still added.
 *
 *	Parameters:
 *	pRef		string or object	Reference to element that
 *									changes class
 *	pOldClass	string		Old class to replace
 *	pNewClass	string		New class to add
 *
 *	Return:
 *	none
 *------------------------------------------------------*/
 
function changeClass(pRef, pOldClass, pNewClass) {
	var obj = getObject(pRef);
	
	var newClassStr = pNewClass;
	
	if (obj.className) {
		var classNames = obj.className.split(' ');
		for (var i = 0; i < classNames.length; i++) {
			if (classNames[i] != pOldClass) {
				newClassStr += ' ' + classNames[i];
			}
		}
	}
	
	obj.className = newClassStr;
}


/*--------------------------------------------------------
 *	Function:  popWin
 *------------------------------------------------------*/

function popWin(width,height,path) {
	var features = "height="+height+",width="+width;
	features = features+",menubar=no,location=no,scrollbars=no,status=no,titlebar=no,toolbar=no,resizeable=yes";
	var w = window.open(path,"popWin",features,false);
	return false;
}


/*--------------------------------------------------------
 * Utility Exceptions
 *------------------------------------------------------*/

/*--------------------------------------------------------
 *	Exception:  ObjectNotFoundException
 *	
 *	Description:
 *	Runtime exception. Indicates that an object reference
 *	was not found.
 *
 *	Parameters:
 *	pRef		string		Erroneous reference to object
 *------------------------------------------------------*/
 
function ObjectNotFoundException(pRef) {
   this.ref = pRef;

   this.toString = function() {
      return "Object not found: " + pRef;
   };
}

/*--------------------------------------------------------
 *	Exception:  InvalidObjectRefException
 *	
 *	Description:
 *	Runtime exception. Indicates an invalid reference
 *	to an object (not a string or object reference) was used.
 *
 *	Parameters:
 *	pRef		not string or object	Erroneous reference to object
 *------------------------------------------------------*/
 
function InvalidObjectRefException(pRef) {
	this.ref = pRef;
	
	this.toString = function() {
		return "Invalid object reference: " + pRef;
	};
}

/*--------------------------------------------------------
 *	Application Specific Functions
 *------------------------------------------------------*/


/*--------------------------------------------------------
 *  Function:  checkAllChecks
 *  
 *  Description:
 *  Manages checked status of a series of checkboxes
 *  including a "check all" control.
 *  
 *  Parameters:
 *	pRef		string or object	Reference to checkbox that changes.
 *	pAll		string or object	Reference to the "check all" checkbox.
 *	pChecks		array	Array of references to checkbox series.
 *  
 *  Return:
 *  none
 *-------------------------------------------------------*/

function checkAllChecks(pRef, pAll, pChecks) {
	var changed = getObject(pRef);
	var all = getObject(pAll);
		
	// User changed the all checkbox
	if (changed == all) {
		// Set all of the checkboxes accordingly
		for (var i = 0; i < pChecks.length; i++) {
			getObject(pChecks[i]).checked = changed.checked;
		}
	} else {
		var allChecked = true;
		// Check to see if all checks are checked
		for (var i = 0; i < pChecks.length; i++) {
			if (!getObject(pChecks[i]).checked) {
				allChecked = false;
				break;
			}
		}
		// Only check the all checkbox if all checkboxes haved been checked
		all.checked = allChecked;
	}
}

/*--------------------------------------------------------
 *  Function:  charCounter
 *  
 *  Description:
 *  Counts the number of characters entered in input field
 *  and updates display of that number.
 *
 *	pText contains the string to display the number.
 *  It should contain the string "{CHAR}" indicating
 *	where the character count should be displayed.
 *  
 *  Parameters:
 *	pInput		string or object	Reference to input field
 *	pOutput		string or object	Reference to output display
 *	pText		string		Message text for display
 *	pMax		integer		Maximum number of characters allowed
 *  
 *  Return:
 *  none
 *-------------------------------------------------------*/

function charCounter(pInput, pOutput, pText, pMax) {
	var inputObj = getObject(pInput);
	var outputObj = getObject(pOutput);
	var length = inputObj.value.length;

	// Ensure we are not over the max
	if (length >= pMax) {
		length = pMax;
		inputObj.value = inputObj.value.substr(0, pMax);
	}

	// Display the character count
	outputObj.innerHTML = pText.replace('{CHAR}', length);
}

/* ----------------------------------------------------
***	TEXT UTILITIES
***---------------------------------------------------- */


function LTrim( value ) {
	var re = /\s*((\S+\s*)*)/;
	return value.replace(re, "$1");
}

/* ---------------------------------------------------- */

function RTrim( value ) {
	var re = /((\s*\S+)*)\s*/;
	return value.replace(re, "$1");
}

/* ---------------------------------------------------- */

function trim( value ) {
	return LTrim(RTrim(value));
}


/* ---------------------------------------------------- */

function pad (pNum) {
	if (pNum < 10) {
		return "0" + pNum;
	} else {
		return pNum;
	}
}


/*--------------------------------------------------------
 *	Validation
 *------------------------------------------------------*/

function isNumber (val) {
	if (isNaN(parseInt(val))) {
		return false;
	}
	return true;
}



/*--------------------------------------------------------
 *	Miscellaneous Functions
 *------------------------------------------------------*/

/* SEARCH */
function search_onsubmit(){
	preSearchSubmit();
	return true;
}

function submitSearch(frmId){
	var frm = document.getElementById(frmId);
	if (frm.qt.value != 'Search PCWorld') {
		preSearchSubmit(frm);
		frm.submit();
	}
}

function preSearchSubmit(frm){
	if(null != frm.sw && frm.sw.checked==false) frm.old_qt.value="";
}

/* COOKIES */
function pcw_setCookie(name, value, expires, domain){
	pcw_setRawCookie(name, escape(value), expires, domain);
}

function pcw_setRawCookie(name, value, expires, domain){
	if(navigator.cookieEnabled){
		var cookie = name+"="+value+";";
		
		//handle session cookies
		if (null != expires) {
			cookie += "expires="+expires.toGMTString()+";";
		}
		cookie += "domain="+domain+";";
		cookie += "path=/";
		document.cookie = cookie;
	}
}

function pcw_writeCookie(name, value, expires, domain){
	pcw_setCookie(name, value, expires, domain);
}

function pcw_readCookie(name){
	return unescape(pcw_readRawCookie(name));
}

function pcw_readRawCookie(name){
	if(navigator.cookieEnabled&&document.cookie!=''){
		var strAll = document.cookie;
		var i1 = strAll.indexOf(name);
		if(i1!=-1){
			// skip name and '='
			i1 = i1+name.length+1;
			i2 = strAll.indexOf(';', i1);
			if(i2==-1) i2 = strAll.length;
			return strAll.substring(i1, i2);
		}
	}
	return "";
}

function pcw_removeCookie(name, domain){
	if(navigator.cookieEnabled){
		var d = new Date();
		d.setDate(d.getDate()-30);
		document.cookie=name+"=;expires="+d.toGMTString()+";domain="+domain+";path=/";
	}
}



var ord = pcw_GetOrd(8);

//generates random number of length integers
function pcw_GetOrd (length) {
	var ord = "";
	for(var o=0;o<length;o++) {
		ord = ord + Math.floor((Math.random()*10));
	}
	ord = ord + "?";
	return ord;
}

function ar(u,i) {
	var unit= 'ad'+u;
	ms = i*1000;
	var f = function() {
		ref(unit);
	};
	setInterval(f,ms);
}

var defaultAdType = "iframe";
function ref(u) {
	if (defaultAdType == "script") {
		refScript(u);
	} else {
		refIframe(u);
	}
}

function refIframe(u) {
	if (null != document.getElementById(u)) {
		var oldsrc=document.getElementById(u).src;
		if (null == ord) {
			ord = pcw_GetOrd(8);
		}
		var newsrc = oldsrc.replace(/ord=[0-9]+\?/,'ord='+ord);
		document.getElementById(u).src = newsrc;
	}
}

function refScript(u) {

	var dw = document.write;
	document.write = function(pStr) {
		adBuffer += pStr;
	};

	var e = document.getElementById(u);
	if (null != e) {

		if (null == ord) {
			ord = pcw_GetOrd(8);
		}
		
		if (e.nodeName == 'iframe') {

			//refresh iframe a
			var oldsrc=e.src;
			var newsrc = oldsrc.replace(/ord=[0-9]+\?/,'ord='+ord);
			e.src = newsrc;

		} else if (e.className == 'scriptAdSrc') {
			
			adBuffer = '';

			//overwrite doc.write
			

			var parent = e.parentNode;
			while (parent.firstChild) {
				parent.removeChild(parent.firstChild);
			}
			
			//now refresh script ad?
			var oldsrc=adSrc[u];
			var newsrc = oldsrc.replace(/ord=[0-9]+\?/,'ord='+ord);
			//alert("u:"+u+" oldsrc"+oldsrc+" newsrc:"+newsrc);

			//now add new script
			var s = document.createElement("script");
			s.setAttribute("type", "text/javascript");
			s.setAttribute("class","scriptAdSrc");
			s.setAttribute("id", u);
			s.setAttribute("src", newsrc);
			parent.appendChild(s);
			
			///and write to div
			parent.innerHTML = adBuffer;

		}

	}

	//reset document.write
	document.write = dw;

}

function buildScriptAd (id,src) {
	return s;
}

var adSrc = new Object();
$(document).ready(function(){
	$(".scriptAdSrc").each(function (i) {
		adSrc[$(this).attr("id")] = $(this).attr("src");
	});
});

/*--------------------------------------------------------
 *  Microsoft Paid Search ads
 *
 *  the renderMsPaidSearchAds functions require the existence of a variable set in js code
 * 		msTextAds ={mainLine: new Array(),sideBar:new Array()}
 * 	the function takes as a parameter a flag to switch from
 * 	mainline to sidebar ads.
 * 	
 * 		 
 *-------------------------------------------------------*/

function renderMsPaidSearchAd(adObject){
	var out = "<li>";
	out += "<a href='"+adObject.adURL+"'  target='_blank' rel='nofollow' onclick='msTextAdExit()'>"+adObject.title+"</a>";
	out += adObject.description;
	out += "<a href='"+adObject.adURL+"'  target='_blank' rel='nofollow' onclick='msTextAdExit()' class='siteLink'>"+adObject.displayURL+"</a>";
	out += "</li>";
	return out;
}

function renderMsPaidSearchAds(isMainLine,start,end,customClass){
	var list = isMainLine ? msTextAds.mainLine : msTextAds.sidebar;
	var myCustomClass = "";
	if (null != customClass) {
		myCustomClass = " "+customClass;
	}
	if(start< list.length){
		var out="<div class=\""+myCustomClass+" textAds\"><strong>Sponsored Links</strong><ul>";
		for(var i=start; i<list.length && i<=end;i++){
			out+=renderMsPaidSearchAd(list[i]);
		}
		out+="</ul></div>";
		document.write(out);
	}
}

function msTextAdExit(sTrackingLocation, sExitUrl){
	var s = s_gi('pcwmw-pcworld');
	s.linkTrackVars="prop35,eVar35,events"; 
	s.linkTrackEvents="event6";
	s.prop35="text ad exit"; 
	s.eVar35="text ad exit"; 
	s.events="event6";
	s = copyPropsToEvars(s);
	s.tl(this,'o','Text Ad Exit');
	return true;
}




function getQsVal (name) {
	if (window.location.search != "") {
		var qs = window.location.search.substring(1);
		var pairs = qs.split("&");
		for (var i=0;i<pairs.length;i++) {
			var pair = pairs[i].split("=");
			if (pair[0] == name) {
				return pair[1];
				break;
			}
		}
	}
	return "";
}

/*--------------------------------------------------------
 *	Base Classes
 *------------------------------------------------------*/


/*--------------------------------------------------------
 *  BEGIN FeatureViewer class
 *-------------------------------------------------------*/
 
//var FeatureViewer = Class.create();

//var FeatureViewer = new Object();

function FeatureViewer() {
	this.initialize();
}

FeatureViewer.prototype = {

	initialize: function() {

		//get and cache dom references
		this.containerNode = document.getElementById("FVContainer");
		this.contentNode = document.getElementById("FVContent");
		this.navNode = document.getElementById("FVNav");
		//this.readBtn = document.getElementById("FVReadBtn");
		this.arrNavs = this.navNode.getElementsByTagName("a");
		
		//build shadow node
		this.shadowNode = this.contentNode.cloneNode(1);
		this.shadowNode.id = "FVContentShadow";
		this.containerNode.appendChild(this.shadowNode);

		this.arrContent = this.contentNode.childNodes;
		this.arrContentShadow = this.shadowNode.childNodes;

		//set input arrays
		this.arrImages = new Array();
		this.arrLinks = new Array();
		
		//cache these values so we don't have to calculate later
		this.contentLength = 0;
		this.selectedItem = 0;

		//set timer durations		
		this.rotateDuration = 4000;
		this.idleDuration = 5000;
		this.navCloseDuration = 10000;
		
	},
	
	start: function() {

		this.contentLength = this.arrLinks.length;

		//preload images
		for (var i=0;i<this.contentLength;i++) {
			this.preload(this.arrImages[i]);
		}
		
		this.swapItem(1);
		this.startIdle();

	},
	
	swapItem: function(item) {
	
	 	if (!item) {
	 		//this is an automated swap
	 		if (this.selectedItem == this.contentLength) {
	 			item = 1;
	 		} else {
	 			item = this.selectedItem+1;
	 		}
	 	} else {
	 		this.stopAllTimers();
	 	}
	 	
	 	this.selectedItem = item;
	 	var index = item-1;

	 	
	 	this.renderContent(index);
	 	this.highlightNav(index);
	 	
	},
	
	stopAllTimers: function() {
		this.stopDelayedNavClose();
		this.stopIdle();
		this.stopRotation();
	},
	
	startIdle: function() {
		this.stopDelayedNavClose();
		this.stopIdle();
		this.stopRotation();
		this.idleInterval = setInterval("fv.startRotation()",this.idleDuration);
	},
	
	stopIdle: function() {
		clearInterval(this.idleInterval);
	},
	
	startRotation: function() {
		this.stopIdle();
		this.startDelayedNavClose();
		this.rotateInterval = setInterval("fv.swapItem()",this.rotateDuration);
	},
	
	stopRotation: function() {
		clearInterval(this.rotateInterval);
	},
	
	startDelayedNavClose: function () {
		this.stopDelayedNavClose();
		this.navCloseInterval = setInterval("fv.closeNav()",this.navCloseDuration);
	},
	
	stopDelayedNavClose: function() {
		clearInterval(this.navCloseInterval);
	},

	openNav: function() {
		fv.navNode.style.width = "188px";

		//clear existing event if it exists
		if ( fv.navNode.detachEvent ) {
			fv.navNode.detachEvent( "onmouseover", fv.openNav );
		} else {
			fv.navNode.removeEventListener( "mouseover", fv.openNav, false );
		}

		//set an event to close nav onmouseout
		if ( fv.navNode.attachEvent ) {
			fv.navNode.attachEvent( "onmouseout", fv.closeNav );
		} else {
			fv.navNode.addEventListener( "mouseout", fv.closeNav, false );
		}

		//addEvent(fooNode,"mouseout",fv.closeNav);
		//fv.startDelayedNavClose();
	},

	closeNav: function() {

		fv.stopDelayedNavClose();
		fv.navNode.style.width = "33px";

		//clear existing event
		if ( fv.navNode.detachEvent ) {
			fv.navNode.detachEvent( "onmouseout", fv.closeNav );
		} else {
			fv.navNode.removeEventListener( "mouseout", fv.closeNav, false );
		}

		//set an event to open nav onmouseover
		if ( fv.navNode.attachEvent ) {
			fv.navNode.attachEvent( "onmouseover", fv.openNav );
		} else {
			fv.navNode.addEventListener( "mouseover", fv.openNav, false );
		}

		//this.resizeNav(160,18,10);
	},
	
	resizeNav: function(start,end,interval) {
		this.navNode.style.width = start + "px";
		var newStart = start - 20;
		if (newStart > end) {
			setTimeout("fv.resizeNav("+newStart+","+end+")",interval);
		}
	},
	
	contentLink: function() {
		var href=fv.arrLinks[fv.selectedItem-1].replace(/&amp;/gi,"&");
		document.location.href = href;
	},

	renderContent: function(index) {
		
		//clear content
	 	for (var i=0;i<this.contentLength;i++) {
	 		this.arrContent[i].style.display = "none";
	 		this.arrContentShadow[i].style.display = "none";
	 	}

		//show content
	 	this.arrContent[index].style.display = "block";
	 	this.arrContentShadow[index].style.display = "block";
	 	this.containerNode.style.backgroundImage = "url("+this.arrImages[index]+")";

	 	//to be safe, remove old link event
		if ( fv.contentNode.detachEvent ) {
			fv.contentNode.detachEvent( "onclick", fv.contentLink );
		} else {
			fv.contentNode.removeEventListener( "click", fv.contentLink, false );
		}

	 	//add content hotspot
		if ( fv.contentNode.attachEvent ) {
			fv.contentNode.attachEvent( "onclick", fv.contentLink );
		} else {
			fv.contentNode.addEventListener( "click", fv.contentLink, false );
		}

	 	//this.readBtn.setAttribute("href",this.arrLinks[index]);

	},
	
	highlightNav: function(index) {
	 	for (var i=0;i<this.arrContent.length;i++) {
	 		this.arrNavs[i].className = "";
	 	}
	 	this.arrNavs[index].className = "FVNavOn";
	},
	
	preload: function(url) {
		var img = new Image;
		img.src = url;
		//this.arrImages.push(url);
	}

}

/*--------------------------------------------------------
 *  END FeatureViewer class
 *-------------------------------------------------------*/

function downgradeWin1252Chars(sIn){
	var arrCharMap = {
		128:"&#8364;", /* euro */
		133:"...", /* ellipsis */
		145:"'",
		146:"'",
		147:'"',
		148:'"',
		150:"-",
		151:"-"
	}
	
	var sOut = sIn;

	for(var i = 0; i< sOut.length; i++){
		if(sOut.charCodeAt(i) > 127) {
			var charCode = sOut.charCodeAt(i);
			var replacement = arrCharMap[charCode] ? arrCharMap[charCode] : "&#"+charCode+";";
			sOut = sOut.substr(0,i) + replacement + sOut.substr(i+1);
			i = i + replacement.length;
		}
	}

	//dbg:alert(sIn+'\n\n'+sOut);
	return sOut;
}
 
/*--------------------------------------------------------
 *  Class:  ArticleComments
 *  
 *  Description:
 *	Fetches/Posts/Displays comments in cached article pages
 *
 *  Properties
 *  - threadId (required): thread id associated with this article
 *  - communityId (required): community id associated with comments
 * 
 *  Methods:
 *	- getComments: Gets current comments
 *	- postComment: posts a user comment to server
 *	- injectComments: takes ajax result from either of above, inserts
 *		comment data into page
 *  
 *-------------------------------------------------------*/

var ArticleComments = new Object();
ArticleComments.threadId = null;
ArticleComments.communityId = null;
ArticleComments.isLoggedOn = 0;
ArticleComments.style = "default";

/* call with threadId */
ArticleComments.getComments = function() {
	if (null==ArticleComments.threadId) {
		return false;
	}
	if (null != Logon.isValid) {
		ArticleComments.isLoggedOn = 1;
	}
	$.get("/articleComment/get.chtml",
		{
			threadId:		ArticleComments.threadId,
			style:			ArticleComments.style
		}
	);
}

ArticleComments.postComment = function() {
	//alert("article commenting has been disabled during alpha testing");
	//return false;
	
	if (null==ArticleComments.threadId) {
		return false;
	}
	if (null != Logon.isValid) {
		ArticleComments.isLoggedOn = 1;
	}
	var commentNode = document.getElementById('forum_comment');

	var commentText = commentNode.value;
	if ('' == commentText) {
		alert('Please enter a comment');
		commentNode.focus();
		return;
	}
	try {
		if (FB) {
			if (FB.Connect.get_loggedInUser()) {
				var attachment = { 
						'name': document.title ,
						'href': window.location.href,
						'caption': '{*actor*} commented on this post'
						};
				var action_links = [{'text':'View Post', 'href': window.location.href }];
				FB.Connect.streamPublish(commentText, attachment, action_links);
			}
	    }
	} catch (e) {/*Swallow error, Facebook Connect problem*/};
	/* Clear post text-area */
	commentNode.value = '';
	document.getElementById('postingMessage').style.display = 'block';
	$.post("/articleComment/post.do",
		{
			threadId:		ArticleComments.threadId,
			communityId:	ArticleComments.communityId,
			style:			ArticleComments.style,
			postSubject:	downgradeWin1252Chars(ArticleComments.subject),
			postComment:	downgradeWin1252Chars(commentText)
		}
	);
}

/*--------------------------------------------------------
 *  article Voting
 *-------------------------------------------------------*/

var ArticleVote = new Object();
ArticleVote.aid = null;
ArticleVote.catid = null;
ArticleVote.dspid = null;
ArticleVote.message = null;
ArticleVote.myVote = null;
ArticleVote.style = "default";

ArticleVote.getVotes = function() {
	//Check for existing vote
	var cookie = pcw_readCookie('articleVotes');
	var strAid = String(ArticleVote.aid);
	var idx = cookie.indexOf(strAid);

	if (idx > -1) {
		var end = cookie.indexOf('\n',idx);
		if(end == -1) end = cookie.length;
		ArticleVote.myVote = cookie.substring(idx+strAid.length+1,end);
	}
	
	var params = new Object();
	params.aid = ArticleVote.aid;
	params.style = ArticleVote.style;
	if (ArticleVote.myVote) {
		params.hasVoted = true;
		params.vote = ArticleVote.myVote;
	}
	
	if (ArticleVote.catid) {
		params.catid = ArticleVote.catid;
	}
	
	if (ArticleVote.dspid) {
		params.dspid = ArticleVote.dspid;
	}
	
	if (ArticleVote.message) {
		params.message = ArticleVote.message;
	}
	
	//get votes
	$.get("/articleVote/get.chtml", params);
}

ArticleVote.submitVote = function(vote, bNoReload) {

	//handle vote
	if (vote == "no") {
		ArticleVote.myVote = "no";
	} else {
		ArticleVote.myVote = "yes";
	}
	
	//set voted cookie
	var d = new Date();
	d.setHours(d.getHours()+24);
	var cookie = pcw_readCookie('articleVotes');
	cookie = cookie + '\n'+ArticleVote.aid+'\t'+ArticleVote.myVote;
	pcw_setCookie('articleVotes', cookie, d, 'pcworld.com');

	var params = new Object();
	params.aid = ArticleVote.aid;
	if(ArticleVote.dspid){
		params.dspid = ArticleVote.dspid;
	}
	params.style = ArticleVote.style;
	params.vote = ArticleVote.myVote;
	params.nocache = Math.round((Math.random() * 90000) + 1);
	
	//submit vote
	if(ArticleVote.disable == undefined){
		if(bNoReload)
			$.post("/articleVote/post.do",params);
		else
			$.post("/articleVote/post.do",params,reload);
	}
	//disable subsequent clicks
	ArticleVote.disable = true;
}

/*
Reload page on 
*/
function reload () {
	window.location.reload(true);
}

/*--------------------------------------------------------
 *  download Voting
 *-------------------------------------------------------*/

var DownloadVote = new Object();
DownloadVote.fid = null;
DownloadVote.myVote = null;

DownloadVote.getVotes = function() {

	//Check for existing vote
	var cookie = pcw_readCookie('DownloadVotes');
	var strFid = String(DownloadVote.fid);
	var idx = cookie.indexOf(strFid);
	if (idx > -1) {
		var end = cookie.indexOf('\n',idx);
		if(end == -1) end = cookie.length;
		DownloadVote.myVote = cookie.substring(idx+strFid.length+1,end);
	}
	
	var params = new Object();
	params.fid = DownloadVote.fid;
	if (DownloadVote.myVote) {
		params.hasVoted = true;
		params.vote = DownloadVote.myVote;
	}
	
	//get votes
	$.get("/DownloadVote/get.chtml",params);
}

DownloadVote.submitVote = function(vote) {
	if(DownloadVote.myVote != undefined){
		return;
	}
	//handle vote
	if (vote == "no") {
		DownloadVote.myVote = "no";
	} else {
		DownloadVote.myVote = "yes";
	}
	
	//set voted cookie
	var d = new Date();
	d.setHours(d.getHours()+24);
	var cookie = pcw_readCookie('DownloadVotes');
	cookie = cookie + '\n'+DownloadVote.fid+'\t'+DownloadVote.myVote;
	pcw_setCookie('DownloadVotes', cookie, d, 'pcworld.com');

	var params = new Object();
	params.fid = DownloadVote.fid;
	params.vote = DownloadVote.myVote;
	params.nocache = Math.round((Math.random() * 90000) + 1);

	//submit vote
	$.post("/DownloadVote/post",params);

}


/*--------------------------------------------------------
 *	Function:  tabSwap
 *	
 *	Description:
 *	changes visible tab in a standard tabswap module
 *
 *	Parameters:
 *	tabsetId	string		Reference to tabswap module id
 *	node		object		selected node
 *
 *	Return:
 *	none
 *------------------------------------------------------*/

function tabSwap(tabsetId,node) {

	var selectedNode = node.parentNode;
	
	if (selectedNode.className.indexOf("Selected") > -1) {
		return false;
	}

	var contentContainer = getChildrenByClassName(tabsetId,"tabContentGroup")[0];
	var tabContainer = getChildrenByClassName(tabsetId,"tabs")[0];

	//swap tabs
	var tabNodes = tabContainer.childNodes;
	var tabCounter = 0;
	var index = 0;
	for (var i=0; i<tabNodes.length; i++) {
		if (tabNodes[i].nodeType == 1) {
			var className = tabNodes[i].className;
			if (tabNodes[i] == selectedNode) {
				tabNodes[i].className = className+"Selected";
				index = tabCounter;
			} else {
				tabNodes[i].className = className.replace("Selected", "");
			}
			tabCounter++;
		}
	}
		
	//swap content	
	var contentNodes = contentContainer.childNodes;
	var contentCounter = 0;
	for (var i=0; i<contentNodes.length; i++) {
		if (contentNodes[i].nodeType == 1) {
			if (contentCounter == index) {
				contentNodes[i].style.display = "block";
			} else {
				contentNodes[i].style.display = "none";
			}
			contentCounter++;
		}
	}
	
}

/*--------------------------------------------------------
 *  Function:  timestamp
 *  
 *  Description:
 *  Accepts a date and style, writes localized timestamp
 *  	in specified style to the reader's browser
 *  
 *  Parameters:
 *  date	string	unixtime timestamp
 *  style	string	predefined style of output
 *							
 *  Return:
 *  none		
 *-------------------------------------------------------*/
 
function timestamp(pDate,style) {

	var date = new Date(pDate);
	var now = new Date();

	var year = date.getFullYear();
	var month = date.getMonth()+1;
	var monthday = date.getDate();
	var weekday = date.getDay();
	var hour = date.getHours();
	var minute = pad(date.getMinutes());

	var strShortMonth = "";	
	var strLongMonth = "";
	switch (month) {
		case 1:
			strShortMonth = "Jan";
			strLongMonth = "January";
			break;
		case 2:
			strShortMonth = "Feb";
			strLongMonth = "February";
			break;
		case 3:
			strShortMonth = "Mar";
			strLongMonth = "March";
			break;
		case 4:
			strShortMonth = "Apr";
			strLongMonth = "April";
			break;
		case 5:
			strShortMonth = "May";
			strLongMonth = "May";
			break;
		case 6:
			strShortMonth = "Jun";
			strLongMonth = "June";
			break;
		case 7:
			strShortMonth = "Jul";
			strLongMonth = "July";
			break;
		case 8:
			strShortMonth = "Aug";
			strLongMonth = "August";
			break;
		case 9:
			strShortMonth = "Sep";
			strLongMonth = "September";
			break;
		case 10:
			strShortMonth = "Oct";
			strLongMonth = "October";
			break;
		case 11:
			strShortMonth = "Nov";
			strLongMonth = "November";
			break;
		case 12:
			strShortMonth = "Dec";
			strLongMonth = "December";
			break;
	}
	
	var strWeekday = "";
	switch (weekday) {
		case 0:
			strWeekday = "Sunday";break;
		case 1:
			strWeekday = "Monday";break;
		case 2:
			strWeekday = "Tuesday";break;
		case 3:
			strWeekday = "Wednesday";break;
		case 4:
			strWeekday = "Thursday";break;
		case 5:
			strWeekday = "Friday";break;
		case 6:
			strWeekday = "Saturday";break;
	}
	
	var meridian = "am";
	if (hour >= 12) {
		hour = hour - 12;
		meridian = "pm";
	}
	if (hour == 0) {
		hour = 12;
	}
	
	var time = hour+":"+minute+" "+meridian;
	var shortDate = strShortMonth + " " + monthday;
	var longDate = shortDate + ", " + year;
	
	var timestamp = "";
	switch (style) {
		case "longDate" :
			document.write(longDate);
			break;
		case "shortDate" :
			document.write(shortDate);
			break;
		case "time" :
			document.write(time);
			break;
		case "longDateTime" :
			document.write(longDate + " " + time);
			break;
		case "shortDateTime" :
			document.write(shortDate + ", " + time);
			break;
		case "homepage" :
			if (date.getDate() == now.getDate()) {
				document.write(time);
			} else {
				document.write(shortDate);
			}
			break;
		case "dateTime" :
			if (date.getYear() == now.getYear()) {
				document.write(shortDate + ", " + time);
			} else {
				document.write(longDate + " " + time);
			}
			break;
		default :
			//document.write(time_ago_in_words(pDate));
			if (date.getYear() == now.getYear()) {
				document.write(shortDate);
			} else {
				document.write(longDate);
			}
	}
	
}

function pad (pNum) {
	if (pNum < 10) {
		return "0" + pNum;
	} else {
		return pNum;
	}
}

function time_ago_in_words(from) {
	return distance_of_time_in_words(new Date().getTime(), from) 
}

function distance_of_time_in_words(to, from) {
	seconds_ago = ((to  - from) / 1000);
	minutes_ago = Math.floor(seconds_ago / 60)

	if(minutes_ago == 0) { return "less than a minute";}
	if(minutes_ago == 1) { return "a minute ago";}
	if(minutes_ago < 45) { return minutes_ago + " minutes ago";}
	if(minutes_ago < 90) { return " about 1 hour ago";}
	hours_ago  = Math.round(minutes_ago / 60);
	if(minutes_ago < 1440) { return "about " + hours_ago + " hours ago";}
	if(minutes_ago < 2880) { return "1 day ago";}
	days_ago  = Math.round(minutes_ago / 1440);
	if(minutes_ago < 43200) { return days_ago + " days ago";}
	if(minutes_ago < 86400) { return "about 1 month ago";}
	months_ago  = Math.round(minutes_ago / 43200);
	if(minutes_ago < 525960) { return months_ago + " months ago";}
	if(minutes_ago < 1051920) { return "about 1 year ago";}
	years_ago  = Math.round(minutes_ago / 525960);
	return "over " + years_ago + " years ago" 
}
 

/*--------------------------------------------------------
 *  getPricingUrl
 *
 *  dynamically generates pricing urls to shopping
 * 		prodid - pricegrabber masterid
 *-------------------------------------------------------*/

function getPricingUrl(prodid,sortby,filename) {
	if (filename == null) {
		filename = "pricing.html";
	}
	var zip = pcw_readCookie("pcw.shopping.zip");
	var url = "/shopping/detail/prtprdid,"+prodid;
	if (sortby != null && sortby != "") {
		if (zip != "" && sortby == "price") {
			sortby = "blprice";
		}
		url += "-sortby," + sortby;
	}
	if (zip != "") {
		url += "-zip," + zip;
	}
	url += "/" + filename;
	window.location.href = url;
}

function getPricingExitUrl(prodid) {
	var zip = pcw_readCookie("pcw.shopping.zip");
	var url = "/shopping/exit/prtprdid,"+prodid;
	if (zip != "") {
		url += "-zip," + zip;
	}
	url += "/exit.html";
	window.location.href = url;
}

/*--------------------------------------------------------
 *  ShoppingUrl onClick Handler
 *
 *  Appends zip code info to pricing urls (a.pricingRewrite)
 * 	after a click event.	
 *-------------------------------------------------------*/
var pricingZipRewriteRegEx= /shopping\/(detail|exit)\/([^\/]+)\/(.*)/;

	$("a.pricingRewrite").click(function () {
    	var zip = pcw_readCookie("pcw.shopping.zip");
    	if(zip != undefined && zip.length >0){
    		var href=$(this).attr("href");
    		var matchInfo=href.match(pricingZipRewriteRegEx);
    		if(matchInfo != undefined && href.indexOf("zip")==-1){
    			var newHref="/shopping/"+matchInfo[1]+"/"+matchInfo[2]+"-zip,"+zip+"/"+matchInfo[3];
    			window.location.href=newHref;
    			return false;
    		}
    	}
    	return true;
  });
  


/*--------------------------------------------------------
 *  Inform helper
 *-------------------------------------------------------*/

function doInformUrl(path) {
	var p = path;
	p = p.replace(/^\/tags\//,"");
	p = p.replace(/\.html$/,"");
	p = p.replace(/\./g,"%2E");
	window.location.href = "/tags/" + p + ".html";
}

/*--------------------------------------------------------
 *  SMB Product Finder Nav
 *-------------------------------------------------------*/

var filterSelected = false;
function toggleFilter (index,nodeId) {
	if (filterSelected) {
		showAllFilters(nodeId);
	} else {
		showSelectedFilter(index,nodeId);
	}
	return false;
}

function showSelectedFilter (index,nodeId) {
	var kids = document.getElementById(nodeId).childNodes;
	var counter = 0;
	for (var i=0;i<kids.length;i++) {
		var kid = kids[i];
		if (kid.nodeType == 1 && kid.className != "itemMain") {
			if (index == counter) {
				kid.className = "itemSelected clearfix";
				//add "show all" link
				var selectedItemHeader = kid.getElementsByTagName("h3")[0];
				selectedItemHeader.appendChild(createToggleFilterLink(index,nodeId));
			} else {
				kid.style.display = "none";
			}
			counter++;
		}
	}
	filterSelected = true;
}

function showAllFilters (nodeId) {
	var kids = document.getElementById(nodeId).childNodes;
	for (var i=0;i<kids.length;i++) {
		var kid = kids[i];
		if (kid.nodeType == 1 && kid.className != "itemMain") {
			kid.className = "item";
			kid.style.display = "inline";
		}
	}
	
	//remove extra added link
	var showAllLink = document.getElementById("pfItemSelectedToggleLink");
	if (showAllLink != null) {
		showAllLink.parentNode.removeChild(showAllLink);
	}
	filterSelected = false;
}

function createToggleFilterLink (index,nodeId) {
	var link = document.createElement("a");
	link.setAttribute("id","pfItemSelectedToggleLink");
	link.setAttribute("class","viewAll");
	link.setAttribute("href","javascript:void(0)");
	link.setAttribute("onclick","toggleFilter("+index+",'"+nodeId+"');return false;");
	link.innerHTML = "(View All Filters)";
	return link;
}

function validatePrices(frm){
	re = /^\d*$/;
	if(re.exec(frm.lo_p.value) && re.exec(frm.hi_p.value)){
		return true;
	}
	alert("Please enter whole dollar amounts without commas or decimals.");
	return false;
}

function clearPriceRange(){
	document.priceForm.lo_p.value = "";
	document.priceForm.hi_p.value = "";
	document.priceForm.submit();
}


function swaptab(tabsetId,node) {

	var selectedNode = node.parentNode;

	var contentContainer = getChildObjectsByClassName(tabsetId,"tabContentGroup")[0];
	var tabContainer = getChildObjectsByClassName(tabsetId,"tabs")[0];

	//swap tabs
	var tabNodes = tabContainer.childNodes;
	var tabCounter = 0;
	var index = 0;
	for (var i=0; i<tabNodes.length; i++) {
		if (tabNodes[i].nodeType == 1) {
			if (tabNodes[i] == selectedNode) {
				tabNodes[i].childNodes[0].className = "selected";
				index = tabCounter;
			} else {
				tabNodes[i].childNodes[0].className = "";
				//tabNodes[i].blur();
			}
			tabCounter++;
		}
	}
		
	//swap content	
	var contentNodes = contentContainer.childNodes;
	var contentCounter = 0;
	for (var i=0; i<contentNodes.length; i++) {
		if (contentNodes[i].nodeType == 1) {
			if (contentCounter == index) {
				contentNodes[i].style.display = "block";
			} else {
				contentNodes[i].style.display = "none";
			}
			contentCounter++;
		}
	}
	
}


function doFilterPrice(price,url) {
	window.location.href=url+'&prod_price='+price;
}

function randomlyShowOneItem(lst){
	var items = lst.getElementsByTagName("li");
	var iSize = items.length;
	var iRandom = Math.floor(iSize * Math.random());
	items[iRandom].style.display = 'inline';
}

$.fn.replaceEscapedHtml = function(newContentElements) {
    return this.each(function() {
        $(this).empty().append(newContentElements);
        this.innerHTML = this.innerHTML.replace(/&amp;/g,"&");
    });
};

/*--------------------------------------------------------
 *  Class:  MVTest
 *  
 *  Description:
 *	Simple multivariate test
 *
 *-------------------------------------------------------*/

function MVTest() {
	this.init();
}

MVTest.prototype = {

	init: function() {
		this.testWinner = null;
		this.session = null;
		this.useCookie = false;
		this.hasCookie = false;
	},
	
	isValid: function() {
		if (null == this.testIds || this.testIds.length == 0) {
			return false;
		}
		if (null != this.session) {
			this.useCookie = true;
		}
		return true;
	},
	
	decide: function() {

		//first validate that we have test criteria
		if (!this.isValid()) {
			return false;
		}

		//then test for session cookie
		if (this.useCookie) {
			var cookieVal = pcw_readCookie(this.session);
			for (var i=0; i<this.testIds.length; i++) {
				if (cookieVal == this.testIds[i]) {
					this.testWinner = cookieVal;
					this.hasCookie = true;
				}
			}
		}

		//if no cookie is returned, execute fresh test
		if (null == this.testWinner) {
			var rand = Math.floor(Math.random()*this.testIds.length);
			this.testWinner = this.testIds[rand];
		}

		//now set session cookie
		if (this.useCookie && !this.hasCookie) {
			pcw_setCookie(this.session, this.testWinner, null, '.pcworld.com');
		}
		
	},

	display: function() {
	
		//now display winner
		var displayNode = document.getElementById(this.testWinner); 
		if (null != displayNode) {
			displayNode.style.display = 'block';
		}
		
	}

}

function merchantExit(sTrackingLocation, sExitUrl){
	var s = s_gi('pcwmw-pcworld');
	s.linkTrackVars="prop26,eVar26,events,eVar39,prop39"; 
	s.linkTrackEvents="event4";
	s.prop26="merchant exit"; 
	s.eVar26="merchant exit";
	s.prop39=sTrackingLocation; 
	s.eVar39=sTrackingLocation; 
	s.events="event4";
	s = copyPropsToEvars(s);
	s.tl(this,'o','Merchant Exit');
	window.open(sExitUrl);
}

function iTunesExit(sTrackingLocation, sExitUrl){
	var s = s_gi('pcwmw-pcwappg');
	s.linkTrackVars="prop36,eVar36,events"; 
	s.linkTrackEvents="event5";
	s.prop36="iTunes exit"; 
	s.eVar36="iTunes exit"; 
	s.events="event5";
	s = copyPropsToEvars(s);
	s.tl(this,'o','iTunes Exits');
	window.open(sExitUrl);
}

function itunesExit(){
	var s = s_gi('pcwmw-pcwappg'); //change to 'pcwmw-macworld' on mw appguide
	s.linkTrackVars="prop36,eVar36,events"; 
	s.linkTrackEvents="event5";
	s.prop36="itunes exit"; 
	s.eVar36="itunes exit"; 
	s.events="event5";
	s = copyPropsToEvars(s);
	s.tl(this,'o','iTunes Exit');
}



function logUserReview(){
	var s = s_gi('pcwmw-pcworld'); //change to 'pcwmw-macworld' on mw 
	s.linkTrackVars="events"; 
	s.linkTrackEvents="event3";
	s.events="event3";
	s = copyPropsToEvars(s);
	s.tl(this,'o','User Reviews');
}

function logAppUserReview(){
	var s = s_gi('pcwmw-pcwappg'); //change to 'pcwmw-macworld' on mw 
	s.linkTrackVars="events"; 
	s.linkTrackEvents="event3";
	s.events="event3";
	s = copyPropsToEvars(s);
	s.tl(this,'o','User Reviews');
}

function logArticleComment(){
	var s = s_gi('pcwmw-pcworld'); //change to 'pcwmw-macworld' on mw 
	s.linkTrackVars="events"; 
	s.linkTrackEvents="event8";
	s.events="event8";
	s = copyPropsToEvars(s);
	s.tl(this,'o','Article Comments');
}

function logUserRegistration(sTrackingLocation){
	var s = s_gi('pcwmw-pcworld'); //change to 'pcwmw-macworld' on mw 
	s.linkTrackVars="events"; 
	s.linkTrackEvents="event7";
	s.events="event7";
	s = copyPropsToEvars(s);
	s.tl(this,'o','Registrations');
}

function downloadExit(sTrackingLocation, sExitUrl){
	var s = s_gi('pcwmw-pcworld');
	s.linkTrackVars="prop26,eVar26,events,eVar39,prop39"; 
	s.linkTrackEvents="event5";
	s.prop26="download buy exit"; 
	s.eVar26="download buy exit";
	s.prop39=sTrackingLocation; 
	s.eVar39=sTrackingLocation; 
	s.events="event5";
	s = copyPropsToEvars(s);
	s.tl(this,'o','Downloads Buy Exits');
	window.open(sExitUrl);
}

function pcwabbreviate(sIn, iMaxChars){
	var sOut = sIn;
	if(sIn && sIn.length > iMaxChars){
		sOut = sIn.substr(0, iMaxChars-3)+'...';
	}
	
	return sOut;
}

/*********************************
* 	Mobile OptIn
*********************************/

$(document).ready(function(){
	var optOutCookie = pcw_readCookie('pcw.mobileOptOut');
	if (optOutCookie!=undefined && optOutCookie=='optout'){
		$("<div id='mobileOptIn'><a href='javascript:void(0)' onClick='mobileOptIn()'>Click here to return to the mobile version of PCWorld</a>.</span>").insertBefore("#header");
	}
	
});
function mobileOptIn(){
		var d = new Date();
		d.setFullYear(d.getFullYear()-20);
		pcw_writeCookie("pcw.mobileOptOut","",d,".pcworld.com");
		location.href=location.href;
}

/*
 * new rf stuff
*/

function getFilters(catId) {
	if (catId.length == 0) {
		return null;
	}

	//get votes
	$.get("/reviews/products/browse/form.do", {"catId":catId}, function (data, textStatus){
		$("#finderOptions").html(data).show();
	});
}

var filterIsValid = true;
function validateNumeric(node){
	if (isNaN($(node).val())) {
		filterIsValid = false;
		$(node).addClass("invalid");
		$("#invalidMsg").show();
	} else {
		filterIsValid = true;
		$(node).removeClass("invalid");
		$("#invalidMsg").hide();
	}
}

function rfValidate() {
	var form = $("#reviewFinderForm").get(0);
	if (isNaN(form.p.value)) {
		form.p.value = '';
	}
	/*
	var inputs = $("#reviewFinderForm :input");
	for (var i=0; i<inputs.length; i++) {
		alert($(inputs[i]).val());
	}
	*/
	if (filterIsValid) {
		$("#reviewFinderForm").submit();
	}

}

function rfReset() {
	document.getElementById('reviewFinderForm').reset();
}

//Format number w/ commas
function addCommas(nStr) {
    nStr += '';
    x = nStr.split('.');
    x1 = x[0];
    x2 = x.length > 1 ? '.' + x[1] : '';
    var rgx = /(\d+)(\d{3})/;
    while (rgx.test(x1)) {
        x1 = x1.replace(rgx, '$1' + ',' + '$2');
    }
    return x1 + x2;
}

function tweetMemeButton(container,url) {
	if ($(container)) {
		var iframe = "";
		iframe += "<iframe src=\"http://api.tweetmeme.com/button.js?url=" + escape(url) + "&style=compact&source=pcworld\" scrolling=\"no\" frameborder=\"0\" width=\"90\" height=\"20\" ></iframe>";
		$(container).html(iframe);
	}
}
/********************
 * 
 * Nielsen Tracking for Ajax
 */
function Nielsen_Event() {
	var d = new Image(1, 1);
	d.src = ["//secure-us.imrworldwide.com/cgi-bin/m?ci=us-203426h&cg=0&cc=1&si=",escape(window.location.href), "&rp=", escape(document.referrer),"&ts=usergen&rnd=", (new Date()).getTime()].join('');
};



//global override for forcing ajax to load
forceLoad = false;


/*******************
 * from fb.connect.js - merging here for performance
 * 
 */

$(document).ready(function() {
	$("a[href='/logoff']").click(function() {
        fbConnectLogOut();
	});
	setUpFBConnectView();
	updateFromLogonCookie();
});

function getFBConnectedState() {
	loggedIn = false;
	FB.ensureInit(function() {
            FB.Connect.get_status().waitUntilReady( function( status ) {
                    switch ( status ) {
                            case FB.ConnectState.connected: loggedIn = true;
                            break;
                            case FB.ConnectState.appNotAuthorized:
                                    case FB.ConnectState.userNotLoggedIn: loggedIn = false;
                    }
            });
    });
	return loggedIn;
}

function redirectFBConnectedUnAuthd() {
    var fbGauntlet = false;
    var fbloggedIn = false;
    var fbGauntletCount = 0;
    var fromLogout = false;
    var limit = 3;
    var fbGauntletCookie =  unescape(pcw_readCookie('fbGauntlet'));
    var fromLogout = (unescape(pcw_readCookie('from.logout')) === "true");
    if (fbGauntletCookie.length > 0 && (typeof(fbGauntletCookie) != "undefined") && fbGauntletCookie != "") {
        fbGauntletObj = eval('(' + unescape(fbGauntletCookie) + ')');
    }
    if (typeof(fbGauntletObj) != "undefined" && typeof(fbGauntletObj) === "object") {
        fbGauntlet = (fbGauntletObj.fbconnected == "true");
        fbGauntletCount = fbGauntletObj.count;
    }
    fbloggedIn = getFBConnectedState();
    //FB Logout if just came from logout and still connected
    if (fromLogout && fbloggedIn) {
    	fbConnectLogOut();
    }
    //Do redirect to /logonFbConnectAction if FBConnected but have not gone through the fbGauntlet
    var clientReferer = (window.location.pathname.search("/logon") == -1)?window.location.pathname:window.document.referrer;
    if ($("form#logonForm > [name='refererClient']").val())
    	clientReferer = $("form#logonForm > [name='refererClient']").val();
    if (!(fbGauntlet) && fbloggedIn && fbGauntletCount < limit && !(fromLogout)) {
        window.location="/logonFBConnectAction" + "?clientReferer=" + clientReferer;
    }
}

function resetFBConnectedState() {
	fbRemoveCookie("from.logout", ".pcworld.com");
	setUnLoggedFBGauntletCookie();
}

function updateFromLogonCookie() {
	if (window.location.pathname.search("/logon") == -1 )
		fbRemoveCookie("from.logon", ".pcworld.com");
}

function fbConnectLogOut() {
	fbRemoveCookie("fbGauntlet", ".pcworld.com");
	setSecondBasedCookie("from.logout","true",10,"/",".pcworld.com");
	try {
		FB.Connect.logout( function() {return (false);} );
	    
		var session = FB.Facebook.apiClient.get_session();
		var user = session ? session.uid : null;
		var singleton = FB.Connect._singleton;
		var nextUrl = FBIntern.Uri.addQueryParameters(
				FB.XdComm.Server.singleton.get_receiverUrl(),
				'fb_login&fname=_parent&session=loggedout');
		singleton._ensureLoginHandler();
		singleton._logoutCallback = "..."; // <-- wherever you want to go after logout
		logoutUrl = FBIntern.Utility.getFacebookUrl('www');
		logoutUrl += 'logout.php?app_key=' + FB.Facebook.apiKey;
		logoutUrl += '&session_key=' + encodeURIComponent(session.session_key)
				+ '&next=' + encodeURIComponent(nextUrl);
		FB.Facebook.apiClient.set_session(null);
		singleton.set__userInfo(null);
		singleton._logoutIframe = FB.XdComm.Server.singleton
				.createNamedHiddenIFrame('fbLogout', logoutUrl, 'fb_logout',
						null);
	} catch (e) {/*Swallow error, Facebook Connect problem*/};
	return (false);
}

function fbRemoveCookie(name, domain){
    if(navigator.cookieEnabled){
        var d = new Date();
        d.setDate(d.getDate()-1000);
        document.cookie=name+"=;expires="+d.toGMTString()+";domain="+domain+";path=/";
    }
}

function setSecondBasedCookie(name,value,expires,path,domain,secure) {
    var d = new Date();
    d.setTime(d.getTime());
    if (name && value && expires) {
        //in seconds
        expires = expires * 1000;
        var expires_date = new Date( d.getTime() + (expires) ); 
        document.cookie = name + "=" +escape(value) +
        ((expires)?";expires=" + expires_date.toGMTString():"") +
        ((path)?";path=" + path:"") +
        ((domain)?";domain=" + domain:"") +
        ((secure)?";secure":""); 
    }
}

function setUnLoggedFBGauntletCookie() {
	var value = "{\"fbconnected\":\"false\",\"count\":0,\"fbname\":\"\"}";
	var d = new Date();
	d.setHours(d.getHours()+24);
	pcw_setCookie("fbGauntlet", value, d,".pcworld.com")
}

function setUpFBConnectView() {
	var userName = null;
	var loggedInAs = null;
    var fbGauntletCookie =  unescape(pcw_readCookie('fbGauntlet'));
	if (fbGauntletCookie.length > 0 && (typeof(fbGauntletCookie) != "undefined") && fbGauntletCookie != "") {
        fbGauntletObj = eval('(' + unescape(fbGauntletCookie) + ')');
    }
    if (typeof(fbGauntletObj) != "undefined") {
    	userName = fbGauntletObj.fbname.replace("+"," ");
    }
    if (userName === null || userName == "" || userName.length == 0 ) {
    	try {
			FB.ensureInit(function() {
				if (getFBConnectedState()) {
					var uid = FB.Facebook.apiClient.get_session().uid;
					var sql = "SELECT name FROM user WHERE uid ="+uid;
				    FB.Facebook.apiClient.fql_query(sql, function(result, ex) {
				        userName= result[0]['name'];
					});
				}
			});
    	} catch (e) {/*Swallow error, Facebook Connect problem*/};
    }
    if (userName != null  && userName != "" && userName.length != 0 ) {
    	//Change header to FBC name
    	$(".hdUserName").html(pcwabbreviate(userName, 9));
    	//Change name in comment box to FBC name
    	loggedInAs = $(".loggedinas");
    	if (loggedInAs != null) {
        	if (loggedInAs != null) {
        		jQuery.each(loggedInAs, function(){
        			$(this).html(userName);
        		});
        	}
    	}
    }
	//Mask email from my account if @facebook
	try {
		if ((document.forms['UpdateProfileForm'].email.value.search("@facebook.com") != -1) || 
				(document.forms['UpdateProfileForm'].email.value.search("@facebookconnectuser.com") != -1)) {
			document.forms['UpdateProfileForm'].email.value = "";
		}
	} catch (e) {/* Swallow undefined element errors */};
}

/*******************
 * from flashobject.js - to be deprecated
 */

/**
 * FlashObject v1.2.3: Flash detection and embed - http://blog.deconcept.com/flashobject/
 * Modified by PCWorld
 *
 * FlashObject is (c) 2005 Geoff Stearns and is released under the MIT License:
 * http://www.opensource.org/licenses/mit-license.php
 *
 */
if(typeof com == "undefined") var com = new Object();
if(typeof com.deconcept == "undefined") com.deconcept = new Object();
if(typeof com.deconcept.util == "undefined") com.deconcept.util = new Object();
if(typeof com.deconcept.FlashObjectUtil == "undefined") com.deconcept.FlashObjectUtil = new Object();
com.deconcept.FlashObject = function(swf, id, w, h, ver, c, useExpressInstall, quality, redirectUrl, detectKey){
   this.DETECT_KEY = detectKey ? detectKey : 'detectflash';
   this.skipDetect = com.deconcept.util.getRequestParameter(this.DETECT_KEY);
   this.params = new Object();
   this.variables = new Object();
   this.attributes = new Array();

   if(swf) this.setAttribute('swf', swf);
   if(id) this.setAttribute('id', id);
   if(w) this.setAttribute('width', w);
   if(h) this.setAttribute('height', h);
   if(ver) this.setAttribute('version', new com.deconcept.PlayerVersion(ver.toString().split(".")));
   if(c) this.addParam('bgcolor', c);
   var q = quality ? quality : 'high';
   this.addParam('quality', q);
   this.setAttribute('redirectUrl', '');
   if(redirectUrl) this.setAttribute('redirectUrl', redirectUrl);
   if(useExpressInstall) {
   // check to see if we need to do an express install
   var expressInstallReqVer = new com.deconcept.PlayerVersion([6,0,65]);
   var installedVer = com.deconcept.FlashObjectUtil.getPlayerVersion();
      if (installedVer.versionIsValid(expressInstallReqVer) && !installedVer.versionIsValid(this.getAttribute('version'))) {
         this.setAttribute('doExpressInstall', true);
      }
   } else {
      this.setAttribute('doExpressInstall', false);
   }
}
com.deconcept.FlashObject.prototype.setAttribute = function(name, value){
	this.attributes[name] = value;
}
com.deconcept.FlashObject.prototype.getAttribute = function(name){
	return this.attributes[name];
}
com.deconcept.FlashObject.prototype.getAttributes = function(){
	return this.attributes;
}
com.deconcept.FlashObject.prototype.addParam = function(name, value){
	this.params[name] = value;
}
com.deconcept.FlashObject.prototype.getParams = function(){
	return this.params;
}
com.deconcept.FlashObject.prototype.getParam = function(name){
	return this.params[name];
}
com.deconcept.FlashObject.prototype.addVariable = function(name, value){
	this.variables[name] = value;
}
com.deconcept.FlashObject.prototype.getVariable = function(name){
	return this.variables[name];
}
com.deconcept.FlashObject.prototype.getVariables = function(){
	return this.variables;
}
com.deconcept.FlashObject.prototype.getParamTags = function(){
   var paramTags = ""; var key; var params = this.getParams();
   for(key in params) {
        paramTags += '<param name="' + key + '" value="' + params[key] + '" />';
    }
   return paramTags;
}
com.deconcept.FlashObject.prototype.getVariablePairs = function(){
	var variablePairs = new Array();
	var key;
	var variables = this.getVariables();
	for(key in variables){
		variablePairs.push(key +"="+ variables[key]);
	}
	return variablePairs;
}
com.deconcept.FlashObject.prototype.getHTML = function() {
    var flashHTML = "";
    if (navigator.plugins && navigator.mimeTypes && navigator.mimeTypes.length) { // netscape plugin architecture
        if (this.getAttribute("doExpressInstall")) { this.addVariable("MMplayerType", "PlugIn"); }
        flashHTML += '<embed type="application/x-shockwave-flash" wmode="transparent" src="'+ this.getAttribute('swf') +'" width="'+ this.getAttribute('width') +'" height="'+ this.getAttribute('height') +'" id="'+ this.getAttribute('id') + '" name="'+ this.getAttribute('id') +'"';
		var params = this.getParams();
        for(var key in params){ flashHTML += ' '+ key +'="'+ params[key] +'"'; }
		pairs = this.getVariablePairs().join("&");
        if (pairs.length > 0){ flashHTML += ' flashvars="'+ pairs +'"'; }
        flashHTML += '></embed>';
    } else { // PC IE
        if (this.getAttribute("doExpressInstall")) { this.addVariable("MMplayerType", "ActiveX"); }
        flashHTML += '<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" width="'+ this.getAttribute('width') +'" height="'+ this.getAttribute('height') +'" id="'+ this.getAttribute('id') +'">';
        flashHTML += '<param name="movie" value="' + this.getAttribute('swf') + '" />';
        flashHTML += '<param name="wmode" value="transparent" />';
		var tags = this.getParamTags();
        if(tags.length > 0){ flashHTML += tags; }
		var pairs = this.getVariablePairs().join("&");
        if(pairs.length > 0){ flashHTML += '<param name="flashvars" value="'+ pairs +'" />'; }
        flashHTML += '</object>';
    }
    flashHTML += '<img src="http://ad.doubleclick.net/ad/pcw_video/tracking;sz=1x1" border="0" />';
    return flashHTML;
}
com.deconcept.FlashObject.prototype.write = function(elementId){
	if(this.skipDetect || this.getAttribute('doExpressInstall') || com.deconcept.FlashObjectUtil.getPlayerVersion().versionIsValid(this.getAttribute('version'))){
		var targetElement = null;
		if(document.getElementById){
			targetElement = document.getElementById(elementId);
		}
		if (null != targetElement) {
		   if (this.getAttribute('doExpressInstall')) {
		      this.addVariable("MMredirectURL", escape(window.location));
		      document.title = document.title.slice(0, 47) + " - Flash Player Installation";
		      this.addVariable("MMdoctitle", document.title);
		   }
		   targetElement.innerHTML = this.getHTML();
		} else {
		   document.write('<div style="text-align:center;margin:16px 0 10px 0">'+this.getHTML()+'</div>');
		}
	}else{
		if(this.getAttribute('redirectUrl') != "") {
			document.location.replace(this.getAttribute('redirectUrl'));
		}
	}
}
/* ---- detection functions ---- */
com.deconcept.FlashObjectUtil.getPlayerVersion = function(){
   var PlayerVersion = new com.deconcept.PlayerVersion(0,0,0);
	if(navigator.plugins && navigator.mimeTypes.length){
		var x = navigator.plugins["Shockwave Flash"];
		if(x && x.description) {
			PlayerVersion = new com.deconcept.PlayerVersion(x.description.replace(/([a-z]|[A-Z]|\s)+/, "").replace(/(\s+r|\s+b[0-9]+)/, ".").split("."));
		}
	}else if (window.ActiveXObject){
	   try {
   	   var axo = new ActiveXObject("ShockwaveFlash.ShockwaveFlash");
   		PlayerVersion = new com.deconcept.PlayerVersion(axo.GetVariable("$version").split(" ")[1].split(","));
	   } catch (e) {}
	}
	return PlayerVersion;
}
com.deconcept.PlayerVersion = function(arrVersion){
	this.major = parseInt(arrVersion[0]) || 0;
	this.minor = parseInt(arrVersion[1]) || 0;
	this.rev = parseInt(arrVersion[2]) || 0;
}
com.deconcept.PlayerVersion.prototype.versionIsValid = function(fv){
	if(this.major < fv.major) return false;
	if(this.major > fv.major) return true;
	if(this.minor < fv.minor) return false;
	if(this.minor > fv.minor) return true;
	if(this.rev < fv.rev) return false;
	return true;
}
/* ---- get value of query string param ---- */
com.deconcept.util.getRequestParameter = function(param){
	var q = document.location.search || document.location.href.hash;
	if(q){
		var startIndex = q.indexOf(param +"=");
		var endIndex = (q.indexOf("&", startIndex) > -1) ? q.indexOf("&", startIndex) : q.length;
		if (q.length > 1 && startIndex > -1) {
			return q.substring(q.indexOf("=", startIndex)+1, endIndex);
		}
	}
	return "";
}

/* add Array.push if needed (ie5) */
if (Array.prototype.push == null) { Array.prototype.push = function(item) { this[this.length] = item; return this.length; }}

/* add some aliases for ease of use / backwards compatibility */
var getQueryParamValue = com.deconcept.util.getRequestParameter;
var FlashObject = com.deconcept.FlashObject;

/* fix for video streaming bug */
com.deconcept.FlashObjectUtil.cleanupSWFs = function() {
  var objects = document.getElementsByTagName("OBJECT");
  for (var i=0; i < objects.length; i++) {
    for (var x in objects[i]) {
      if (typeof objects[i][x] == 'function') {
      	try {
	        objects[i][x] = null;
	    } catch(e) {
	    	/* Ignore exception */
	    }
      }
    }
  }
}
var oldunload=undefined;
if (typeof window.onunload == 'function') {
  if(oldunload==undefined){
	var oldunload = window.onunload;
  }
  window.onunload = function() {
    com.deconcept.FlashObjectUtil.cleanupSWFs();
    oldunload();
  }
} else {
  window.onunload = com.deconcept.FlashObjectUtil.cleanupSWFs;
}

