/*********************************************************
 *	File:		main.js
 *	Author:		MetaDesign
 *	Created:	December 6, 2005
 *
 *	Description:
 *	Javascript file for all functions and classes provided
 *	in the following order:
 *	
 *	Utility Functions
 *  -----------------
 *	getObject
 *	filterByClassName
 *	getObjectsByClassName
 *	getChildrenByClassName
 *	getDescendantsByClassName
 *	compareById
 *	createElement
 *	getWindowWidth
 *	getWindowHeight
 *	getScrollX
 *	getScrollY
 *	tmpAlert
 *	clearFld
 *	changeclass
 *	
 *	Utility Exceptions
 *	------------------
 *	ObjectNotFoundException
 *	InvalidObjectRefException
 *	
 *	Application Specific Functions
 *	------------------------------
 *	addToMyPages
 *	addToMyPagesToolbar
 *	showImage
 *	toggleCommentsDisplay
 *	checkAllChecks
 *	charCounter
 *	selProdCat
 *	chgToolbar
 *
 *	FeatureViewer
 *	-------------
 *  Constants:
 *	Feature Viewer
 *  FEATURE_CLASS
 *  FEATURE_NAMES
 *
 *	Browser Detection:
 *  userAgent
 *  isPCIE
 *
 *	getFeatureCount
 *	getFeatureContent
 *	getFeatures
 *	encodeSpecial
 *	getFlashMovieObject
 *	setFlashVariables
 *
 *	Base Classes
 *	------------
 *	DOMDecorator
 *	OpacityControl
 *	Timer
 *	TimeObserver
 *	Move
 *
 *	Generic UI Control Classes
 *	--------------------------
 *	Drawer
 *	WindowShade
 *	AlertMessage
 *	ListControl
 *
 *	Module Specific Classes
 *	-----------------------
 *	ReviewFinder
 *	MyPages
 *
 ********************************************************/

/*--------------------------------------------------------
 *	Utility Functions
 *------------------------------------------------------*/

/*--------------------------------------------------------
 *  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:  tmpAlert
 *	
 *	Description:
 *	Temporary function for alert of server side functionality
 *
 *	Paramaters:
 *	pType		string		Verbiage for alert box
 *
 *	Return:
 *	none
 *------------------------------------------------------*/
 
function tmpAlert(pType) {
	alert('This will perform a ' + pType + '.');
	return false;
}

/*--------------------------------------------------------
 *	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
 *------------------------------------------------------*/
 
 var MYPAGES_ACTIVE = 'myPagesUnSaved';
 var MYPAGES_DISABLED = 'myPagesSaved';
 var MYPAGES_TOOLBAR_ACTIVE = 'toolbarMyPagesUnsaved';
 var MYPAGES_TOOLBAR_DISABLED = 'toolbarMyPagesSaved';
 
/*--------------------------------------------------------
 *  Function:  addToMyPages
 *  
 *  Description:
 *  Utility function for adding to My Pages.
 *  
 *  Parameters:
 *	pObj		string or object	Reference to My Pages icon
 *	pId			string	Unique ID of page to add
 *  pLabel		string	The label to display in the My Pages
 *						panel
 *	pURL		string	The URL of the page to add
 *  
 *  Return:
 *  none
 *-------------------------------------------------------*/

function addToMyPages(pObj, pId, pLabel, pURL) {
	var obj = getObject(pObj);
	
	// If the page is added successfully, switch to the saved (disabled)
	// version of the My Pages icon
	if (MyPages.prototype.addPage(pId, pLabel, pURL, true)) {
		changeClass(obj, MYPAGES_ACTIVE, MYPAGES_DISABLED);
	}
}

/*--------------------------------------------------------
 *  Function:  addToMyPagesToolbar
 *  
 *  Description:
 *  Toolbar version of utility function for adding to My Pages.
 *  
 *  Parameters:
 *	pObj		string or object	Reference to My Pages icon
 *	pId			string	Unique ID of page to add
 *  pLabel		string	The label to display in the My Pages
 *						panel
 *	pURL		string	The URL of the page to add
 *  
 *  Return:
 *  none
 *-------------------------------------------------------*/
	
function addToMyPagesToolbar(pObj, pId, pLabel, pURL) {
	var obj = getObject(pObj);
	
	// If the page is added successfully, switch to the saved (disabled)
	// version of the My Pages icon
	if (MyPages.prototype.addPage(pId, pLabel, pURL, true)) {
		var objs = document.getElementsByName("toolbarAddToMyPagesLink");
		for(var i = 0; i < objs.length; i++){
			changeClass(objs[i], MYPAGES_TOOLBAR_ACTIVE, MYPAGES_TOOLBAR_DISABLED);
		}
	}
}

/*--------------------------------------------------------
 *  Function:  showImage
 *  
 *  Description:
 *  Changes the src and alt for an image object.
 *  
 *  Parameters:
 *	pImg		string or object	Reference to the image object.
 *	pSrc		string	The new src for the image.
 *	pAlt		string	The new alt for the image.
 *  
 *  Return:
 *  none
 *-------------------------------------------------------*/

function showImage(pImg, pSrc, pAlt) {
	var imageObj = getObject(pImg);
	imageObj.src = pSrc;
	imageObj.alt = pAlt;
}

/*--------------------------------------------------------
 *	Function:  toggleCommentsDisplay
 *	
 *	Description:
 *	Changes the display of ancillary article elements
 *	(comments and post a comment)
 *
 *	Paramaters:
 *	pArea		string or object	Reference to area being toggled
 *
 *	Return:
 *	none
 *------------------------------------------------------*/
 
function toggleCommentsDisplay(pArea) {
	// Check current display
	var display = getObject(pArea).style.display;
	
	// Not showing.  Display it.
	if (display == 'none') {
		getObject(pArea).style.display = 'block';
		
		// Toggle controls
		getObject(pArea + 'LinkOn').style.display = 'none';
		getObject(pArea + 'LinkOff').style.display = 'inline';
		if ('post' == pArea) {
			getObject("forum_comment").focus();
		}

	// Showing.  Hide it.
	} else {
		getObject(pArea).style.display = 'none';
		
		// Toggle controls
		getObject(pArea + 'LinkOff').style.display = 'none';
		getObject(pArea + 'LinkOn').style.display = 'inline';
	}
}

//same as above but bizcenter html is a little diff
function toggleCommentsDisplayBC(pArea) {
	// Check current display
	var display = getObject(pArea).style.display;
	
	// Not showing.  Display it.
	if (display == 'none') {
		getObject(pArea).style.display = 'block';
		
		//if user not signed in textarea doesn't exist
		try{
			getObject("forum_comment").focus();
		}catch(e){
			//noop
		}

	// Showing.  Hide it.
	} else {
		getObject(pArea).style.display = 'none';
		
	}
}


/*--------------------------------------------------------
 *  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);
}

/*--------------------------------------------------------
 *  Function:  selProdCat
 *  
 *  Description:
 *  Step 1 of Review Finder pane process.
 *  Selects product category from dropdown, displays
 *	conditional dropdowns based on choice. This is only 
 *	a simulation.
 *  
 *  Parameters:
 *  none
 *  
 *  Return:
 *  none
 *-------------------------------------------------------*/

function selProdCat(bHide) {
	// reveal conditional dropdowns and buttons
	// these items would likely be determined with an AJAX
	// call to the database.  Potentially, the values
	// of the dropdowns would also be determined via AJAX.
	arrReveal = new Array('blockStep2', 'btnGetReviews');
	for (var i = 0; i < arrReveal.length; i++) {
		getObject(arrReveal[i]).style.display = 'block';
	}
	
	// Reveal standard dropdowns.
	// These get displayed regardless of choice.
	arrRFElement = getObjectsByClassName('reviewFinderElement');
	for (var i = 0; i < arrRFElement.length; i++) {
		getObject(arrRFElement[i]).style.display = 'block';
	}
	
	if(bHide){
		// move the drawer closed to hide whole form
		ReviewFinder.prototype.close();
	}else{
		// move the drawer open to reveal whole form
		ReviewFinder.prototype.open();
	}
}

//review finder functions
function prodCatSelChange(typid, bHide, searchCriteria){
	g_selectedProdCat = typid;
	var url = '/product/rffilters.html' + (searchCriteria ? searchCriteria : '?id='+typid);
	var myAjax = new Ajax.Request(
	    url,
	    {method: 'GET', parameters: null, onComplete: (bHide ? hiddenProdCatResponse : prodCatResponse)}
	);
}
function prodCatResponse(originalRequest) {
	$('reviewFinder').innerHTML = originalRequest.responseText;
	selProdCat();
	$('id').value = g_selectedProdCat;
}

function prodArtCatSelChange(catid, bHide){
	g_selectedProdCat = catid;
	var myAjax = new Ajax.Request(
	    '/product/rffilters.html?catid='+catid,
	    {method: 'GET', parameters: null, onComplete: (bHide ? hiddenProdCatResponse : prodCatResponse)}
	);
}
function hiddenProdCatResponse(originalRequest) {
	$('reviewFinder').innerHTML = originalRequest.responseText;
	selProdCat(true);
	$('id').value = g_selectedProdCat;
}

function rfCatSelect(typid) {
	var myAjax = new Ajax.Request(
	    '/product/rffilters.html?id='+typid+'&style=smb',
	    {method: 'GET', parameters: null, onComplete: rfCatDisplay}
	);
}
function rfCatDisplay(req) {
	var displayNode = $("rfFields");
	displayNode.innerHTML = req.responseText;
	displayNode.style.display = "block";
}
function rfCatClose() {
	$("rfFields").style.display = "none";
}

function displaySelectedSelect(selectList,id) {
	for (var i=0; i< selectList.options.length; i++) {
		if (selectList.options[i].value == id) {
			selectList.selectedIndex = i;
			break;
		}
	}
	//var selectList = document
	//var selectedModelId = select.options[select.selectedIndex].value;
}

/*--------------------------------------------------------
 *	Function:  chgToolbar
 *	
 *	Description:
 *	Changes the display of the toolbar.
 *	Calls getObjectsByClassName.
 *
 *	Paramaters:
 *	pState		Integer		Which of 4 toolbar states to set
 *
 *	Toolbar states are as follows:
 *	1: default
 *	2: enter find.pcworld.com code
 *	3: sign in
 *	4: signed in
 *	
 *	Return:
 *	none
 *------------------------------------------------------*/
 
function chgToolbar(pState) {
	// turn display off for conditional elements
	var els = getObjectsByClassName('conditional');
	for (i = 0; i < els.length; i++) {
		els[i].style.display = 'none';
	}

	// selective turn on display of elements according to state
	switch (pState) {
		case '2':
			$('hdCodeOn').style.display='block';
			$('hdSignin1').className='hdToolbarCodeonFill conditional';
			$('hdSignin1').style.display='block';
			$('hdRegister').style.display='block';
			break;
		case '3':
			$('hdCodeOff').style.display='block';
			$('hdSignin1').className='hdToolbarCodeoffFill conditional';
			$('hdSignin1').style.display='block';
			$('hdRegister').style.display='block';
			break;
		case '4':
			$('hdCodeOff').style.display='block';
			$('hdSignin1').className='hdToolbarCodeoffFill conditional';
			$('hdSignin1').style.display='block';
			$('hdSignout').style.display='block';
			break;
		case '1':
		default:
			$('hdCodeOff').style.display='block';
			$('hdSignin1').className='hdToolbarCodeoffFill conditional';
			$('hdSignin1').style.display='block';
			$('hdRegister').style.display='block';
			break;
	}
	return false;
}

/*--------------------------------------------------------
 *	Feature Viewer
 *------------------------------------------------------*/

var hasFeature = false;
 
function showTimers() {
	var strOut = "";
	var lastTime = aFlashCounters[0].split(":")[0];

	for (var i=0;i<aFlashCounters.length;i++) {
		var func = aFlashCounters[i].split(":")[0];
		var time = aFlashCounters[i].split(":")[1];
		var delta = time - lastTime;

		strOut = strOut + func + ":" + delta + "\n";

		var lastTime = time;
	}
	
	var first = aFlashCounters[0].split(":")[1];
	var last = aFlashCounters[aFlashCounters.length-1].split(":")[1];
	var allDelta = last - first;

	alert(strOut);
}
 
 
/* CONSTANTS */
var FEATURE_CLASS = 'feature';	 // Class name for features
var FEATURE_VIEWER_MOVIE = 'sendmovie';	// Name of the feature viewer Flash movie
var IE_VAR = 'ieQuery';	// Name of the variable to use when sending info to Flash via IE
var aFlashCounters = new Array();

// Map of content names to feature structure
var FEATURE_NAMES = {
	// name				// class name			// attribute
	'head'			: ['featureHead',			'text'],
	'deck'			: ['featureDeck',			'text'],
	'short summary'	: ['featureSummary',		'text'],
	'link'			: ['featureLink',			'href'],
	'image path'	: ['featureImg',			'src'],
	'button path'	: ['featureBtn',			'src'],
	'rollover path'	: ['featureBtnRollover',	'src']
};

/* BROWSER DETECTION */
var userAgent = navigator.userAgent.toLowerCase(); 
var isPCIE = (
	(userAgent.indexOf('msie') != -1)
	&& (userAgent.indexOf('win') != -1)
	&& (userAgent.indexOf('opera') == -1)
	&& (userAgent.indexOf('webtv') == -1)
	);
var hasFlash8 = true;
if (navigator.platform == "Linux") {hasFlash8 = false;}

/*--------------------------------------------------------
 *  Function:  getFeatureCount
 *  
 *  Description:
 *  Returns the number of features to display in the feature
 *  viewer.
 *  
 *  Parameters:
 *  none
 *  
 *  Return:
 *  integer		The number of features to display
 *-------------------------------------------------------*/

function getFeatureCount() {
	//aFlashCounters.push("getFeatureCount():" + new Date().valueOf());
	return getChildObjectsByClassName("FeatureViewerContent",FEATURE_CLASS).length;
}

/*--------------------------------------------------------
 *  Function:  getFeatureContent
 *  
 *  Description:
 *  Returns a string representing the content of a feature
 *  requested by name.
 *  
 *  Parameters:
 *  pOrder		The order number of the feature to retrieve
 *				content for (starting at 0).  Features are
 *				ordered by ID.
 *  pName		The name of the content to retrieve
 *  
 *  Return:
 *  string		The feature content requested
 *-------------------------------------------------------*/

function getFeatureContent(pOrder, pName) {
	//aFlashCounters.push("getFeatureContent():" + new Date().valueOf());
	var features = getChildObjectsByClassName("FeatureViewerContent",FEATURE_CLASS);
	
	// Sort the features by id
	features.sort(compareById);

	// Retrieve the feature we want
	var feature = features[pOrder];
	
	if (feature) {
		if (pName == 'order') {
			return pOrder;
		} else if (FEATURE_NAMES[pName]) {
			// Find the element by class name.  There should only be one, so grab the first.
			var element = getDescendantsByClassName(feature, FEATURE_NAMES[pName][0])[0];
			if (element) {
				if (FEATURE_NAMES[pName][1] == 'text') {
					return element.innerHTML;
				} else {
					return element.getAttribute(FEATURE_NAMES[pName][1]);
				}
			}
		}
		// Return empty string if the field requested is wrong or does not exist.
		return '';
	}

	// The feature at pOrder does not exist.  Return null.
	return null;
}

/*--------------------------------------------------------
 *  Function:  getFeatures
 *  
 *  Description:
 *  Returns a string in url delimited format representing
 *	the content of all features.
 *
 *  Each content item is provided in the following format:
 *  <#>_<name>=<content>
 *
 *  where
 *    # is the order number of the feature (starting at 0)
 *    name is the name of the content (e.g. head, deck, etc)
 *    content is the value of the content
 *  
 *  Parameters:
 *  none
 *  
 *  Return:
 *  string		All feature content in url delimited format
 *-------------------------------------------------------*/

function getFeatures() {
	//aFlashCounters.push("getFeatures():" + new Date().valueOf());
	var featureCount = getFeatureCount();
	var returnStr = '';
	var delim = '';
	
	for (var i = 0; i < featureCount; i++) {
		for (var name in FEATURE_NAMES) {
			returnStr += delim + i + '_' + name + '=' + encodeSpecial(getFeatureContent(i, name));
			delim = '&';
		}
	}

	setFlashVariables(FEATURE_VIEWER_MOVIE,	returnStr);
}

/*--------------------------------------------------------
 *  Function:  encodeSpecial
 *  
 *  Description:
 *	Encodes characters that might obscure a string in URL
 *	delimited format (i.e. name1=value1&name2=value2).  Uses
 *  a proprietary encoding format to allow for further
 *	encoding with escape().
 *
 *  Parameters:
 *  pStr	string		The string to encode
 *  
 *  Return:
 *  string		The encoded string
 *-------------------------------------------------------*/
 
function encodeSpecial(pStr) {
	//aFlashCounters.push("encodeSpecial():" + new Date().valueOf());
	var returnStr = pStr;
	returnStr = returnStr.split('&').join('zzzampzzz');
	returnStr = returnStr.split('=').join('zzzequzzz');
	returnStr = returnStr.split('%').join('zzzperzzz');
	
	return returnStr;
}

/*--------------------------------------------------------
 *  Function:  getFlashMovieObject
 *  
 *  Description:
 *	Retrieves a handle to the Flash movie identified by
 *	pMovieId.
 *
 *  Parameters:
 *  pMovieId	string	The ID of the Flash movie
 *  
 *  Return:
 *  object		Reference to the Flash movie
 *-------------------------------------------------------*/

function getFlashMovieObject(pMovieId) {
	//aFlashCounters.push("getFlashMovieObject():" + new Date().valueOf());
	if (window.document[pMovieId]) {
		return window.document[pMovieId];
	}
	
	if (navigator.appName.indexOf('Microsoft Internet') == -1) {
		if (document.embeds && document.embeds[pMovieId]) {
			return document.embeds[pMovieId];
		}
	}

	return getObject(pMovieId);
}

/*--------------------------------------------------------
 *  Function:  setFlashVariables
 *  
 *  Description:
 *	Sends information to a Flash movie in string format.
 *
 *	Information should be provided in url format:
 *		var1=foo&var2=bar
 *
 *  Parameters:
 *  pMovieId	string	The ID of the Flash movie
 *	pFlashInfo	string	The information to send to the Flash
 *						movie.
 *  
 *  Return:
 *  none
 *-------------------------------------------------------*/

function setFlashVariables(pMovieId, pFlashInfo){
	//aFlashCounters.push("setFlashVariables():" + new Date().valueOf());
	
	if (isPCIE || hasFlash8 == true) {
		var movie = getFlashMovieObject(pMovieId);
		movie.SetVariable(IE_VAR, escape(pFlashInfo));
	} else {
		// Otherwise, we need to use gateway.swf to handle our javascript to Flash communication.
	
		var divId = 'flash_setvariables_' + pMovieId;
		var divElement = document.getElementById(divId);
		
		// Embed gateway.swf if it's not already there.
		if (!divElement) {
			divElement = createElement('div', {});
			divElement.id = divId;
			document.body.appendChild(divElement);
		}
		
		divElement.innerHTML = '<embed src="/flash/feature_viewer/gateway.swf" FlashVars="lc=' + pMovieId + '&fq=' + escape(pFlashInfo) + '" width="0" height="0" type="application/x-shockwave-flash"></embed>';
	}
}

/*--------------------------------------------------------
 *  Function:  WriteFeatureViewer
 *  
 *  Description:
 *	Writes feature viewer object to page
 *
 *  Parameters:
 *  none
 *  
 *  Return:
 *  none
 *-------------------------------------------------------*/

function WriteFeatureViewer() {
	//aFlashCounters.push("WriteFeatureViewer():" + new Date().valueOf());
	if (hasFeature) {
		var strOut = "";

		strOut = strOut + '<object id="sendmovie" classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=6,0,79,0" width="576" height="190">';
		strOut = strOut + '<param name="FlashVars" value="movieid=sendmovie&live=1" />';
		strOut = strOut + '<param name="wmode" value="transparent">'
		if (hasFlash8) {
			strOut = strOut + '<param name="allowScriptAccess" value="sameDomain" />';
			strOut = strOut + '<param name="movie" value="/flash/feature_viewer/FeatureBoxV8.swf" />';
			strOut = strOut + '<embed name="sendmovie" src="/flash/feature_viewer/FeatureBoxV8.swf" FlashVars="movieid=sendmovie&live=1" wmode="transparent" width="576" height="190" type="application/x-shockwave-flash" pluginspace="http://www.macromedia.com/shockwave/download/index.cgi?P1_Prod_Version=ShockwaveFlash" allowscriptaccess="sameDomain"></embed>';
		} else {
			strOut = strOut + '<param name="movie" value="/flash/feature_viewer/FeatureBoxV7.swf" />';
			strOut = strOut + '<embed name="sendmovie" src="/flash/feature_viewer/FeatureBoxV7.swf" FlashVars="movieid=sendmovie&live=1" wmode="transparent" width="576" height="190" type="application/x-shockwave-flash" pluginspace="http://www.macromedia.com/shockwave/download/index.cgi?P1_Prod_Version=ShockwaveFlash" ></embed>';
		}
		strOut = strOut + '</object>';

		$("FeatureViewer").innerHTML = strOut;
	}
	return false;
}



/*--------------------------------------------------------
 *	Miscellaneous Functions
 *------------------------------------------------------*/

/* SEARCH */
function search_onsubmit(){
	preSearchSubmit();
	return true;
}

function submitSearch(frm){
	preSearchSubmit(frm);
	document.getElementById(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){
		document.cookie = name+"="+value+";expires="+expires.toGMTString()+";domain="+domain+";path=/";
	}
}

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=/";
	}
}

/* SAVED PAGES */

/*
	returns an array of saved pages read from cookie
*/
function getSavedPages(){

	var rv = new Array();
	cookie = pcw_readCookie("savedPages");
	if(cookie!=""){
		var pages = cookie.split("\n");
		for(var i = 0; i < pages.length; i++){
			var page = pages[i].split("\t");
			rv.push({id:page[0], title: page[1], url: page[2]});
		}
	}
	return rv;
}


function esc (str) {
	var out = str;
	if (null != out) {
		out = out.replace(/http:\/\//g,"");
		out = out.replace(/\//g,"%2f");
		out = escape(out);
	}
	return out;
}


function doNothing () {
	return false;
}


/* HITBOX */
function setHbxVals () {

	var hcDim = "|" + hbx.mlc + "/" + hbx.pn;

	var sg = new Object();
	sg.dlBrowsers = 1; //done
	sg.dlLoaders = 2;
	sg.newsUsers = 3; //done
	sg.reviewsUsers = 4; //done
	sg.blogUsers = 5; //done
	sg.idgnsUsers = 6; //done
	sg.members = 7; //done
	sg.forumViewers = 8; //done
	sg.forumPosters = 9;
	sg.ddUsers = 10; //done
	sg.bgUsers = 11; //done
	sg.pgUsers = 12; //done
	sg.rssUsers = 13; //done
	sg.icUsers = 14; //done
	sg.nlSubscribers = 15; //done
	sg.magSubscribers = 16;
	sg.pi = 17;
	sg.sem = 18; //done
	
	var sgs = new Array();
	
	var nv = true;
	var ref = document.referrer;
	if (ref.indexOf("pcworld") > -1) {nv = false;}
	
	if (hbx.mlc.indexOf("downloads") > -1 && nv == true) {sgs.push(sg.dlBrowsers);}
	if (hbx.mlc.indexOf("news") > -1 && nv == true) {sgs.push(sg.newsUsers);}
	if (hbx.mlc.indexOf("review") > -1 && nv == true) {sgs.push(sg.reviewsUsers);}
	if (hbx.mlc.indexOf("blog") > -1 && nv == true) {sgs.push(sg.blogUsers);}
	if (hbx.mlc.indexOf("idgns") > -1 && nv == true) {sgs.push(sg.idgnsUsers);}
	if (hbx.mlc.indexOf("duo") > -1 && nv == true) {sgs.push(sg.ddUsers);}
	if (hbx.mlc.indexOf("guide") > -1 && nv == true) {sgs.push(sg.bgUsers);}
	if (hbx.mlc.indexOf("/ic/") > -1 && nv == true) {sgs.push(sg.icUsers);}
	if (hbx.mlc.indexOf("forum") > -1 && nv == true) {sgs.push(sg.forumUsers);}
	if (hbx.mlc.indexOf("prices") > -1 && nv == true) {sgs.push(sg.pgUsers);}
	
	//members
	if (pcw_readCookie('userEmail') != '') {sgs.push(sg.members);}

	//tk vals
	var tk = getQsVal("tk");
	if (tk != "") {
		hbx.hc1 = tk+hcDim;
		if (tk.indexOf("nl_") > -1) {sgs.push(sg.nlSubscribers);}
		if (tk.indexOf("pcw_") > -1) {sgs.push(sg.sem);}
		if (tk.indexOf("rss_") > -1) {sgs.push(sg.rssUsers);}
	}
	
	//set appropriate hbx vals
	hbx.seg = sgs.join(",");
	
}

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
 *------------------------------------------------------*/

/*--------------------------------------------------------
 *  Class:  DOMDecorator
 *  
 *  Description:
 *	Decorator class to add functionality to DOM objects.
 *	Not a true decorator as the DOM object must be referenced
 *	as this.obj.
 *
 *	Adds two DOM traversing methods to the object:
 *	- getChildrenByClassName(pClassName)
 *	- getDescendantsByClassName(pClassName)
 *
 *	ObjectNotFoundException runtime exception is thrown if
 *	a proper DOM element is not provided to the constructor.
 *	InvalidObjectRefException is thrown if an invalid object
 *	reference is provided to the constructor.
 *
 *	Properties:
 *	- obj: Reference to DOM element
 *
 *  Methods:
 *	- intialize: Constructor. Sets reference to the DOM element.
 *	- getChildrenByClassName: Returns all children of the DOM
 *		element that match a particular class name.
 *	- getDescendantsByClassName: Returns all descendants of
 *		the DOM element that match a particular class name.
 *  
 *  Exceptions:
 *	- ObjectNotFoundException: Left uncaught if a valid DOM
 *		object is not provided to the constructor.
 *	- InvalidObjectRefException: Left uncaught if an invalid
 *		object reference is provided to the constructor.
 *-------------------------------------------------------*/

var DOMDecorator = Class.create();
DOMDecorator.prototype = {

	/*--------------------------------------------------------
	 *  Method:  initialize
	 *  
	 *  Description:
	 *  Constructor. Establishes reference to DOM element as
	 *	this.obj. Throws ObjectNotFoundException
	 *	if DOM element is not found in page.
	 *
	 *  Parameters:
	 *  pObj		string or object	Reference to DOM element
	 *  
	 *  Return:
	 *  none
	 *-------------------------------------------------------*/

	initialize: function(pObj) {
		this.obj = getObject(pObj);
	},
	
	/*--------------------------------------------------------
	 *  Method:  getChildrenByClassName
	 *  
	 *  Description:
	 *  Returns all children of the DOM element that match a
	 *	particular class name.  Uses global function 
	 *	getChildrenByClassName.
	 *
	 *  Parameters:
	 *  pClassName		string	Class name to match children to
	 *  
	 *  Return:
	 *  array	Array of all children that match the class name
	 *-------------------------------------------------------*/

	getChildrenByClassName: function(pClassName) {
		return getChildrenByClassName(this.obj, pClassName);
	},
	
	/*--------------------------------------------------------
	 *  Method:  getDescendantsByClassName
	 *  
	 *  Description:
	 *  Returns all descendants of the DOM element that match a
	 *	particular class name.  Uses global function 
	 *	getDescendantsByClassName.
	 *
	 *  Parameters:
	 *  pClassName		string	Class name to match descendants to
	 *  
	 *  Return:
	 *  array	Array of all descendants that match the class name
	 *-------------------------------------------------------*/

	getDescendantsByClassName: function(pClassName) {
		return getDescendantsByClassName(this.obj, pClassName);
	}
}

/*--------------------------------------------------------
 *  Class:  OpacityControl
 *  
 *  Description:
 *	Decorator class that adds a cross-browser method for
 *	controlling DOM element opacity.
 *
 *	Extends DOMDecorator class and uses its constructor for
 *	initialization.
 *
 *	ObjectNotFoundException runtime exception is thrown if
 *	a proper DOM element is not provided to the constructor.
 *	InvalidObjectRefException is thrown if an invalid object
 *	reference is provided to the constructor.
 *
 *	Properties:
 *	- obj: Reference to DOM element
 *
 *  Methods:
 *	- setOpacity: Sets the opacity of the DOM element
 *  
 *  Exceptions:
 *	- ObjectNotFoundException: Left uncaught if a valid DOM
 *		object is not provided to the constructor.
 *	- InvalidObjectRefException: Left uncaught if an invalid
 *		object reference is provided to the constructor.
 *-------------------------------------------------------*/

var OpacityControl = Class.create();
OpacityControl.prototype.extend(DOMDecorator.prototype).extend({

	/*--------------------------------------------------------
	 *  Method:  setOpacity
	 *  
	 *  Description:
	 *  Sets the opacity of the DOM element using a variety of
	 *	browser-specific techniques.
	 *
	 *  Parameters:
	 *  pOpacity	integer	The opacity to set (0 - 100)
	 *  
	 *  Return:
	 *  none
	 *-------------------------------------------------------*/

	setOpacity: function(pOpacity) {
		var objStyle = this.obj.style;
		objStyle.opacity = pOpacity/100;
		objStyle.filter = 'alpha(opacity:' + pOpacity + ')';
		objStyle.MozOpacity = pOpacity/100;
	}
});

/*--------------------------------------------------------
 *  Class:  Timer
 *  
 *  Description:
 *	Like a metronome, this class establishes a heartbeat
 *	for other classes to observe.  The Timer class allows
 *	multiple objects to act synchronously.
 *
 *	Classes that wish to observe the Timer class should
 *	extend the TimeObserver class.
 *
 *  This class uses the observer pattern for notifications.
 *	It also uses the singleton pattern to establish a single
 *	unique instance of the class.  Objects that wish to
 *	reference the Timer should use Timer.getInstance() instead
 *	of "new Timer()".
 *
 *	Properties:
 *	- observers: Array for tracking the objects observing the
 *		Timer.
 *	- interval:	The internal interval that establishes the
 *		Timer's heartbeat.
 *	- timer: The singleton instance of the Timer class.
 *
 *  Methods:
 *	- initialize: Constructor. Sets the internal interval and
 *		establishes the class' properties.
 *	- getInstance: Retrieves the singleton instance of the Timer.
 *	- stopTimer: Stops the Timer.
 *	- registerObserver: Registers an object as an observer of the
 *		Timer.
 *	- removeObserver: Removes an object from the observers array,
 *		thereby removing it as an observer of the Timer.
 *	- notifyObservers: Notifies all observers of the Timer of
 *		a heartbeat event.
 *-------------------------------------------------------*/

var Timer = Class.create();
Timer.prototype = {

	/*--------------------------------------------------------
	 *  Method:  initialize
	 *  
	 *  Description:
	 *  Constructor. Sets the internal interval in motion and 
	 *	establishes an empty observers array and timer instance.
	 *
	 *  Parameters:
	 *  none
	 *  
	 *  Return:
	 *  none
	 *-------------------------------------------------------*/

	initialize: function() {
		this.observers = new Array();	// Array to tally our observers
		this.interval = setInterval(this.notifyObservers.bind(this), 100);	// Start the timer
		this.timer = null;
	},
	
	/*--------------------------------------------------------
	 *  Method:  getInstance
	 *  
	 *  Description:
	 *	Returns a singleton instance of the Timer.  An instance
	 *	is created if it does not already exist.
	 *
	 *  Parameters:
	 *  none
	 *  
	 *  Return:
	 *  object	The singleton instance of the Timer class.
	 *-------------------------------------------------------*/

	getInstance: function() {
		if (!this.timer) {
			this.timer = new Timer();
		}
		
		return this.timer;
	},

	/*--------------------------------------------------------
	 *  Method:  stopTimer
	 *  
	 *  Description:
	 *  Stops the Timer by clearing its internal interval.
	 *
	 *  Parameters:
	 *  none
	 *  
	 *  Return:
	 *  none
	 *-------------------------------------------------------*/
	
	stopTimer: function() {
		clearInterval(this.interval);
		this.interval = null;
	},

	/*--------------------------------------------------------
	 *  Method:  registerObserver
	 *  
	 *  Description:
	 *  Registers an object as an observer of the Timer by
	 *	adding the object to the observers property.  The
	 *	object must have a unique ID to distinguish it from
	 *	the other observers.
	 *
	 *  Parameters:
	 *  pObserver	object	The observer to register
	 *  
	 *  Return:
	 *  none
	 *-------------------------------------------------------*/
	
	registerObserver: function(pObserver) {
		this.observers[pObserver.id] = pObserver;
	},

	/*--------------------------------------------------------
	 *  Method:  removeObserver
	 *  
	 *  Description:
	 *  Removes an object from the observers property, thereby
	 *	removing it as an observer of the Timer.  The object
	 *	must have a unique ID to distinguish it from the other
	 *	observers.
	 *
	 *  Parameters:
	 *  pObserver	object	The observer to remove
	 *  
	 *  Return:
	 *  none
	 *-------------------------------------------------------*/
	
	removeObserver: function(pObserver) {
		delete(this.observers[pObserver.id]);
	},

	/*--------------------------------------------------------
	 *  Method:  notifyObservers
	 *  
	 *  Description:
	 *  Notifies all observers of a heartbeat event.  Called by
	 *	the Timer's internal interval.  Notification is established
	 *	by calling the observer's update method.  Observers should
	 *	extend the TimeObserver class to ensure that this method is
	 *	available.
	 *
	 *  Parameters:
	 *  none
	 *  
	 *  Return:
	 *  none
	 *-------------------------------------------------------*/
	
	notifyObservers: function() {
		for (var id in this.observers) {
			if (typeof this.observers[id] == "object") {
				this.observers[id].update();
			}
		}
	}
}
/*--------------------------------------------------------
 *  END Timer class
 *-------------------------------------------------------*/

/*--------------------------------------------------------
 *  Class:  TimeObserver
 *  
 *  Description:
 *	Observer class for the Timer class.  All objects that wish
 *	to observe the Timer class should extend this class.
 *
 *	Uses the observer pattern.
 *
 *	Properties:
 *	- id: Each TimeObserver should have its own unique ID
 *	- counter: Internal counter for tracking the number of
 *		heartbeats that were received from the Timer.
 *	- duration: The length of time in number of heartbeats
 *		that the TimeObserver should observe the Timer.
 *	- timer: A reference to the Timer object.
 *
 *  Methods:
 *	- initialize: Constructor. Establishes a reference to the
 *		Timer. Initializes the object properties.
 *	- registerObserver: Registers the observer with the Timer.
 *	- removeObserver: Removes the observer from Timer registration.
 *	- update: Called by the Timer with each heartbeat.
 *	- updateAction: The action to take with each heartbeat.  This
 *		should be overriden by each object.
 *	- finishAction:  The action to take following the last
 *		heartbeat.  This should be overriden by each object.
 *	- stop: Stops the object from observing the Timer. Resets
 *		the counter to 0.
 *-------------------------------------------------------*/

var TimeObserver = Class.create();
TimeObserver.prototype = {

	/*--------------------------------------------------------
	 *  Method:  initialize
	 *  
	 *  Description:
	 *  Constructor. Retrieves an instance of the Timer. Initializes
	 *	the object properties.
	 *
	 *  Parameters:
	 *  none
	 *  
	 *  Return:
	 *  none
	 *-------------------------------------------------------*/

	initialize: function() {
		this.id			= "";			// Needed to register with Timer. We expect this to be overriden.
		this.counter	= 0;
		this.duration	= 10;			// We expect this to be overriden as needed.
		this.timer		= Timer.prototype.getInstance();
	},

	/*--------------------------------------------------------
	 *  Method:  registerObserver
	 *  
	 *  Description:
	 *  Registers the object with the Timer.
	 *
	 *  Parameters:
	 *  none
	 *  
	 *  Return:
	 *  none
	 *-------------------------------------------------------*/
	
	registerObserver: function() {
		this.timer.registerObserver(this);
	},

	/*--------------------------------------------------------
	 *  Method:  removeObserver
	 *  
	 *  Description:
	 *  Removes the object from the Timer.
	 *
	 *  Parameters:
	 *  none
	 *  
	 *  Return:
	 *  none
	 *-------------------------------------------------------*/
	
	removeObserver: function() {
		this.timer.removeObserver(this);
	},

	/*--------------------------------------------------------
	 *  Method:  update
	 *  
	 *  Description:
	 *  Called by the Timer with each heartbeat.  Manages the
	 *	counter.  Calls updateAction with each heartbeat. Calls
	 *	finishAction after the last hearbeat.
	 *
	 *  Parameters:
	 *  none
	 *  
	 *  Return:
	 *  none
	 *-------------------------------------------------------*/
	
	update: function() {
		this.counter++;
		this.updateAction(this.counter);
		if (this.counter >= this.duration) {
			this.finishAction();
			this.stop();
		}
	},

	/*--------------------------------------------------------
	 *  Method:  updateAction
	 *  
	 *  Description:
	 *  The action to take with each heartbeat. It is expected
	 *	that each object will override this method.
	 *
	 *  Parameters:
	 *  pCounter	integer		The current value of the counter
	 *  
	 *  Return:
	 *  none
	 *-------------------------------------------------------*/
	
	updateAction: function(pCounter) {},			// We expect this to be overriden

	/*--------------------------------------------------------
	 *  Method:  finishAction
	 *  
	 *  Description:
	 *  The action to take following the last heartbeat. It is
	 *	expected that each object will override this method.
	 *
	 *  Parameters:
	 *  none
	 *  
	 *  Return:
	 *  none
	 *-------------------------------------------------------*/

	finishAction: function() {},					// We expect this to be overriden

	/*--------------------------------------------------------
	 *  Method:  stop
	 *  
	 *  Description:
	 *  Stops the object from observing the Timer. Removes the
	 *	object from the Timer's list of observers and resets
	 *	the counter to 0.
	 *
	 *  Parameters:
	 *  none
	 *  
	 *  Return:
	 *  none
	 *-------------------------------------------------------*/
	
	stop: function() {
		this.timer.removeObserver(this);
		this.counter = 0;
	}
}
/*--------------------------------------------------------
 *  END TimeObserver class
 *-------------------------------------------------------*/

/*--------------------------------------------------------
 *  Object:  Move
 *  
 *  Description:
 *	Utility object.  Provides methods for movement.
 *
 *	Adapted from Robert Penner's easing formulas.
 *
 *  Methods:
 *	- easeOutCubic: Formula for cubic ease out.
 *	- easeInOutCubic: Formula for cubic ease in and out.
 *-------------------------------------------------------*/

var Move = {

	/*--------------------------------------------------------
	 *  Method:  easeOutCubic
	 *  
	 *  Description:
	 *  Formula for cubic ease out (slows down). Returns a position
	 *  given a particular start position, distance to travel and
	 *  duration for travel.
	 *
	 *  Parameters:
	 *  pTime		integer	The current time count
	 *	pStart		integer	The start position
	 *	pDistance	integer	The total distance to travel
	 *	pDuration	integer	The total time count to complete
	 *						movement
	 *  
	 *  Return:
	 *  integer		The current position at the current time count
	 *-------------------------------------------------------*/

	easeOutCubic: function(pTime, pStart, pDistance, pDuration) {
		return pStart + pDistance*((pTime = pTime/pDuration - 1)*pTime*pTime + 1);
	},

	/*--------------------------------------------------------
	 *  Method:  easeInOutCubic
	 *  
	 *  Description:
	 *  Formula for cubic ease in and out (speeds up and slows down).
	 *	Returns a position given a particular start position,
	 *	distance to travel and duration for travel.
	 *
	 *  Parameters:
	 *  pTime		integer	The current time count
	 *	pStart		integer	The start position
	 *	pDistance	integer	The total distance to travel
	 *	pDuration	integer	The total time count to complete
	 *						movement
	 *  
	 *  Return:
	 *  integer		The current position at the current time count
	 *-------------------------------------------------------*/
	
	easeInOutCubic: function(pTime, pStart, pDistance, pDuration) {
		if ((pTime*=2/pDuration) < 1) {
			return pStart + pDistance/2*(pTime * pTime * pTime);
		} else {
			return pStart + pDistance/2*((pTime -= 2) * pTime * pTime + 2);
		}
	}
}

/*--------------------------------------------------------
 *	Generic UI Control Classes
 *------------------------------------------------------*/

/*--------------------------------------------------------
 *  Class:  Drawer
 *  
 *  Description:
 *	Establishes a scrolling drawer element. The Drawer element
 *	takes up space when opened and pushes other elements down on
 *	the page.
 *
 *	Requires a div with class drawerContainer containing a div
 *	with class drawer.
 *
 *	Extends DOMDecorator and TimeObserver classes.
 *
 *	ObjectNotFoundException runtime exception is thrown if
 *	a proper DOM element is not provided to the constructor or
 *	if the Drawer child div is not found.
 *	InvalidObjectRefException is thrown if an invalid object
 *	reference is provided to the constructor.
 *
 *	Properties:
 *	- obj: Reference to DOM element representing the
 *		Drawer container
 *	- id: Unique ID of the Drawer for TimeObserver purposes
 *	- drawer: Reference to the child Drawer object
 *	- start: Start position for Drawer movement
 *	- distance: Distance to travel for Drawer movement
 *	- duration: Duration for opening or closing Drawer
 *	- isOpen: True if the Drawer is open
 *
 *  Methods:
 *	- intialize: Constructor. Sets references to the Drawer container
 *		DOM element and its child Drawer. Initializes the start,
 *		distance and duration properties for Drawer movement.
 *	- open:	Opens the Drawer.
 *	- close: Closes the Drawer.
 *	- toggle: Toggles the Drawer between opened and closed.
 *	- updateAction: Overrides TimeObserver's updateAction method.
 *		Initiates movement for closing or opening the Drawer.
 *	- move: Calculates the movement of the Drawer using a
 *		cubic ease out formula.
 *  
 *  Exceptions:
 *	- ObjectNotFoundException: Left uncaught if a valid DOM
 *		object is not provided to the constructor or if the
 *		child Drawer element is not found.
 *	- InvalidObjectRefException: Left uncaught if an invalid
 *		object reference is provided to the constructor.
 *-------------------------------------------------------*/

var Drawer = Class.create();
Drawer.prototype.extend(DOMDecorator.prototype).extend(TimeObserver.prototype).extend({

	/*--------------------------------------------------------
	 *  Method:  initialize
	 *  
	 *  Description:
	 *  Constructor. Sets references to the Drawer container
	 *	DOM element and its child Drawer. Initializes the start,
	 *	distance and duration properties for Drawer movement.
	 *
	 *  Parameters:
	 *  pObj		string or object	Reference to the Drawer
	 *									container
	 *	pDuration	integer		Optional duration for Drawer
	 *							movement. Overrides the default
	 *							of TimeObserver.
	 *  
	 *  Return:
	 *  none
	 *-------------------------------------------------------*/

	initialize: function(pObj, pDuration) {
		DOMDecorator.prototype.initialize.apply(this, arguments);
		TimeObserver.prototype.initialize.apply(this, arguments);
		
		this.drawer = this.getChildrenByClassName('drawer')[0];
		if (!this.drawer) {
			throw new ObjectNotFoundException('drawer in ' + pObj);
		}
		this.id = this.obj.id;
		
		// set times
		if (pDuration) {
			this.duration = pDuration;
		}

		// track open/close state
		this.isOpen = false;

		this.obj.style.height = '0px';
		
		// set default distances
		this.start = 0;												// coord of beginning location
		this.distance = this.drawer.clientHeight;					// total distance tweened
	},

	/*--------------------------------------------------------
	 *  Method:  open
	 *  
	 *  Description:
	 *  Opens the Drawer. Uses Timer to coordinate movement.
	 *
	 *  Parameters:
	 *  none
	 *  
	 *  Return:
	 *  none
	 *-------------------------------------------------------*/
	
	open: function() {
		/* If we're already moving, reset it so we can change direction */
		this.stop();

		/* Start the movement */
		this.start = 0;												// coord of beginning location
		this.distance = this.drawer.clientHeight;					// total distance tweened

		this.registerObserver();									// Subscribe to the timer so we can move in sync with the other objects
		this.isOpen = true;
	},

	/*--------------------------------------------------------
	 *  Method:  close
	 *  
	 *  Description:
	 *  Closes the Drawer. Uses the Timer to coordinate movement.
	 *
	 *  Parameters:
	 *  none
	 *  
	 *  Return:
	 *  none
	 *-------------------------------------------------------*/

	close: function() {
		/* If we're already moving, reset it so we can change direction */
		this.stop();
		
		var paneDimension = this.drawer.clientHeight;

		/* Start the movement */
		this.start = paneDimension;						// coord of beginning location
		this.distance = -paneDimension;					// total distance tweened

		this.registerObserver();		// Subscribe to the timer so we can move in sync with the other objects
		this.isOpen = false;
	},

	/*--------------------------------------------------------
	 *  Method:  toggle
	 *  
	 *  Description:
	 *  Toggles the Drawer between opened and closed.
	 *
	 *  Parameters:
	 *  none
	 *  
	 *  Return:
	 *  none
	 *-------------------------------------------------------*/

	toggle: function() {
		if (this.isOpen) {
			this.close();
		} else {
			this.open();
		}
	},

	/*--------------------------------------------------------
	 *  Method:  updateAction
	 *  
	 *  Description:
	 *  Overrides TimeObserver's updateAction method.
	 *	Initiates movement for closing or opening the Drawer.
	 *
	 *  Parameters:
	 *  pCounter	integer		The current value of the counter
	 *  
	 *  Return:
	 *  none
	 *-------------------------------------------------------*/

	updateAction: function(pCounter) {				// Override the TimeObserver.updateAction
		this.move(pCounter);
	},

	/*--------------------------------------------------------
	 *  Method:  move
	 *  
	 *  Description:
	 *  Calculates the movement of the Drawer using a cubic 
	 *	ease out formula.
	 *
	 *  Parameters:
	 *  pCounter	integer		The current value of the counter
	 *  
	 *  Return:
	 *  none
	 *-------------------------------------------------------*/

	move: function(pCounter) {
		this.obj.style.height = Move.easeOutCubic(pCounter, this.start, this.distance, this.duration) + 'px';
	}

});
/*--------------------------------------------------------
 *  END Drawer class
 *-------------------------------------------------------*/

/*--------------------------------------------------------
 *  Class:  WindowShade
 *  
 *  Description:
 *	Establishes a scrolling WindowShade element. The
 *	WindowShade element does not take up space when opened.
 *	Instead, it displays over other elements on the page.
 *
 *	Requires a div with class windowShadeContainer containing a div
 *	with class windowShade.
 *
 *	Extends DOMDecorator and TimeObserver classes.
 *
 *	ObjectNotFoundException runtime exception is thrown if
 *	a proper DOM element is not provided to the constructor or
 *	if the WindowShade child div is not found.
 *	InvalidObjectRefException is thrown if an invalid object
 *	reference is provided to the constructor.
 *
 *	Properties:
 *	- obj: Reference to DOM element representing the
 *		WindowShade container
 *	- id: Unique ID of the WindowShade for TimeObserver purposes
 *	- windowShade: Reference to the child WindowShade object
 *	- start: Start position for WindowShade movement
 *	- distance: Distance to travel for WindowShade movement
 *	- duration: Duration for opening or closing WindowShade
 *	- isOpen: True if the WindowShade is open
 *
 *  Methods:
 *	- intialize: Constructor. Sets references to the WindowShade
 *		container DOM element and its child WindowShade.
 *		Initializes the start, distance and duration properties
 *		for WindowShade movement.
 *	- open:	Opens the WindowShade.
 *	- close: Closes the WindowShade.
 *	- toggle: Toggles the WindowShade between opened and closed.
 *	- updateAction: Overrides TimeObserver's updateAction method.
 *		Initiates movement for closing or opening the WindowShade.
 *	- finishAction: Overrides TimeObserver's finishAction method.
 *		Hides the WindowShade when closed.
 *	- move: Calculates the movement of the WindowShade using a
 *		cubic ease out formula.
 *  
 *  Exceptions:
 *	- ObjectNotFoundException: Left uncaught if a valid DOM
 *		object is not provided to the constructor or if the
 *		child WindowShade element is not found.
 *	- InvalidObjectRefException: Left uncaught if an invalid
 *		object reference is provided to the constructor.
 *-------------------------------------------------------*/

var WindowShade = Class.create();
WindowShade.prototype.extend(DOMDecorator.prototype).extend(TimeObserver.prototype).extend({

	/*--------------------------------------------------------
	 *  Method:  initialize
	 *  
	 *  Description:
	 *  Constructor. Sets references to the WindowShade container
	 *	DOM element and its child WindowShade. Initializes the start,
	 *	distance and duration properties for WindowShade movement.
	 *
	 *  Parameters:
	 *  pObj		string or object	Reference to the WindowShade
	 *									container.
	 *	pDuration	integer		Optional duration for WindowShade
	 *							movement. Overrides the default
	 *							of TimeObserver.
	 *  
	 *  Return:
	 *  none
	 *-------------------------------------------------------*/

	initialize: function(pObj, pDuration) {
		DOMDecorator.prototype.initialize.apply(this, arguments);
		TimeObserver.prototype.initialize.apply(this, arguments);
		
		this.windowShade = this.getChildrenByClassName('windowShade')[0];
		if (!this.windowShade) {
			throw new ObjectNotFoundException('windowShade in ' + pObj);
		}
		
		this.id = this.obj.id;

		if (pDuration) {
			this.duration = pDuration;
		}

		this.isOpen = false;

		var windowShadeHeight = this.windowShade.clientHeight;

		this.windowShade.style.top = -windowShadeHeight + 'px';
		this.windowShade.style.visibility = 'hidden';
		this.obj.style.height = '0px';

		this.start = -windowShadeHeight;
		this.distance = windowShadeHeight;
	},

	/*--------------------------------------------------------
	 *  Method:  open
	 *  
	 *  Description:
	 *  Opens the WindowShade. Uses Timer to coordinate movement.
	 *
	 *  Parameters:
	 *  none
	 *  
	 *  Return:
	 *  none
	 *-------------------------------------------------------*/
	
	open: function() {
		/* If we're already moving, reset it so we can change direction */
		this.stop();

		var windowShadeHeight = this.windowShade.clientHeight;
		this.windowShade.style.top = -windowShadeHeight + 'px';
		this.windowShade.style.visibility = 'visible';
		this.obj.style.height = 'auto';

		this.start = -windowShadeHeight;
		this.distance = windowShadeHeight;
		
		this.registerObserver();
		this.isOpen = true;
	},

	/*--------------------------------------------------------
	 *  Method:  close
	 *  
	 *  Description:
	 *  Closes the WindowShade. Uses the Timer to coordinate movement.
	 *
	 *  Parameters:
	 *  none
	 *  
	 *  Return:
	 *  none
	 *-------------------------------------------------------*/
	
	close: function() {
		/* If we're already moving, reset it so we can change direction */
		this.stop();

		this.start = 0;
		this.distance = -this.windowShade.clientHeight;
		
		this.registerObserver();
		this.isOpen = false;
	},

	/*--------------------------------------------------------
	 *  Method:  toggle
	 *  
	 *  Description:
	 *  Toggles the WindowShade between opened and closed.
	 *
	 *  Parameters:
	 *  none
	 *  
	 *  Return:
	 *  none
	 *-------------------------------------------------------*/
		
	toggle: function() {
		if (this.isOpen) {
			this.close();
		} else {
			this.open();
		}
	},

	/*--------------------------------------------------------
	 *  Method:  updateAction
	 *  
	 *  Description:
	 *  Overrides TimeObserver's updateAction method.
	 *	Initiates movement for closing or opening the WindowShade.
	 *
	 *  Parameters:
	 *  pCounter	integer		The current value of the counter
	 *  
	 *  Return:
	 *  none
	 *-------------------------------------------------------*/
	
	updateAction: function(pCounter) {				// Override the TimeObserver.updateAction
		this.move(pCounter);
	},

	/*--------------------------------------------------------
	 *  Method:  finishAction
	 *  
	 *  Description:
	 *  Overrides TimeObserver's finishAction method.
	 *	Hides the WindowShade after closing.
	 *
	 *  Parameters:
	 *  none
	 *  
	 *  Return:
	 *  none
	 *-------------------------------------------------------*/
	
	finishAction: function() {
		if (!this.isOpen) {
			this.windowShade.style.visibility = 'hidden';
			this.obj.style.height = '0px';
		}
	},

	/*--------------------------------------------------------
	 *  Method:  move
	 *  
	 *  Description:
	 *  Calculates the movement of the WindowShade using a cubic 
	 *	ease out formula.
	 *
	 *  Parameters:
	 *  pCounter	integer		The current value of the counter
	 *  
	 *  Return:
	 *  none
	 *-------------------------------------------------------*/

	move: function(pCounter) {
		this.windowShade.style.top = Move.easeOutCubic(pCounter, this.start, this.distance, this.duration) + 'px';
	}
	
});
/*--------------------------------------------------------
 *  END WindowShade class
 *-------------------------------------------------------*/

/*--------------------------------------------------------
 *  Class:  AlertMessage
 *  
 *  Description:
 *	Provides a message alert mechanism.  Displays text
 *	in a styled DOM element.
 *
 *	Inherits from OpacityControl and TimeObserver classes.
 *
 *	ObjectNotFoundException runtime exception is thrown if
 *	a proper DOM element is not provided to the constructor
 *	or the message text area is not found within the element
 *  structure.
 *	InvalidObjectRefException is thrown if an invalid object
 *	reference is provided to the constructor.
 *
 *	Properties:
 *	- obj: Reference to DOM element representing the
 *		AlertMessage
 *	- id: Unique ID of the AlertMessage for TimeObserver purposes
 *	- textArea: Reference to the DOM element for displaying the
 *		message
 *	- start: Start position for the fade effect
 *	- distance: Distance to travel for the fade effect
 *	- duration: Duration for the fade effect
 *
 *  Methods:
 *	- intialize: Constructor. Sets references to the AlertMessage
 *		DOM element and its text area
 *	- display: Displays the AlertMessage
 *	- updateAction: Overrides TimeObserver method to execute fade
 *	- finishAction: Hides AlertMessage at end of fade
 *	- fade: Executes fade effect
 *  
 *  Exceptions:
 *	- ObjectNotFoundException: Left uncaught if a valid DOM
 *		object is not provided to the constructor or if text
 *		area for displaying message is not found
 *	- InvalidObjectRefException: Left uncaught if an invalid
 *		object reference is provided to the constructor.
 *-------------------------------------------------------*/

var AlertMessage = Class.create();
AlertMessage.prototype.extend(OpacityControl.prototype).extend(TimeObserver.prototype).extend({

	/*--------------------------------------------------------
	 *  Method:  initialize
	 *  
	 *  Description:
	 *  Constructor. Calls super constructor of OpacityControl
	 *	and TimeObserver. Establishes reference to alertText
	 *	DOM element. Throws ObjectNotFoundException
	 *	if proper DOM structure is not found in page.
	 *
	 *  Parameters:
	 *  pObj		string or object	Reference to DOM element representing
	 *						AlertMessage
	 *	pDuration	integer	The count or duration for the fade
	 *						effect.
	 *  
	 *  Return:
	 *  none
	 *-------------------------------------------------------*/

	initialize: function(pObj, pDuration) {
		OpacityControl.prototype.initialize.apply(this, arguments);
		TimeObserver.prototype.initialize.apply(this, arguments);
		
		// Establish unique ID for TimeObserver behavior
		this.id = this.obj.id;
		
		// Retrieve the text display area for the message
		this.textArea = this.getDescendantsByClassName('alertText')[0];
		if (!this.textArea) {
			throw new ObjectNotFoundException('alertText in ' + pObj);
		}

		// Make sure the AlertMessage is hidden
		this.setOpacity(0);
		this.obj.style.display = 'none';

		// We only go to 99.99 opacity because 100 causes an odd "flash" effect
		this.start = 99.99;
		this.distance = -99.99;

		// Set the duration if provided.  Otherwise take the default
		// established by TimeObserver
		if (pDuration) {
			this.duration = pDuration;
		}
	},

	/*--------------------------------------------------------
	 *  Method:  display
	 *  
	 *  Description:
	 *	Initiates display of the AlertMessage.  AlertMessage
	 *	fades from displayed to hidden.
	 *  
	 *  Parameters:
	 *  pMsg	string	The message to display in the AlertMessage
	 *  
	 *  Return:
	 *  none
	 *-------------------------------------------------------*/
	
	display: function(pMsg) {
		/* If we're already fading, reset it so we can redisplay */
		this.stop();

		// Set the message
		this.textArea.innerHTML = pMsg;
		
		// Display the message
		this.obj.style.display = 'block';
		
		// Calculate the message position on the page.
		// Centered vertically
		this.obj.style.top = Math.floor((getWindowHeight() - this.obj.clientHeight)/2 + getScrollY()) + 'px';

		// Over the left nav
		this.obj.style.left = '22px';		// bug ID 411 - adjusted left position to center over left nav
		
		// Start the fade out
		this.registerObserver();
	},

	/*--------------------------------------------------------
	 *  Method:  updateAction
	 *  
	 *  Description:
	 *  Overrides updateAction of TimeObserver.  Executes fade.
	 *  
	 *  Parameters:
	 *  pCounter	integer		Counter for the update
	 *  
	 *  Return:
	 *  none
	 *-------------------------------------------------------*/
	
	updateAction: function(pCounter) {
		this.fade(pCounter);
	},

	/*--------------------------------------------------------
	 *  Method:  finishAction
	 *  
	 *  Description:
	 *  Overrides finishAction of TimeObserver.  Used to perform
	 *	cleanup activities after the AlertMessage completes its
	 *	fade.
	 *  
	 *  Parameters:
	 *  none
	 *  
	 *  Return:
	 *  none
	 *-------------------------------------------------------*/
	
	finishAction: function() {
		// Hide the AlertMessage
		this.obj.style.display = 'none';
	},

	/*--------------------------------------------------------
	 *  Method:  fade
	 *  
	 *  Description:
	 *  Fades out the AlertMessage using an easeInOut formula
	 *  
	 *  Parameters:
	 *  pCounter	integer		Counter for the fade steps
	 *  
	 *  Return:
	 *  none
	 *-------------------------------------------------------*/
	
	fade: function(pCounter) {
		this.setOpacity(Move.easeInOutCubic(pCounter, this.start, this.distance, this.duration));
	}
});
/*--------------------------------------------------------
 *  END AlertMessage class
 *-------------------------------------------------------*/

/*--------------------------------------------------------
 *  Class:  ListControl
 *  
 *  Description:
 *	Manages a list of "items".  Manages items both as an
 *	array and as a physical set of DOM elements appended
 *	within a container.
 *
 *	Inherits from DOMDecorator class.
 *
 *	ObjectNotFoundException runtime exception is thrown if
 *	a proper DOM element is not provided to the constructor.
 *
 *	Properties:
 *	- obj: Reference to DOM element representing the
 *		ListControl
 *	- listItems: array for tracking the items
 *	- count: integer count of the number of items
 *
 *  Methods:
 *	- intialize: Constructor.  Establishes an empty ListControl
 *	- addItem: Adds an item to the ListControl
 *	- removeItem: Removes an item by ID
 *	- removeAll: Removes all items
 *	- hasItem: Checks to see if an item is already being
 *		managed by ID
 *	- getCount: Returns the count of items in the ListControl
 *  
 *  Exceptions:
 *	- ObjectNotFoundException: Left uncaught if a valid DOM
 *		object is not provided to the constructor.
 *-------------------------------------------------------*/

var ListControl = Class.create();
ListControl.prototype.extend(DOMDecorator.prototype).extend({

	/*--------------------------------------------------------
	 *  Method:  initialize
	 *  
	 *  Description:
	 *  Constructor. Calls super constructor of DOMDecorator.
	 *	Creates an empty list and sets count to 0
	 *  
	 *  Parameters:
	 *  pObj	string or object	Reference to DOM element representing list
	 *  
	 *  Return:
	 *  none
	 *-------------------------------------------------------*/

	initialize: function(pObj) {
		DOMDecorator.prototype.initialize.apply(this,arguments);
		this.listItems = new Array;
		this.count = 0;
	},
	
	/*--------------------------------------------------------
	 *  Method:  addItem
	 *  
	 *  Description:
	 *	Adds an item to the ListControl.  Will not add an item
	 *	if it already exists.  Tracks the item in listItems
	 *	array and updates the count.
	 *  
	 *  Parameters:
	 *  pId			string	Unique ID of the item
	 *	pNode		obj		Any DOM element to include inside the
	 *						item
	 *	pAttributes	hash	A hash of attributes to set for the item
	 *	pItem		obj		The item.  Can be any object or data structure
	 *  
	 *  Return:
	 *  none
	 *-------------------------------------------------------*/

	addItem: function(pId, pNode, pAttributes, pItem) {
		// Check if we already have the item by ID
		if (!this.listItems[pId]) {
			// Add the item and track our count
			this.listItems[pId] = pItem;
			this.count++;

			// Create the item, set the attributes and append
			// the node.
			var newItem = createElement('div', pAttributes);
			newItem.appendChild(pNode);
		
			// Add out listItem class
			newItem.className += ' listItem';
			
			// Set the item ID
			newItem.id = pId;
		
			// Add the item to our DOM obj
			this.obj.appendChild(newItem);
		}
	},
	
	/*--------------------------------------------------------
	 *  Method:  removeItem
	 *  
	 *  Description:
	 *	Removes an item from the ListControl by ID
	 *  
	 *  Parameters:
	 *  pId			string	Unique ID of the item
	 *  
	 *  Return:
	 *  none
	 *-------------------------------------------------------*/

	removeItem: function(pId) {
		// Get all our items
		var itemDivs = this.getChildrenByClassName('listItem');
		
		// Search all of our items until we find what we
		// want to remove
		for (var i = 0; i < itemDivs.length; i++) {
			var currItemDiv = itemDivs[i];

			if (currItemDiv.id == pId) {
				// Remove from the DOM
				this.obj.removeChild(currItemDiv);
				
				// Remove from the listItems array
				delete(this.listItems[pId]);
				
				// Update our running count
				this.count--;

				break;
			}
		}
	},
	
	/*--------------------------------------------------------
	 *  Method:  removeAll
	 *  
	 *  Description:
	 *	Removes all items from the ListControl.
	 *  
	 *  Parameters:
	 *  none
	 *  
	 *  Return:
	 *  none
	 *-------------------------------------------------------*/

	removeAll: function() {
		// Get all our items
		var itemDivs = this.getChildrenByClassName('listItem');

		for (var i = 0; i < itemDivs.length; i++) {
			var currItemDiv = itemDivs[i];
			
			// Remove from the DOM
			this.obj.removeChild(currItemDiv);

			// Remove from the listItems array
			delete(this.listItems[currItemDiv.id]);
		}
		// Update our running count
		this.count = 0;
	},
	
	/*--------------------------------------------------------
	 *  Method:  hasItem
	 *  
	 *  Description:
	 *	Determines if an item is already in our ListControl by
	 *	ID
	 *  
	 *  Parameters:
	 *  pId			string	Unique ID of the item
	 *  
	 *  Return:
	 *  boolean		True if the item is already in the ListControl
	 *-------------------------------------------------------*/

	hasItem: function(pId) {
		return (this.listItems[pId] ? true: false);
	},
	
	/*--------------------------------------------------------
	 *  Method:  getCount
	 *  
	 *  Description:
	 *	Getter method for retrieving the count of items
	 *  
	 *  Parameters:
	 *  none
	 *  
	 *  Return:
	 *  none
	 *-------------------------------------------------------*/

	getCount: function() {
		return this.count;
	}
});
/*--------------------------------------------------------
 *  END ListControl class
 *-------------------------------------------------------*/

/*--------------------------------------------------------
 *	Module Specific Controls 
 *------------------------------------------------------*/

/*--------------------------------------------------------
 *	- Review Finder module 
 *------------------------------------------------------*/

/*--------------------------------------------------------
 *  Class:  ReviewFinder
 *  
 *  Description:
 *  Class for handling business logic for Review Finder
 *	functionality.  Composes and manages a Drawer and
 *	anchor element (button).
 *
 *	Assumes a specific DOM structure is available on the page:
 *		- Anchor with id 'reviewFinderBtn'
 *		- Div with id 'reviewFinderDrawer' and following
 *			Drawer structure
 *
 *	ObjectNotFoundException runtime exceptions are thrown if
 *	proper DOM structure is not available.
 *	InvalidObjectRefException is thrown if an invalid object
 *	reference is provided to the constructor.
 *
 *	Uses singleton pattern to ensure that there is only one
 *	ReviewFinder object.  Provides methods as class methods
 *	operating on singleton object.
 *
 *	Properties:
 *	- button: reference to button link element
 *	- finderDrawer: reference to Drawer component
 *	- reviewFinder: singleton instance of ReviewFinder object
 *
 *  Methods:
 *	- intialize: Constructor. Creates handles to components.
 *	- getInstance: Retrieves singleton instance of ReviewFinder.
 *	- open: Opens the finderDrawer Drawer.
 *	- close: Closes finderDrawer Drawer.
 *	- toggle: Toggles finderDrawer Drawer between open
 *		and closed.
 *  
 *  Exceptions:
 *	- ObjectNotFoundException: Left uncaught if	document
 *		structure does not match expectations.
 *	- InvalidObjectRefException: Left uncaught if an invalid
 *		object reference is provided to the constructor.
 *-------------------------------------------------------*/

var ReviewFinder = Class.create();
ReviewFinder.prototype.extend({

	/*--------------------------------------------------------
	 *  Method:  initialize
	 *  
	 *  Description:
	 *  Constructor. Establishes references to DOM elements
	 * 	composing ReviewFinder. Creates Drawer. Throws
	 *	ObjectNotFoundException if proper DOM structure is not
	 *	found in page. Attempts to find button, but ignores
	 *	exceptions if button is not found.
	 *
	 *  WARNING: This constructor should not be called directly
	 *		using 'new'.  Use 'ReviewFinder.prototype.getInstance()',
	 *		instead.  This constructor should be treated as
	 *		private.
	 *  
	 *  Parameters:
	 *  none
	 *  
	 *  Return:
	 *  none
	 *-------------------------------------------------------*/

	initialize: function() {
		this.finderDrawer = new Drawer('reviewFinderDrawer', 1);
		
		try {
			this.button = getObject('reviewFinderBtn');
		} catch (e) {
			this.button = false;
			// We're catching the ObjectNotFoundException for the button.
			// We're okay with the button not existing.
		}
	},

	/*--------------------------------------------------------
	 *  Method:  getInstance
	 *  
	 *  Description:
	 *  Retrieves singleton instance of ReviewFinder object.
	 *  Generates the object if necessary.  This method should
	 *  be used to get a ReviewFinder object instead of using
	 *	'new ReviewFinder()'.
	 *  
	 *  Parameters:
	 *  none
	 *  
	 *  Return:
	 *  object	Singleton instance of ReviewFinder object
	 *-------------------------------------------------------*/
	
	getInstance: function() {
		if (!this.reviewFinder) {
			this.reviewFinder = new ReviewFinder();
		}
		
		return this.reviewFinder;
	},
	
	/*--------------------------------------------------------
	 *  Method:  open
	 *  
	 *  Description:
	 *  Opens finderDrawer Drawer. Sets opened style for button
	 *	link.
	 *  
	 *  Parameters:
	 *  none
	 *  
	 *  Return:
	 *  none
	 *-------------------------------------------------------*/
	
	open: function() {
		with (ReviewFinder.prototype.getInstance()) {
			if (button) {
				changeClass(button, 'reviewFinderBtnOff', 'reviewFinderBtnOn');
			}
			finderDrawer.open();
		}
	},

	/*--------------------------------------------------------
	 *  Method:  close
	 *  
	 *  Description:
	 *  Closes finderDrawer Drawer. Sets closed style for button
	 *	link.
	 *  
	 *  Parameters:
	 *  none
	 *  
	 *  Return:
	 *  none
	 *-------------------------------------------------------*/
	
	close: function() {
		with (ReviewFinder.prototype.getInstance()) {
			if (button) {
				changeClass(button, 'reviewFinderBtnOn', 'reviewFinderBtnOff');
			}
			finderDrawer.close();
		}
	},

	/*--------------------------------------------------------
	 *  Method:  toggle
	 *  
	 *  Description:
	 *  Toggles finderDrawer Drawer between opened and closed.
	 *	Coordinates opened and closed styles for button link.
	 *  
	 *  Parameters:
	 *  none
	 *  
	 *  Return:
	 *  none
	 *-------------------------------------------------------*/
	
	toggle: function() {
		with (ReviewFinder.prototype.getInstance()) {
			if (button) {
				if (finderDrawer.isOpen) {
					changeClass(button, 'reviewFinderBtnOn', 'reviewFinderBtnOff');
				} else {
					changeClass(button, 'reviewFinderBtnOff', 'reviewFinderBtnOn');
				}
			}
			finderDrawer.toggle();
		}
	}
});

/*--------------------------------------------------------
 *	- Custom module utils
 *------------------------------------------------------*/
var PcwCustomModules = new Object();
PcwCustomModules.slotNumber = 0;
PcwCustomModules.defaultSet = ['news','downloads','revhard','blogs','howto','revsoft'];
PcwCustomModules.getModuleSet = function() {
	var labelName = 'custom.modules';
	var data = null;
	var labelLen = labelName.length;
	/* read cookie property only once for speed */
	var cookieData = document.cookie;
	var cLen = cookieData.length;
	var i = 0;
	var cEnd;
	while (i < cLen) {
		var j = i + labelLen;
		if (cookieData.substring(i,j) == labelName) {
			cEnd = cookieData.indexOf(';',j);
			if (cEnd == -1) {
				cEnd = cookieData.length;
			}
			data = unescape(cookieData.substring(j+2, cEnd-1));
		}
		i++;
	}
	if (data) {
		data = data.split(',');
	}
	return data;
}

/*--------------------------------------------------------
 *	- My Pages module 
 *------------------------------------------------------*/

/* CONSTANTS */
var TOO_MANY_PAGES = 'Your list is full. Please remove a page.';
var ALREADY_ADDED = 'Already added&#8230;';
var ADDING = 'Adding to My Pages...';
var REMOVE_TITLE = 'Remove from My Pages';

/*--------------------------------------------------------
 *  Class:  MyPages
 *  
 *  Description:
 *  Class for handling business logic for My Pages
 *	functionality.  Composes and manages a WindowShade,
 *	ListControl, anchor element (button), and AlertMessage.
 *
 *	Assumes a specific DOM structure is available on the page:
 *		- Anchor with id 'myPagesBtn'
 *		- Div with id 'myPagesAlert' and following
 *			AlertMessage structure
 *		- Div with id 'myPagesList' and following
 *			WindowShade structure
 *		- Div within 'myPagesList' WindowShade structure and
 *			with class ''myPagesList'
 *
 *	ObjectNotFoundException runtime exceptions are thrown if
 *	proper DOM structure is not available.
 *	InvalidObjectRefException is thrown if an invalid object
 *	reference is provided to the constructor.
 *
 *	Uses singleton pattern to ensure that there is only one
 *	MyPages object.  Provides methods as class methods
 *	operating on singleton object.
 *
 *	Business rules:
 *	- Enforces a maximum number of pages (10)
 *  
 *	Properties:
 *	- MAX_PAGES: maximum number of pages allowed
 *	- button: reference to button link element
 *	- alertMessage: reference to AlertMessage component
 *	- listWindowShade: reference to WindowShade component
 *	- pageList: reference to ListControl component
 *	- myPages: singleton instance of MyPages object
 *
 *  Methods:
 *	- intialize: Constructor.  Creates handles to components.
 *		Preloads any saved My Pages from server.
 *	- getInstance: Retrieves singleton instance of MyPages.
 *	- preloadPages: Preloads any saved My Pages from server.
 *  - addPage: Adds a page to singleton instance of MyPages
 *	- removePage: Removes a page to singleton instance of
 *		MyPages.
 *	- removeAll: Removes all pages.
 *	- openList:	Opens listWindowShade WindowShade.
 *	- closeList: Closes listWindowShade WindowShade.
 *	- toggleList: Toggles listWindowShade WindowShade between open
 *		and closed.
 *	- updateCount: Updates button link element with the
 *		number of pages saved.  
 *  
 *  Exceptions:
 *	- ObjectNotFoundException: Thrown or left uncaught if
 *		document structure does not match expectations.
 *	- InvalidObjectRefException: Left uncaught if an invalid
 *		object reference is provided to the constructor.
 *-------------------------------------------------------*/

var MyPages = Class.create();
MyPages.prototype.extend({

	/*--------------------------------------------------------
	 *  Method:  initialize
	 *  
	 *  Description:
	 *  Constructor. Establishes references to DOM elements
	 * 	composing MyPages. Creates WindowShade, AlertMessage and
	 *	ListControl member objects. Preloads any existing
	 *	saved pages from server. Throws ObjectNotFoundException
	 *	if proper DOM structure is not found in page.
	 *
	 *  WARNING: This constructor should not be called directly
	 *		using 'new'.  Use 'MyPages.prototype.getInstance()',
	 *		instead.  This constructor should be treated as
	 *		private.
	 *  
	 *  Parameters:
	 *  none
	 *  
	 *  Return:
	 *  none
	 *-------------------------------------------------------*/

	initialize: function() {
		this.MAX_PAGES = 10;
	
		try {
			this.button = getObject('myPagesBtn');

			this.alertMessage = new AlertMessage('myPagesAlertMessage', 35);
			this.listWindowShade = new WindowShade('myPagesWindowShade');

			var listObj = this.listWindowShade.getDescendantsByClassName('myPagesList')[0];
			if (!listObj) {
				throw new ObjectNotFoundException('myPagesList');
			} else {
				this.pageList = new ListControl(listObj);
			}
		} catch (e) {
			throw 'Could not create MyPages object. ' + e.toString();
		}
		
		this.preloadPages();
	},

	/*--------------------------------------------------------
	 *  Method:  getInstance
	 *  
	 *  Description:
	 *  Retrieves singleton instance of MyPages object.
	 *  Generates the object if necessary.  This method should
	 *  be used to get a MyPages object instead of using
	 *	'new MyPages()'.
	 *  
	 *  Parameters:
	 *  none
	 *  
	 *  Return:
	 *  object		Singleton instance of MyPages object
	 *-------------------------------------------------------*/
	
	getInstance: function() {
		if (!this.myPages) {
			this.myPages = new MyPages();
		}
		
		return this.myPages;
	},

	/*--------------------------------------------------------
	 *  Method:  preloadPages
	 *  
	 *  Description:
	 *  Preloads existing saved pages from the server.
	 *  
	 *  Parameters:
	 *  none
	 *  
	 *  Return:
	 *  none
	 *-------------------------------------------------------*/
	
	preloadPages: function() {
		/*
		Placeholder method for preloading saved pages from DB.
		Presumably, this is an AJAX call to the server that retrieves
		the user's saved pages based on cookied authentication and loads
		them into pageList using addItem().
		*/
	},

	/*--------------------------------------------------------
	 *  Method:  addPage
	 *  
	 *  Description:
	 *  Adds a page to the pageList ListControl. Verifies that
	 *	MAX_PAGES limit has not been reached. Verifies if page
	 *	has already been saved.  Generates DOM structure to
	 *	represent page in pageList. Notifies server if page
	 *	was added.
	 *  
	 *  Parameters:
	 *  pId		string	Unique reference to page being added
	 *	pLabel	string	User-friendly label to identify page
	 *	pURL	string	The url of the page being saved
	 *  pSetCookie boolean Pages added by user call this method with this parameter set to true
	 * 			pages added on page load have this method set to false
	 *  
	 *  Return:
	 *  boolean		True if the page is added or already added
	 *-------------------------------------------------------*/

	addPage: function(pId, pLabel, pURL, pSetCookie) {
		with (MyPages.prototype.getInstance()) {
		
			// Check if we've already saved the page
			if (pageList.hasItem(pId)) {
				alertMessage.display(ALREADY_ADDED);
				return true;
				
			// Check if we're over our limit on pages
			} else if (pageList.getCount() >= MAX_PAGES) {
				alertMessage.display(TOO_MANY_PAGES);
				return false;
				
			// Otherwise, add the page.
			} else {
				if(pURL.substr(0,1) == "/"){
					pURL = "http://www.pcworld.com"+pURL;
				}

				// Generate DOM structure for the page in pageList
				// This could be a separate method or a unique class
				// But we only need to call it here, so far
				
				// Create overall div.  Establish rollovers for the listing
				// Rollovers change the class of the div
				var newElement = createElement('div', {
					'class': 'myPageOff',
					'onmouseover': "changeClass(this, 'myPageOff', 'myPageOn');",
					'onmouseout': "changeClass(this, 'myPageOn', 'myPageOff')"
				});
				
				// Add a link to remove the page from the list
				// Clicking link calls removePage()
				var removeLink = createElement('a', {
					'href': "javascript:MyPages.prototype.removePage('" + pId + "')",
					'class': 'removeBtn',
					'title': REMOVE_TITLE
				});
				newElement.appendChild(removeLink);
				
				// Create actual link to page.  Use pLabel to identify the page
				var pageLink = createElement('a', {
					'href': pURL,
					'class': 'myPageLink'
				});
				pageLink.innerHTML = pLabel;
				newElement.appendChild(pageLink);
				
				// We need a clear div to clear out side-by-side display
				// of remove link and page link
				var clearDiv = createElement('div', {'class': 'clear'});
				newElement.appendChild(clearDiv);

				// Finally, a divider to visually separate from the next link
				var divider = createElement('div', {'class': 'myPagesDivider'});
				newElement.appendChild(divider);
			
				// Let the user know we're adding
				// if pSetCookie = false then don't display this message
				// this means that the saved pages list is getting preloaded at 
				// page load from cookie
				if(pSetCookie){
					alertMessage.display(ADDING);
				}

				// Add the page and update the count display
				pageList.addItem(pId, newElement, {'class': 'myPageItem'}, pURL);
				updateCount();
				
				// AJAX call to save page to DB goes here
				
				// set cookie to expire in 60 days
				var count = pageList.getCount();
				if(pSetCookie){
					var d = new Date();
					d.setDate(d.getDate()+60);
					var savedPage = pId+"\t"+pLabel+"\t"+pURL;
					var cookie = pcw_readCookie("savedPages");
					if(cookie!=""){
						cookie=cookie+"\n";
					}
					cookie=cookie+savedPage;
					pcw_setCookie("savedPages", cookie, d, "pcworld.com");
				}
				return true;
			}
		}
	},

	/*--------------------------------------------------------
	 *  Method:  removePage
	 *  
	 *  Description:
	 *  Removes a page from pageList ListControl, identified by
	 *	unique id. Updates button link with proper count of
	 *	pages.  Notifies server of change.
	 *  
	 *  Parameters:
	 *  pId		string	Unique reference to page being removed
	 *  
	 *  Return:
	 *  none
	 *-------------------------------------------------------*/
	
	removePage: function(pId) {
		with (MyPages.prototype.getInstance()) {
			pageList.removeItem(pId);
			updateCount();
			
			// AJAX call to remove page from DB goes here
			
			// remove from cookie too
			var savedPages = getSavedPages();
			// find the page with mathing pId;
			for(var i = 0; i < savedPages.length; i++){
				if(savedPages[i].id==pId){
					savedPages = savedPages.slice(0,i).concat(savedPages.slice(i+1));
					break;
				}
			}
			
			// now set the cookie again 
			var cookie="";
			for(var i = 0; i < savedPages.length; i++){
				cookie=cookie+savedPages[i].id+"\t"+savedPages[i].title+"\t"+savedPages[i].url+"\n";
			}
			cookie = cookie.substr(0, cookie.length-1);
			var d = new Date();
			d.setDate(d.getDate()+60);
			pcw_setCookie("savedPages", cookie, d, "pcworld.com");
		}
	},

	/*--------------------------------------------------------
	 *  Method:  removeAll
	 *  
	 *  Description:
	 *  Removes all pages from pageList ListControl. Updates
	 *	button link with proper count of pages. Notifies server
	 *	of change.
	 *  
	 *  Parameters:
	 *  none
	 *  
	 *  Return:
	 *  none
	 *-------------------------------------------------------*/
	
	removeAll: function() {
		with (MyPages.prototype.getInstance()) {
			pageList.removeAll();
			updateCount();
			
			// AJAX call to remove all pages from DB goes here
			
			// remove pages from cookie
			pcw_removeCookie("savedPages", "pcworld.com");
		}
	},
	
	/*--------------------------------------------------------
	 *  Method:  openList
	 *  
	 *  Description:
	 *  Opens listWindowShade WindowShade. Sets opened style for button
	 *	link.
	 *  
	 *  Parameters:
	 *  none
	 *  
	 *  Return:
	 *  none
	 *-------------------------------------------------------*/
	
	openList: function() {
		with (MyPages.prototype.getInstance()) {
			changeClass(button, 'myPagesBtnOff', 'myPagesBtnOn');
			listWindowShade.open();
		}
	},

	/*--------------------------------------------------------
	 *  Method:  closeList
	 *  
	 *  Description:
	 *  Closes listWindowShade WindowShade. Sets closed style for button
	 *	link.
	 *  
	 *  Parameters:
	 *  none
	 *  
	 *  Return:
	 *  none
	 *-------------------------------------------------------*/
	
	closeList: function() {
		with (MyPages.prototype.getInstance()) {
			changeClass(button, 'myPagesBtnOn', 'myPagesBtnOff');
			listWindowShade.close();
		}
	},

	/*--------------------------------------------------------
	 *  Method:  toggleList
	 *  
	 *  Description:
	 *  Toggles listWindowShade WindowShade between opened and closed.
	 *	Coordinates opened and closed styles for button link.
	 *  
	 *  Parameters:
	 *  none
	 *  
	 *  Return:
	 *  none
	 *-------------------------------------------------------*/
	
	toggleList: function() {
		with (MyPages.prototype.getInstance()) {
			if (listWindowShade.isOpen) {
				changeClass(button, 'myPagesBtnOn', 'myPagesBtnOff');
			} else {
				changeClass(button, 'myPagesBtnOff', 'myPagesBtnOn');
			}
			listWindowShade.toggle();
		}
	},

	/*--------------------------------------------------------
	 *  Method:  updateCount
	 *  
	 *  Description:
	 *  Updates button with a count of the pages saved.  Also
	 *	sets a proper title for the link element.
	 *  
	 *  Parameters:
	 *  none
	 *  
	 *  Return:
	 *  none
	 *-------------------------------------------------------*/
	
	updateCount: function() {
		with (MyPages.prototype.getInstance()) {
			button.innerHTML = 'My Pages (' + pageList.getCount() + ')';
			button.setAttribute('title', button.innerHTML);
		}
	}
});
/*--------------------------------------------------------
 *  END MyPages class
 *-------------------------------------------------------*/

 /*--------------------------------------------------------
 *  BEGIN Page preloader class
 *-------------------------------------------------------*/

/*--------------------------------------------------------
 *  Class:  Preloader
 *  
 *  Description:
 *  Class for preloading page assets.  Currently supports
 *  preloading of images only.
 *
 *	Properties:
 *	- images: array of images to preload.
 *
 *  Methods:
 *	- intialize: Constructor.  Preloads any image sources
 *      passed in as an array of strings.
 *	- preload: Preloads an image source.
 *	- onloadHandler: Event handler for image onload events  
 *	- onerrorHandler: Event handler for image onerror events  
 *	- onabortHandler: Event handler for image onabort events  
 *  
 *  Exceptions:
 *	none
 *-------------------------------------------------------*/

var Preloader = Class.create();
Preloader.prototype.extend({

	/*--------------------------------------------------------
	 *  Method:  initialize
	 *  
	 *  Description:
	 *  Constructor. Preloads an array of image sources.
	 *  Establishes images array for tracking preloaded images.
	 *
	 *  Parameters:
	 *  pImgArray	Array	An array string of image sources.
	 *  
	 *  Return:
	 *  none
	 *-------------------------------------------------------*/

	initialize: function(pImgArray) {
		this.images = new Array;
		for (var i = 0; i < pImgArray.length; i++) {
			this.preload(pImgArray[i]);
		}
	},

	/*--------------------------------------------------------
	 *  Method:  preload
	 *  
	 *  Description:
	 *  Preloads an image.  Adds the preloaded image to the 
	 *  images member array.  Assigns event handlers for
	 *  onerror, onload, and onabort.
	 *
	 *  Parameters:
	 *  pImg	String	String indicating the source URI for
	 *					the image to preload.
	 *  
	 *  Return:
	 *  none
	 *-------------------------------------------------------*/
	
	preload: function(pImg) {
		var img = new Image;
		img.onload = this.onloadHandler;
		img.onerror = this.onerrorHandler;
		img.onabort = this.onabortHandler;
		
		img.src = pImg;
		this.images.push(img);
	},

	/*--------------------------------------------------------
	 *  Method:  onloadHandler
	 *  
	 *  Description:
	 *  Placeholder event handler for image onload event.
	 *
	 *  Parameters:
	 *  none
	 *  
	 *  Return:
	 *  none
	 *-------------------------------------------------------*/
	
	onloadHandler: function() {
		// alert(this.src + ' loaded!');
	},

	/*--------------------------------------------------------
	 *  Method:  onerrorHandler
	 *  
	 *  Description:
	 *  Placeholder event handler for image onerror event.
	 *
	 *  Parameters:
	 *  none
	 *  
	 *  Return:
	 *  none
	 *-------------------------------------------------------*/
	
	onerrorHandler: function() {
		// alert(this.src + ' failed in error.');
	},

	/*--------------------------------------------------------
	 *  Method:  onabortHandler
	 *  
	 *  Description:
	 *  Placeholder event handler for image onabort event.
	 *
	 *  Parameters:
	 *  none
	 *  
	 *  Return:
	 *  none
	 *-------------------------------------------------------*/
	
	onabortHandler: function() {
		// alert(this.src + ' aborted.');
	}
});

/*--------------------------------------------------------
 *  END Page preloader class
 *-------------------------------------------------------*/


/*--------------------------------------------------------
 *  Begin FeatureViewer class
 *-------------------------------------------------------*/

 //alert("Note: the feature viewer is in mid-development, so if it looks weird it's because i'm in the middle of something");
 
/*--------------------------------------------------------
 *  BEGIN FeatureViewer class
 *-------------------------------------------------------*/
 
var FeatureViewer = Class.create();
FeatureViewer.prototype.extend({

	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() {
		document.location.href = fv.arrLinks[fv.selectedItem-1];
	},

	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
 *-------------------------------------------------------*/

/*--------------------------------------------------------
 *  Overture ads
 *
 *  both of the Ysm functions require the existence of two variables set in code
 * 		YsmAds = an initialized array to contain ysm results
 * 		skip = number of ads to skip. will compare against index of YsmAds 
 *-------------------------------------------------------*/

function renderYsmAds (max,start) {
	var out = "";
	if (YsmAds.length > start) {
		out += "<div class='search'><div class='ysmSponsored'>";
		out += "<div class='ysmSponsoredLegend'><a href=\"javascript:void(0)\" onclick=\"popWin(300,180,'/sponsorlinkslegend')\">About</a></div>";
	}
	for (var i=start;i<max+start;i++) {
		if (YsmAds.length > i) {
			out += renderYsmAd(i);
		} else {
			break;
		}
		skip++;
	}
	if (YsmAds.length > start) {
		out += "</div></div>";
	}
	//cycle back to beginning of loop
	if (skip = YsmAds.length) {
		skip = 0;
	}
	document.write(out);
}

function renderYsmAd (index) {
	var Ad = YsmAds[index];
	var out = "<div class='ysmSponsoredItem'>";
	out += "<a href='"+Ad.url+"' class='ysmSponsoredHed' target='_blank'>"+Ad.title+"</a>";
	out += "<div class='ysmSponsoredDek'>"+Ad.description+"</div>";
	out += "<a href='"+Ad.url+"' class='ysmSponsoredUrl'>"+Ad.displayUrl+"</a>";
	out += "</div>";
	return out;
}

/* am lazy. copying above rather than extending it. should merge eventually - aj */

function renderYSMBigBoxAds (id,sequence) {
	var out = "";
	for (var i=0;i<YsmAds.length;i++) {
			if (sequence == 0 && i % 2 != 0) {
				out += renderYsmBigBoxAd(i);
			} else if (sequence == 1 && i % 2 == 0) {
				out += renderYsmBigBoxAd(i);
			}
	}
    if (id != null) {
    	document.getElementById(id).innerHTML = out;
    }
}

function renderYsmBigBoxAd (index) {
	var Ad = YsmAds[index];
	var out = "<p>";
	out += "<a href='"+Ad.url+"' style='font-size:13px;font-weight:bold' target='_blank'>"+Ad.title+"</a><br/>";
	out += Ad.description+"<br/>";
	out += "<a href='"+Ad.url+"'>"+Ad.displayUrl+"</a>";
	out += "</p>";
	return out;
}

/*--------------------------------------------------------
 *  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;
}


/*--------------------------------------------------------
 *  article Voting
 *-------------------------------------------------------*/

function voteForArticle(aid) {
	voteArticlePCW(aid,"yes");
}

function voteAgainstArticle(aid) {
	voteArticlePCW(aid,"no");
}

function voteArticlePCW(aid,vote) {

	var elts = document.getElementsByClassName('articleVoteButtons');
	for(var i = 0; i < elts.length; i++) elts[i].style.display="none";
	
	var elts = document.getElementsByClassName('recPrompt');
	for(var i = 0; i < elts.length; i++) elts[i].style.display="inline";
	
	submitArticleVote(aid, vote, articleVoteComplete);

}

function articleVoteComplete(transport) {
	var id = transport.responseXML.getElementsByTagName("id")[0].childNodes[0].nodeValue;
	window.location="/article/vote/id,"+id+"/thanks.html";
}

function getMyArticleVote(aid) {
	var str = String(aid);
	var cookie = pcw_readCookie('articleVotes');
	var idx = cookie.indexOf(str);
	if(idx == -1) return null;
	var end = cookie.indexOf('\n',idx);
	if(end == -1) end = cookie.length;
	var vote = cookie.substring(idx+str.length+1,end);
	return vote;
}

function submitArticleVote(aid, vote, onCompleteFunction) {
	var ajax = new Ajax.Request(
		'/article/ajax/submitVote',
		{
			method:'post',
			parameters:'id='+aid+'&recommend='+vote+'&nocache=' + Math.round((Math.random() * 90000) + 1),
			onSuccess:onCompleteFunction
		});
	
	var thumbToken = "up";
	if (vote == "no") {
		thumbToken = "down";
	}

	var d = new Date();
	d.setHours(d.getHours()+4);
	var cookie = pcw_readCookie('articleVotes');
	cookie = cookie + '\n'+aid+'\t'+thumbToken;
	pcw_setCookie('articleVotes', cookie, d, 'pcworld.com');

}

function getArticleVoteTotals(aid, onCompleteFunction) {
	var ajax = new Ajax.Request(
		'/article/ajax/getVotes/votes.html',
		{
			method:'get',
			parameters:'id='+aid+'&nocache=' + Math.round((Math.random() * 90000) + 1),
			onSuccess:onCompleteFunction
		});
}

function processVoteXml (httpResult) {
	var xml = httpResult.responseXML;
	var ret = new Object();
	ret.id = xml.getElementsByTagName("id")[0].childNodes[0].nodeValue;
	ret.votesFor = xml.getElementsByTagName("votesFor")[0].childNodes[0].nodeValue;
	ret.votesAgainst = xml.getElementsByTagName("votesAgainst")[0].childNodes[0].nodeValue;
	return ret;
}

var AV = new Object();
function setArticleVotes (httpResult) {
	AV = processVoteXml(httpResult);
	$("votePicTop").innerHTML = AV.votesFor;
	$("votePicBot").innerHTML = AV.votesFor;
	$("votePanBot").innerHTML = AV.votesAgainst;
}

function recArticle(aid,vote) {
	submitArticleVote(aid, vote, recArticleComplete);
}

function recArticleComplete(httpResult) {
	window.location.reload();
}

function showVoteResults(aid) {
	var vote = getMyArticleVote(aid);
	if (null != vote) {
		if (vote != "up") {
			$("voteRecord").id = "voteRecord-no";
		}
		$("voteThanks").style.display = "block";
	}
}

function showVoteThumbs(aid) {
	if (pcw_readCookie('articleVotes').indexOf(aid) > -1) {
		$("voteYes").innerHTML = "";
		$("voteNo").innerHTML = "";
	}
}

function updateArticleVotesWithLinks(transport) {
	var results = processVoteXml(transport);
	var elts = document.getElementsByClassName('recUp');
	for(var i = 0; i < elts.length; i++) {
		elts[i].innerHTML = '<a href="javascript:voteForArticle('+results.id+');" class="thumbsUp"></a>'+
		'<a class="linkInline bold" href="javascript:voteForArticle('+results.id+');">Yes</a><span class="recCount">'+results.votesFor+' Votes</span>';
	}
	elts = document.getElementsByClassName('recDown');
	for(var i = 0; i < elts.length; i++) {
		elts[i].innerHTML = '<a href="javascript:voteAgainstArticle('+results.id+');" class="thumbsDown"></a>'+
		'<a class="linkInline bold" href="javascript:voteAgainstArticle('+results.id+');">No</a><span class="recCount">'+results.votesAgainst+' Votes</span>';
	}
}

function updateArticleVotesNoLinks(transport) {
	var results = processVoteXml(transport);
	
	var elts = document.getElementsByClassName('recUp');
	for(var i = 0; i < elts.length; i++) {
		elts[i].innerHTML = '<b>Yes</b><span class="recCount">'+results.votesFor+' Votes</span>';
	}
	elts = document.getElementsByClassName('recDown');
	for(var i = 0; i < elts.length; i++) {
		elts[i].innerHTML = '<b>No</b><span class="recCount">'+results.votesAgainst+' Votes</span>';
	}
}

function updateArticleVotes(id) {
	var myArticleVote = getMyArticleVote(id);
	if(myArticleVote){
		getArticleVoteTotals(id,updateArticleVotesNoLinks);
		var newMsg = "";
		if(myArticleVote=="up")
			newMsg = 'You recommended this story';
		else
			newMsg='You did not recommend this story';

		var elts = document.getElementsByClassName('recPrompt');
		for(var i = 0; i < elts.length; i++) {
			elts[i].style.fontSize="x-small";
			elts[i].innerHTML = newMsg;
		}
	} else {
		getArticleVoteTotals(id, updateArticleVotesWithLinks);
	}
}

function updateArticleVotes2(id) {
	var myArticleVote = getMyArticleVote(id);
}

/*--------------------------------------------------------
 *  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,id) {
	if (filterSelected) {
		showAllFilters(id);
	} else {
		showSelectedFilter(index,id);
	}
	return false;
}

function showSelectedFilter (index,id) {
	var kids = document.getElementById(id).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";
				//add "show all" link
				var selectedItemHeader = kid.getElementsByTagName("h3")[0];
				selectedItemHeader.appendChild(createToggleFilterLink(index));
			} else {
				kid.style.display = "none";
			}
			counter++;
		}
	}
	filterSelected = true;
}

function showAllFilters (id) {
	var kids = document.getElementById(id).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 = $("pfItemSelectedToggleLink");
	if (showAllLink != null) {
		showAllLink.parentNode.removeChild(showAllLink);
	}
	filterSelected = false;
}

function createToggleFilterLink (index) {
	var link = createElement('a',
	{
		'id': 'pfItemSelectedToggleLink',
		'href': 'javascript:void(0)',
		'onclick': 'toggleFilter('+index+');return false;',
		'class': 'viewAll'
	});
	link.innerHTML = "(View All Filters)";
	return link;
}



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++;
		}
	}
	
}

//overrides to be called on the page *after* the server-side options have been set
function pcw_AdOverride () {
	//what are some cookies that I have set
	var adOverrideMetaData=pcw_readCookie("pcw.adOverrideFlag");
	if (adOverrideMetaData !=undefined && adOverrideMetaData !='')
	{
		pcw_ad_sec=adOverrideMetaData;
		pcw_ad_site="pcw_focuson";

		//invalidate the cookie. Only need to overide for specific click-throughs.
		var date = new Date();
		date.setTime(date.getTime());
		pcw_setCookie("pcw.adOverrideFlag",'', date,"pcworld.com");
	}
	//what is my referrer? do something with it
}


function getRegForm(partner,eventId,isSave) {

	//alert(partner+","+eventId+","+isSave);

	//return false;

	var params = new Array();

	if (null != partner) {
		params.push("partner="+partner);
	}

	if (null != eventId) {
		params.push("eventId="+eventId);
	}
	

	//take form data and save it
	if (isSave != undefined) {

		//alert("is a save");

		if (!document.on24Form.coregConfirm.checked) {
			alert("In order to launch the webcast, you must first check the box and agree to the On24 and PC World privacy policy and terms of use.");
			document.on24Form.coregConfirm.focus();
			return false;
		} else {
			params.push("coregConfirm=on");
			//alert("coregConfirm checked");
		}

		//handle various flavors
		if (null != document.on24Form.optin) {
			if (document.on24Form.optin.checked) {
				params.push("optin=2");
			} else {
				params.push("optin=1");
			}
		} else {
			params.push("optin=0");
		}

		for (var i=0; i< document.on24Form.elements.length; i++) {
			//alert(element.name+":"+element.value);
			var element = document.on24Form.elements[i];
			if (element.name != "optin" && element.name != "coregConfirm") {
				params.push(element.name+"="+escape(element.value));
			}
		}

		//alert(params.join('\n'));

	}
	
	
	var myAjax = new Ajax.Request(
	    "/businesscenter/webcast/form.do",
	    {
	    	method: 'post',
	    	parameters: params.join('&'),
	    	onComplete: showRegForm
	    }
	);
	
	return false;
}


function showRegForm(req) {
	var displayNode = $("regForm");
	//alert(req.responseText);
	displayNode.innerHTML = req.responseText;
	displayNode.style.display = "block";
}

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';
}
