/*	Script: randomValueCookieMaker.js
		This script assigns a user a cookie with a random value within a specified range; useful for a/b testing.
		
		Dependancies:
			 mootools - 	<Moo.js>, <String.js>, <Cookie.js>, <Common.js>, <Utilities.js>, <Function.js>
	
		Author:
			Aaron Newton, <aaron [dot] newton [at] cnet [dot] com>
		
		Class: RandomValueCookieMaker
		Assigns a user a cookie with a random value within a specified range; useful for a/b testing
		
		Arguments:
		options - the options object of key/value options

		Options:
		cookieName - (string, required) a unique name for the cookie.
		limit - (integer) the highest random number to generate; defaults to 10.
		days - (integer) how long to store the cookie; defaults to 999.
		domain - (string) the domain to assign to the cookie; optional.		
		
		Property:
		val - the value of the random cookie
		
		Example:
(start code)
var myRndTest = new RandomValueCookieMaker({
	cookieName: 'myRandomCookie', //a unique name for this cookie.
	limit: 99, //give me 0 through 99
	days: 1, //let's only save it for a day
	domain: 'cnet.com' //let's set it to cnet.com 
										 //so subdomains can get the cookie
});

if(myRndTest.val > 90) //only do this for 10% of users...
(end)
	*/

	var RandomValueCookieMaker = new Class({
		options: {
			cookieName: false,
			limit: 10,
			days: 999,
			domain: false
		},
		initialize: function(options) {
			this.setOptions(options);
			if(this.options.cookieName) this.verify();
			else {
				dbug.log('you must specify a cookie name.');
				return;
			}
		},
		verify: function() {
			this.val = Cookie.get(this.options.cookieName);
			if (!$chk(parseInt(this.val))) {
				this.val = this.makeRand();
				this.saveVal();
			}
		},
/*	Property: setVal
		Sets the cookie to a specified value.
		
		Arguments:
		val - (integer) the value to set the cookie to	*/
		saveVal: function(val) {
			this.val = $pick(val, this.val);
			if (this.options.domain) Cookie.set(this.options.cookieName, this.val, {duration:this.options.days, domain:this.options.domain});
			else Cookie.set(this.options.cookieName, this.val, this.options.days);
		},
		makeSeed: function() {
	     return ((new Date().getTime()*9301+49297) % 233280)/(233280.0);
		},
/*	Property: makeRand
		Returns a random number between 0 and the limit set in the options.	*/
		makeRand: function() {
	     return Math.ceil(this.makeSeed()*this.options.limit);
		}
	});
	RandomValueCookieMaker.implement(new Options);
	/*	legacy namespace	*/
	var randomValueCookieMaker = RandomValueCookieMaker;
/* do not edit below this line */   
/* Section: Change Log 

$Source: /cvs/main/flatfile/html/rb/js/global/cnet.global.framework/common/utilities/randomValueCookieMaker.js,v $
$Log: randomValueCookieMaker.js,v $
Revision 1.8  2007/04/13 19:06:11  newtona
dependency update in the docs

Revision 1.7  2007/03/28 18:09:03  newtona
removing $type.isNumber dependencies

Revision 1.6  2007/03/20 19:23:25  newtona
fixing javascript strict warnings

Revision 1.5  2007/03/08 23:31:22  newtona
strict javascript warnings cleaned up
removed deprecated dbug loadtimers
dbug enables on debug.cookie()

Revision 1.4  2007/01/26 05:56:03  newtona
syntax update for mootools 1.0
docs update

Revision 1.3  2007/01/22 22:50:25  newtona
updated cookie.set syntax

Revision 1.2  2007/01/22 21:54:46  newtona
updated docs to require cookieName

Revision 1.1  2007/01/09 02:39:35  newtona
renamed addons directory to "common" directory

Revision 1.4  2006/11/14 02:06:23  newtona
fixed some syntax bugs

Revision 1.3  2006/11/13 23:53:04  newtona
added cvs footer


*/
/*	Script: simple.template.parser.js
		Provides functionality for very simple template parsing; for more complex template parsing, use TrimPath's excellent Javascript Templates (JST): http://trimpath.com/project/wiki/JavaScriptTemplates.

		Dependencies:
		Moo - <Moo.js>, <Utility.js>, <Function.js>, <String.js>
	
		Author:
		Aaron Newton (aaron [dot] newton [at] cnet [dot] com)
		
		Object: simpleTemplateParser
		This object provides functionality for very simple template parsing; for more complex template parsing, use TrimPath's excellent Javascript Templates (JST): http://trimpath.com/project/wiki/JavaScriptTemplates. It can be used on its own or implemented into a class.
	*/

var simpleTemplateParser = {
		STP: {},
/*	Property: parseTemplate
		Parses a template with the values of an object, substituting those values for all instances of the keys in the object found within the template.

		Arguments: 
		template - a string to parse
		object - the object with your key/value pairs
		regexOptions - the options for the regex replace; defaults to 'ig' (ignore case, global replace)
		wrappers - an object with the before and after strings that are on either side of your keys (see example);
			defaults to {before: "%", after: "%"}

		Example:
(start code)
<textarea id="myTemplate">
	<p>This is some html that lets me subsitute things.</p>
	<ul>
		<li>%firstThing%</li>
		<li>%secondThing%</li>
		<li>%thirdThing%</li>
	</ul>
</textarea>
<script>
	var myTemplate = $('myTemplate').innerHTML;
	var myObject = {
		firstThing: 'hi there',
		secondThing: 'howzit goin?',
		thirdThing: 'really? me too!'
	}
	var parsed = simpleTemplateParser.parseTemplate(myTemplate, myObject);
</script>(end)
	*/
		parseTemplate: function(template, object, regexOptions, wrappers) {
			var STP = this.STP;
			STP.template = template;
			STP.object = object;
			STP.regexOptions = $pick(regexOptions, 'ig');
			STP.wrappers = $pick(wrappers, {before:'%', after:'%'});
			return STP.result = this.runParser(STP.object, STP.template, STP.regexOptions);
		},
		runParser: function(object, string, regexOptions){
			for(value in object){
				switch($type(object[value])){
					case 'string':
						string = this.tmplSubst(value, object[value], string, regexOptions);
						break;
					case 'number':
						string = this.tmplSubst(value, object[value], string, regexOptions);
						break;
					case 'object':
						string = this.runParser(object[value]);
						break;
					case 'array':
						string = this.tmplSubst(value, object[value].toString(), string, regexOptions);
						break;
				}
			}
			return string;
		},
		tmplSubst: function(key, value, string, regexOptions){
			return string.replace(new RegExp(this.STP.wrappers.before+key+this.STP.wrappers.after, 'gi'), value);
		}
	};
/* do not edit below this line */   
/* Section: Change Log 

$Source: /cvs/main/flatfile/html/rb/js/global/cnet.global.framework/common/utilities/simple.template.parser.js,v $
$Log: simple.template.parser.js,v $
Revision 1.4  2007/06/07 18:43:37  newtona
added CSS to autocompleter.js
removed string.cnet.js dependencies from template parser and stickyWin.default.layout.js

Revision 1.3  2007/03/02 01:32:52  newtona
swapped out string.replace with string.replaceAll

Revision 1.2  2007/01/26 05:56:03  newtona
syntax update for mootools 1.0
docs update

Revision 1.1  2007/01/09 02:39:35  newtona
renamed addons directory to "common" directory

Revision 1.2  2007/01/09 01:25:47  newtona
docs syntax fix

Revision 1.1  2007/01/05 18:55:02  newtona
first check in


*/
/*
Script: fixpng.js

Dependancies:
	 mootools - <Moo.js>, <String.js>, <Array.js>, <Function.js>, <Element.js>, <Dom.js>
	
Author:
	Aaron Newton, <aaron [dot] newton [at] cnet [dot] com>

		Function: fixPNG
		this will make transparent pngs show up correctly in IE. This function 
		is based almost entirely on the function found here: 
		<http://homepage.ntlworld.com/bobosola/pnginfo.htm>
		
		Arguments:
		myImage - the image element or id to fix
		
		Note: 
		there is an instances of this already set to fire onDOMReady that
		will fix any png files with the class "fixPNG". This means any producer
		can just give the class "fixPNG" to any img tag and they are set BUT, the
		ping will look wrong until the DOM loads, which may or may not be noticeable.
		
		The alternative is to embed the call right after the image like so:
		
		><img src="png1.png" width="50" height="50" id="png1">
		><img src="png2.png" width="50" height="50" id="png2">
		><script>
		>	$$('#png1', '#png2').each(function(png) {fixPNG(png);});
		>	//OR
		>	fixPNG('png1');
		>	fixPNG('png2');
		></script>
*/

function fixPNG(myImage) 
{
	try {
		var arVersion = navigator.appVersion.split("MSIE");
		var version = parseFloat(arVersion[1]);
		if ((version >= 5.5) && (version < 7) && (document.body.filters)){
			myImage = $(myImage);
			var vis = myImage.isVisible();
			if(!vis) myImage.setStyle('display','block');
			var width = $(myImage).offsetWidth;
			var height = $(myImage).offsetHeight;
			if(!vis) myImage.hide();
			var imgID = (myImage.id) ? "id='" + myImage.id + "' " : "";
			var imgClass = (myImage.className) ? "class='" + myImage.className + "' " : "";
			var imgTitle = (myImage.title) ? "title='" + myImage.title	+ "' " : "title='" + myImage.alt + "' ";
			var imgStyle = "display:inline-block;" + myImage.style.cssText;
			var strNewHTML = "<span " + imgID + imgClass + imgTitle
									+ " style=\"" + "width:" + width 
									+ "px; height:" + height 
									+ "px;" + imgStyle + ";"
									+ "filter:progid:DXImageTransform.Microsoft.AlphaImageLoader"
									+ "(src=\'" + myImage.src + "\', sizingMethod='scale');\"></span>";
			myImage.outerHTML = strNewHTML;
		}
	} catch(e) {}
};
if(window.ie6) window.addEvent('domready', function(){$$('img.fixPNG').each(function(png){fixPNG(png)});});
/* do not edit below this line */   
/* Section: Change Log 

$Source: /cvs/main/flatfile/html/rb/js/global/cnet.global.framework/common/browser.fixes/fixpng.js,v $
$Log: fixpng.js,v $
Revision 1.5  2007/05/29 22:01:53  newtona
Split element.cnet.js into seperate files; updated docs in files to note this
Changed element.visible to element.isVisible (left old namespace for legacy support)
Fixed Element.empty in prototype.compatibility.js
Removed as many dependencies in common code to element.*.js as possible (espeically element.shortcuts.js)

Revision 1.4  2007/05/16 20:17:52  newtona
changing window.onDomReady to window.addEvent('domready'

Revision 1.3  2007/01/26 05:46:32  newtona
syntax update for mootools 1.0

Revision 1.2  2007/01/19 01:21:47  newtona
changed event.ondomready > window.ondomready

Revision 1.1  2007/01/09 02:39:35  newtona
renamed addons directory to "common" directory

Revision 1.3  2007/01/09 01:26:38  newtona
changed $S to $$

Revision 1.2  2006/11/02 21:26:42  newtona
checking in commerce release version of global framework.

notable changes here:
cnet.functions.js is the only file really modified, the rest are just getting cvs footers (again).

cnet.functions adds numerous new classes:

$type.isNumber
$type.isSet
$set

*//*
Script: IframeShim.js
Iframe shim class for hiding elements below a floating DOM element.

Dependancies:
	 mootools - <Moo.js>, <Utility.js>, <Common.js>, <String.js>, <Array.js>, <Function.js>, <Element.js>, <Dom.js>

Author:
	Aaron Newton, <aaron [dot] newton [at] cnet [dot] com>


Class: IframeShim
		There are two types of elements that (sometimes) prohibit you from 
		positioning a DOM element over them: some form elements and some
		flash elements. The two options you have are:
			- to hide these elements when your dom is going to be over them; 
			this works if you know your DOM element is going to completely
			obscure that element
			
			- an iframe shim - where you put an iframe below your element but
			ABOVE the form/flash element. more details here:
			http://www.macridesweb.com/oltest/IframeShim.html
			
		The IframeShim class handles a lot of the dirty work for you.
Arguments:
			element -  (required; DOM element or its id) the element you want to put this shim under
			display -  (boolean; optional) display the shim on instantiation; defaults to false
			name -  (string; optional) the id you want to give the new DOM element of the iframe shim; gets "_shim" added to it
			zindex -  (integer; optional) the index of the shim; optional, default is 1 less than the element
			margin -  (integer; optional) make the iframe smaller than the element to give a buffer (for 
							things like shadows)
			offset -  (object: {x:#, y:#}; optional) move the iframe up/down, left/right relative to 
							the element
			className - (string; optional) className for the shim; defaults to "iframeShim"
			browsers - (boolean; optional) allows you to specify the browsers that the iframe should show up for;
							defaults to ie6 or gecko on a mac (window.ie6 || (window.gecko && navigator.userAgent.test('mac', 'i'))). 
							Example usage: *browsers: window.ie6 || window.khtml* //will show for safari, konqueror, and ie6
		
		then, when you make your floating DOM show up you just execute .hide() or .show()
		to make the shim do its magic. You can also call .position() if the element the
		shim is supposed to be under happens to move.
		
		example:
		
		> <div id="myFloatingDiv">stuff</div>
		> <script>
		> 	var myFloatingDivShim = new IframeShim({
		> 		element: 'myFloatingDiv',
		> 		display: false,
		> 		name: 'myFloatingDivShimId'
		> 	});
		> 	function showMyFloatingDiv(){
		> 		$('myFloatingDiv').show();
		> 		myFloatingDivShim.show();
		> 	}
		> </script>
		
		See also <hide>, <show>, <position>
	*/
	
var IframeShim = new Class({
	options: {
		element: false,
		name: '',
		className:'iframeShim',
		display:false,
		name: '',
		zindex: false,
		margin: 0,
		offset: {
			x: 0,
			y: 0
		},
		browsers: (window.ie6 || (window.gecko && navigator.userAgent.test('mac', 'i')))
	},
	initialize: function (options){
		this.setOptions(options);
		//legacy
		if(this.options.offset && this.options.offset.top) this.options.offset.y = this.options.offset.top;
		if(this.options.offset && this.options.offset.left) this.options.offset.x = this.options.offset.left;
		this.element = $(this.options.element);
		if(!this.element) return;
		else this.makeShim();
		return;
	},
	makeShim: function(){
		this.shim = new Element('iframe');
		this.id = (this.options.name || new Date().getTime()) + "_shim";
		if(this.element.getStyle('z-Index').toInt()<1 || isNaN(this.element.getStyle('z-Index').toInt()))
			this.element.setStyle('z-Index',5);
		var z = this.element.getStyle('z-Index')-1;
		
		if($chk(this.options.zindex) && 
			 this.element.getStyle('z-Index').toInt() > this.options.zindex)
			 z = this.options.zindex;
			
 		this.shim.setStyles({
			'position': 'absolute',
			'zIndex': z,
			'border': 'none',
			'filter': 'progid:DXImageTransform.Microsoft.Alpha(style=0,opacity=0)'
		}).setProperties({
			'src':'javascript:void(0);',
			'frameborder':'0',
			'scrolling':'no',
			'id':this.id
		}).addClass(this.options.className);
		
		var inject = function(){
			this.shim.injectInside(document.body);
			if(this.options.display) this.show();
			else this.hide();
		};
		if(this.options.browsers){
			if(window.ie && !IframeShim.ready) {
				window.addEvent('onload', inject.bind(this));
			} else {
				inject.bind(this)();
			}
		}
	},

/*	
		Property: position
		This will reposition the iframe element. Call this when you move or resize
		the iframe element.
	*/
	position: function(shim){
		if(!this.options.browsers) return;
		var wasVis = this.element.getStyle('display')!='none';
		if(!wasVis) this.element.setStyle('display','block');
		var size = this.element.getSize().size;
		var pos = this.element.getPosition();
		if(! wasVis) this.element.setStyle('display','none');
		if($type(this.options.margin)){
			size.x = size.x-(this.options.margin*2);
			size.y = size.y-(this.options.margin*2);
			this.options.offset.x += this.options.margin; 
			this.options.offset.y += this.options.margin;
		}
		//offset.x+=100;// ******* This is my change ********
 		this.shim.setStyles({
			'width': size.x + 'px',
			'height': size.y + 'px'
		}).setPosition({
			relativeTo: this.element,
			offset: this.options.offset
		});
	},
/*	
		Property: hide
		This will hide the IframeShim object. If you don't call this when you
		hide the element that's over the flash or select list, then that thing
		will still be hidden.
	*/
	hide: function(){
		if(!this.options.browsers) return;
		this.shim.setStyle('display','none');
	},

/*	
		Property: show
		This will obscure any form elements or flash elements below the iframe
		shim element. Call this when you show your floating element.
	*/
	show: function(){
		if(!this.options.browsers) return;
		this.shim.setStyle('display','block');
		this.position();
	},
/*	
		Property: remove
		This will remove the iframe from the DOM.
	*/
	remove: function(){
		if(!this.options.browsers) return;
		this.shim.remove();
	}
});
IframeShim.implement(new Options);
//legacy namespace
var iframeShim = IframeShim;
window.addEvent('load', function(){
	IframeShim.ready = true;
});
/* do not edit below this line */   
/* Section: Change Log 

$Source: /cvs/main/flatfile/html/rb/js/global/cnet.global.framework/common/browser.fixes/IframeShim.js,v $
$Log: IframeShim.js,v $
Revision 1.19  2007/05/03 18:24:24  newtona
iframeshim: removed a dbug line
modalizer: only hide select lists for browsers that need it
product picker: added a try/catch, updated cnet api link/code

Revision 1.18  2007/04/12 19:21:11  newtona
iframeshim now defaults to only activate for ie6 AND Firefox on a mac

Revision 1.17  2007/04/12 17:09:32  newtona
*** empty log message ***

Revision 1.16  2007/04/12 17:03:28  newtona
iframeshim now defaults to only activate for ie6

Revision 1.15  2007/04/11 23:11:51  newtona
because IframeShim appends the iframe to the document.body, in IE iframe now waits for window.onload

Revision 1.14  2007/04/09 19:04:18  newtona
fixed a binding problem

Revision 1.13  2007/04/05 00:13:12  newtona
local.vars.js: removing $type.isNumber dependency
login.status.js: no change; fixed typo in docs
search.functions.js: removing $type.isNumber dependency
stickyWinDefaultLayout: infinite buttons!
iframeShim.js: fixed an ie bug that caused it to abort the page

Revision 1.12  2007/03/26 18:30:10  newtona
iframeShim: fixed reference to options (should be this.options)
element.cnet: removed some dbug lines

Revision 1.11  2007/03/23 21:18:42  newtona
fixed reference to options (should be this.options)

Revision 1.10  2007/03/23 20:19:48  newtona
Iframeshim: added className; updated docs
StickyWin: added edge support (see Element.setPosition)

Revision 1.9  2007/03/23 17:17:37  newtona
iframe in iframeshim now gets it's id set (again)

Revision 1.8  2007/03/20 21:02:51  newtona
docs update

Revision 1.7  2007/03/20 19:22:50  newtona
continued refactoring; fixing some IE inconsistencies.

Revision 1.6  2007/03/16 05:24:36  newtona
refactored and cleaned up.

Revision 1.5  2007/02/23 20:02:51  newtona
adjusted z-index logic so that the iframe is always a positive number

Revision 1.4  2007/02/23 18:47:29  newtona
iframe target now gets $() around it.

Revision 1.3  2007/02/07 20:49:21  newtona
implemented Options class

Revision 1.2  2007/01/22 21:09:24  newtona
updated docs for namespace change (IframeShim.js)

Revision 1.1  2007/01/22 21:08:30  newtona
renamed from iframeshim.js

Revision 1.3  2007/01/22 19:54:59  newtona
removed browser.sniffer - this stuff is in mootools 1.0
renamed iframeShim to IframeShim

Revision 1.2  2007/01/11 20:45:49  newtona
fixed syntax error with setProperties

Revision 1.1  2007/01/09 02:39:35  newtona
renamed addons directory to "common" directory

Revision 1.3  2007/01/05 19:30:37  newtona
removed any dependencies on cnet libraries; now only depends on mootools.

Revision 1.2  2006/11/02 21:26:42  newtona
checking in commerce release version of global framework.

notable changes here:
cnet.functions.js is the only file really modified, the rest are just getting cvs footers (again).

cnet.functions adds numerous new classes:

$type.isNumber
$type.isSet
$set

*//*	
	Script: form.validator.js
	A css-class based form validation system.
	
	Dependencies:
	Mootools - <Moo.js>, <Utility.js>, <Common.js>, <Element.js>, <Function.js>, <Event.js>, <String.js>, <Fx.Base.js>, 
			<Window.Base.js>, <Fx.Style.js>, <Fx.Styles.js>, <Dom.js>
			
	CNET - optional: <Fx.SmoothShow.js>
			
	Authors:
		Aaron Newton, <aaron [dot] newton [at] cnet [dot] com>
		Based on validation.js by Andrew Tetlaw (http://tetlaw.id.au/view/blog/really-easy-field-validation-with-prototype)

	Class: InputValidator
	This class contains functionality to test a field for various criteria and also to generate 
	an error message when that test fails.
	
	Arguments:
	className - a className that this field will be related to (see example below);
	options - an object with name/value pairs.
	
	Options:
	errorMsg - a message to display; see section below for details.
	test - a function that returns true or false
	
	errorMsg:
	The errorMsg option can be any of the following
	
		string - the message to display if the field fails validation
		boolean false - do not display a message at all
		function - a function to evaluate that returns either a string or false.
			This function will be passed two parameters: the field being evaluated and
			any properties defined for the validator as a className (see examples below)
	
	test:
	The test option is a function that will be passed the field being evaluated and
	any properties defined for the validator as a className (see example below); this
	function must return true or false.

	Examples:
(start code)
//html code
<input type="text" name="firstName" class="required" id="firstName">
//simple validator
var isEmpty = new InputValidator('required', {
	errorMsg: 'This field is required.',
	test: function(field){
		return ((element.getValue() == null) || (element.getValue().length == 0));
	}
});
isEmpty.test($("firstName")); //true if empty
isEmpty.getError($("firstName")) //returns "This field is required."

//two complex validators
<input type="text" name="username" class="minLength maxLength" validatorProps="{minLength:10, maxLength:100}" id="username">

var minLength = new InputValidator ('minLength', {
	errorMsg: function(element, props){
		//props is {minLength:10, maxLength:100}
		if($type(props.minLength))
			return 'Please enter at least ' + props.minLength + ' characters (you entered ' + element.value.length + ' characters).';
		else return '';
	}, 
	test: function(element, props) {
		//if the value is >= than the minLength value, element passes test
		return (element.value.length >= $pick(props.minLength, 0));
		else return false;
	}
});

minLength.test($('username'));

var maxLength = new InputValidator ('maxLength', {
	errorMsg: function(element, props){
		//props is {minLength:10, maxLength:100}
		if($type(props.maxLength))
			return 'Please enter no more than ' + props.maxLength + ' characters (you entered ' + element.value.length + ' characters).';
		else return '';
	}, 
	test: function(element, props) {
		//if the value is <= than the maxLength value, element passes test
		return (element.value.length <= $pick(props.maxLength, 10000));
		else return false;
	}
});(end)
	*/

var InputValidator = new Class({
	initialize: function(className, options){
		this.setOptions({
			errorMsg: 'Validation failed.',
			test: function(field){return true}
		}, options);
		this.className = className;
	},
/*	Property: test
		Tests a field against the validator's rule(s).
		
		Arguments:
		field - the form input to test
		
		Returns:
		true - the field passes the test
		false - it does not pass the test
	*/
	test: function(field){
		if($(field)) return this.options.test($(field), this.getProps(field));
		else return false;
	},
/*	Property: getError
		Retrieves the error message for the validator.
		
		Arguments:
		field - the form input to test
		
		Returns:
		The error message or the boolean false if no message is meant to be returned.
	*/
	getError: function(field){
		var err = this.options.errorMsg;
		if($type(err) == "function") err = err($(field), this.getProps(field));
		return err;
	},
	getProps: function(field){
		if($(field) && $(field).getProperty('validatorProps')){
			try {
				return Json.evaluate($(field).getProperty('validatorProps'));
			}catch(e){ return {}}
		} else {
			return {}
		}
	}
});
InputValidator.implement(new Options);


/*	Class: FormValidator
		Evalutes an entire form against all the validators that are set up, displaying messages
		and returning a true/false response for the evaluation of the entire form.
		
		An instance of the FormValidator class will test each field and then behave according to
		the options passed in.
		
		Arguments:
		form - the form to evaluate
		options - an object with name/value pairs
		
		Options:
		fieldSelectors - the selector for fields to include in the validation;
				defaults to: "input, select, textarea"
		useTitles - use the titles of inputs for the error message; overrides
				the messages defined in the InputValidators (see <InputValidator>); defaults to false
		evaluateOnSubmit - validate the form when the user submits it; defaults to true
		evaluateFieldsOnBlur - validate the fields when the blur event fires; defaults to true
		evaluateFieldsOnChange - validate the fields when the change event fires; defaults to true
		serial - (boolean) if one field fails validation, do not validate other fields unless 
					their contents actually change (instead of on blur); defaults to true
		warningPrefix - (string) prefix to be added to every warning; defaults to "Warning: "
		errorPrefix - (string) prefix to be added to every error; defaults to "Error: "
		onFormValidate - function to execute when the form validation completes; this function
			is passed two arguments: a boolean (true if the form passed validation) and the form element
		onElementValidate - function to execute when an input element is tested; this function
			is passed two arguments: a boolean (true if the form passed validation) and the input element
		
		Example:
(start code)var myFormValidator = new FormValidator($('myForm'), {
	onFormValidate: myFormHandler,
	useTitles: true
});(end)

		Note: FormValidator must be configured with <Validator> objects; see below for details as well as a list of built-in validators. Each <Validator> will be applied to any input that matches its className within the elements of the form that match the fieldSelectors option.

		Using Warnings:
		Each <Validator> can also be used to generate warnings. Warnings still show error messages, but do not prevent the form from being submitted. Warnings can be applied in two ways.
		warn per validator - You can specify any validator as a warning by prefixing "warn-" to the class name. So, for example, if you have a validator called "validate-numbers" you can add the class "warn-validate-numbers" and a warning will be offered rather than an error. The validator will not prevent the form from submitting.
		warn per field - You can also ignore all the validators for a given field. You can add the class "warnOnly" to set all it's validators to present warnings only or you can add the class "ignoreValidation" to the field to turn all the validators off. Note that the FormValidator class has methods do this for you: see <FormValidator.ignoreField> and <FormValidator.enforceField>.
	*/
var FormValidator = new Class({
	options: {
		fieldSelectors:"input, select, textarea",
		useTitles:false,
		evaluateOnSubmit:true,
		evaluateFieldsOnBlur: true,
		evaluateFieldsOnChange: true,
		serial: true,
		warningPrefix: "Warning: ",
		errorPrefix: "Error: ",
		onFormValidate: function(isValid, form){},
		onElementValidate: function(isValid, field){}
	},
	initialize: function(form, options){
		this.setOptions(options);
		try {
			this.form = $(form);
			if(this.options.evaluateOnSubmit) this.form.addEvent('submit', this.onSubmit.bind(this));
			if(this.options.evaluateFieldsOnBlur) this.watchFields();
		}catch(e){//console.log('error: %s', e);
		}
	},
	getFields: function(){
		return this.fields = this.form.getElementsBySelector(this.options.fieldSelectors)
	},
	watchFields: function(){
		try{
			this.getFields().each(function(el){
					el.addEvent('blur', this.validateField.pass([el, false], this));
				if(this.options.evaluateFieldsOnChange)
					el.addEvent('change', this.validateField.pass([el, true], this));
			}, this);
		}catch(e){//console.log('error: %s', e);
		}
	},
	onSubmit: function(event){
		if(!this.validate()) new Event(event).stop();
		else {
			this.stop();
			this.reset();
		}
	},
/*	Property: reset
		Removes all the error messages from the form.
	*/
	reset: function() {
		this.getFields().each(this.resetField, this);
	}, 
/*	Property: validate
		Validates all the inputs in the form; note that this function is called on submit unless
		you specify otherwise in the options.
	*/
	validate : function() {
		var result = this.getFields().map(function(field) { return this.validateField(field, true); }, this);
		result = result.every(function(val){
			return val;
		});
		this.fireEvent('onFormValidate', [result, this.form]);
		return result;
	},
/*	Property: validateField
		Validates the value of a field against all the validators.
		
		Arguments:
		field - the input element to evaluate
		force - (boolean; optional) if false (or undefined) and options.serial==true, the validation does not occur
	*/
	validateField: function(field, force){
		if(this.paused) return true;
		field = $(field);
		var result = true;
		var failed = this.form.getElement('.validation-failed');
		var warned = this.form.getElement('.warning');
		//if the field is defined
		//if there aren't any failed
		//or if there are failed and it's not serial
		//or force
		//then validate
		if(field && (!failed || force || (failed && !this.options.serial))){
			var validators = field.className.split(" ").some(function(cn){
				return this.getValidator(cn);
			}, this);
			result = field.className.split(" ").map(function(className){
				return this.test(className,field);
			}, this);
			result = result.every(function(val){
				return val;
			});
			if (validators && !field.hasClass('warnOnly')){
				if(result) field.addClass('validation-passed').removeClass('validation-failed');
				else field.addClass('validation-failed').removeClass('validation-passed');
			}
			if(!warned || force || (warned && !this.options.serial)) {
				var warnings = field.className.split(" ").some(function(cn){
					if(cn.test('^warn-') || field.hasClass('warnOnly')) return this.getValidator(cn.replace(/^warn-/,""));
				}, this);
				field.removeClass('warning');
				var warnResult = field.className.split(" ").map(function(cn){
					if(cn.test('^warn-') || field.hasClass('warnOnly')) return this.test(cn.replace(/^warn-/,""), field, true);
				}, this);
			}
		}
		return result;
	},
	getPropName: function(className){
		return '__advice'+className;
	},
/*	Property: test
		Tests a field against a specific validator.
		
		Arguments:
		className - the className associated with the validator
		field - the input element
		warn - (boolean; optional) if set to true, test will add a warning advice message if 
				the validator fails, but will always return valid regardless of the input.
	*/
	test: function(className, field, warn){
		if(field.hasClass('ignoreValidation')) return true;
		warn = $pick(warn, false);
		if(field.hasClass('warnOnly')) warn = true;
		field = $(field);
		var isValid = true;
		if(field) {
			var validator = this.getValidator(className);
			if(validator && this.isVisible(field)) {
				isValid = validator.test(field);
				//if the element is visible and it failes to validate
				if(!isValid && validator.getError(field)){
					if(warn) field.addClass('warning');
					var advice = this.makeAdvice(className, field, validator.getError(field), warn);
					this.insertAdvice(advice, field);
					this.showAdvice(className, field);
				} else this.hideAdvice(className, field);
				this.fireEvent('onElementValidate', [isValid, field]);
			}
		}
		if(warn) return true;
		return isValid;
	},
	showAdvice: function(className, field){
		var advice = this.getAdvice(className, field);
		if(advice && !field[this.getPropName(className)] && (advice.getStyle('display') == "none" || advice.getStyle('visiblity') == "hidden" || advice.getStyle('opacity')==0)){
			field[this.getPropName(className)] = true;
			//if element.cnet.js is present, transition the advice in
			if(advice.smoothShow) advice.smoothShow();
			else advice.setStyle('display','block');
		}
	},
	hideAdvice: function(className, field){
		var advice = this.getAdvice(className, field);
		if(advice && field[this.getPropName(className)]) {
			field[this.getPropName(className)] = false;
			//if element.cnet.js is present, transition the advice out
			if(advice.smoothHide) advice.smoothHide();
			else advice.setStyle('display','none');
		}
	},
	isVisible : function(field) {
		while(field.tagName != 'BODY') {
			if($(field).getStyle('display') == "none") return false;
			field = field.parentNode;
		}
		return true;
	},
	getAdvice: function(className, field) {
		return $('advice-' + className + '-' + this.getFieldId(field))
	},
	makeAdvice: function(className, field, error, warn){
		var errorMsg = (warn)?this.options.warningPrefix:this.options.errorPrefix;
				errorMsg += (this.options.useTitles) ? $pick(field.title, error):error;
		var advice = this.getAdvice(className, field);
		if(!advice){
			var cssClass = (warn)?'warning-advice':'validation-advice';
			advice = new Element('div').addClass(cssClass).setProperty(
				'id','advice-'+className+'-'+this.getFieldId(field)).setStyle('display','none').appendText(errorMsg);
		} else{
			advice.setHTML(errorMsg);
		}
		return advice;
	},
	insertAdvice: function(advice, field){
		switch (field.type.toLowerCase()) {
			case 'radio':
				var p = $(field.parentNode);
				if(p) {
					p.adopt(advice);
					break;
				}
			default: advice.injectAfter($(field));
	  };
	},
	getFieldId : function(field) {
		return field.id ? field.id : field.id = "input_"+field.name;
	},
/*	Property: resetField
		Removes all the error messages for a specific field.
		
		Arguments:
		field - the field to reset.
	*/
	resetField: function(field) {
		field = $(field);
		if(field) {
			var cn = field.className.split(" ");
			cn.each(function(className) {
				if(className.test('^warn-')) className = className.replace(/^warn-/,"");
				var prop = this.getPropName(className);
				if(field[prop]) this.hideAdvice(className, field);
				field.removeClass('validation-failed');
				field.removeClass('warning');
				field.removeClass('validation-passed');
			}, this);
		}
	},
/*	Property: stop
		Stops validating the form; form will submit even if there are values that do not pass validation;
	*/
	stop: function(){
		this.paused = true;
	},
/*	Property: start
		Resumes validating the form.
	*/
	start: function(){
		this.paused = false;
	},
/*	Property: ignoreField
		Stops validating a particular field.
		
		Arguments:
		field - the field to ignore
		warn - (boolean, optional) don't require the validator to pass, but do produce a warning.
	*/
	ignoreField: function(field, warn){
		if(field = $(field)){
			this.enforceField(field);
			if(warn) field.addClass('warnOnly');
			else field.addClass('ignoreValidation');
		}
	},
/*	Property: enforceField
		Resumes validating a particular field
		
		Arguments:
		field - the field to resume validating
	*/
	enforceField: function(field){
		if(field = $(field)){
			field.removeClass('warnOnly');
			field.removeClass('ignoreValidation');
		}
	}
});
FormValidator.implement(new Options);
FormValidator.implement(new Events);

FormValidator.adders = {
/*	Property: validators
		An array of <Validator> objects.
	*/
	validators:{},
/*	Property: add
		Adds a new form validator to the FormValidator object. 
		
		Arguments:
		className - the className associated with the validator
		options - the <Validator> options (errorMsg and test)


		Note:
		This method is a property of every instance of FormValidator as well as the 
		FormValidator object itself. That is to say that you can add validators to
		the FormValidator object or to an instance of it. Adding validators to an instance
		of FormValidator will make those validators apply only to that instance, while
		adding them to the Class will make them available to all instances.
		
		Examples:
(start code)
//add a validator for ALL instances
FormValidator.add('isEmpty', {
	errorMsg: 'This field is required',
	test: function(element){
		if(element.value.length ==0) return false;
		else return true;
	}
});

//this validator is only available to this single instance
var myFormValidatorInstance = new FormValidator('myform');
myFormValidatorInstance.add('doesNotContainTheLetterQ', {
	errorMsg: 'This field cannot contain the letter Q!',
	test: function(element){
		return !element.getValue().test('q','i');
	}
});

//Extend FormValidator, add a global validator for all instances of that version
var NewFormValidator = FormValidator.extend({
	//...some code
});
NewFormValidator.add('doesNotContainTheLetterZ', {
	errorMsg: 'This field cannot contain the letter Z!',
	test: function(element){
		return !element.getValue().test('z','i');
	}
});
(end)

	*/
	add : function(className, options) {
		this.validators[className] = new InputValidator(className, options);
		//if this is a class
		//extend these validators into it
		if(!this.initialize){
			this.implement({
				validators: this.validators
			});
		}
	},
/*	Property: addAllThese
		An array of InputValidator configurations (see <FormValidator.add> above).
		
		Example:
(start code)
FormValidator.addAllThese([
	['className1', {errorMsg: ..., test: ...}],
	['className2', {errorMsg: ..., test: ...}],
	['className3', {errorMsg: ..., test: ...}],
]);
(end)
	*/
	addAllThese : function(validators) {
		$A(validators).each(function(validator) {
			this.add(validator[0], validator[1]);
		}, this);
	},
	getValidator: function(className){
		return this.validators[className];
	}
};
Object.extend(FormValidator, FormValidator.adders);
FormValidator.implement(FormValidator.adders);

/*	Section: Included InputValidators
		Here are the validators that are included in this libary. Add the className to
		any input and then create a new <FormValidator> and these will automatically be
		applied. See <FormValidator.add> on how to add your own.

		Property: IsEmpty
		Evalutes if the input is empty; this is a utility validator, see <FormValidator.required>.
		
		Error Msg - returns false (no message)
			*/
FormValidator.add('IsEmpty', {
	errorMsg: false,
	test: function(element) { 
		if(element.type == "select-one"||element.type == "select")
			return !(element.selectedIndex >= 0 && element.options[element.selectedIndex].value != "");
		else
			return ((element.getValue() == null) || (element.getValue().length == 0));
	}
});


FormValidator.addAllThese([
/*	Property: required
		Displays an error if the field is empty.
		
		Error Msg - "This field is required"			
	*/
	['required', {
		errorMsg: function(element){return 'This field is required.'}, 
		test: function(element) { 
			return !FormValidator.getValidator('IsEmpty').test(element); 
		}
	}],
/*	Property: minLength
		Displays a message if the input value is less than the supplied length.
		
		Error Msg - Please enter at least [defined minLength] characters (you entered [input length] characters)
		
		Note:
		You must add this className AND properties for it to your input.
	
		Example:
		><input type="text" name="username" class="minLength props{minLength:10}" id="username">
	*/
	['minLength', {
		errorMsg: function(element, props){
			if($type(props.minLength))
				return 'Please enter at least ' + props.minLength + ' characters (you entered ' + element.getValue().length + ' characters).';
			else return '';
		}, 
		test: function(element, props) {
			if($type(props.minLength)) return (element.getValue().length >= $pick(props.minLength, 0));
			else return true;
		}
	}],
/*	Property: maxLength
		Displays a message if the input value is less than the supplied length.
		
		Error Msg - Please enter no more than [defined maxLength] characters (you entered [input length] characters)
		
		Note:
		You must add this className AND properties for it to your input.
		
		Example:
		><input type="text" name="username" class="maxLength props{maxLength:100}" id="username">
	*/
	['maxLength', {
		errorMsg: function(element, props){
			//props is {maxLength:10}
			if($type(props.maxLength))
				return 'Please enter no more than ' + props.maxLength + ' characters (you entered ' + element.getValue().length + ' characters).';
			else return '';
		}, 
		test: function(element, props) {
			//if the value is <= than the maxLength value, element passes test
			return (element.getValue().length <= $pick(props.maxLength, 10000));
		}
	}],
/*	Property: validate-number
		Validates that the entry is a number.
		
		Error Msg - 'Please enter a valid number in this field.'
	*/	
	['validate-number', {
		errorMsg: 'Please enter a valid number in this field.',
		test: function(element) {
				return FormValidator.getValidator('IsEmpty').test(element) || !/[^\d+$]/.test(element.getValue());
		}
	}],
/*	Property: validate-digits
		Validates that the entry contains only numbers

		Error Msg - 'Please use numbers only in this field. Please avoid spaces or other characters such as dots or commas.'
	*/
	['validate-digits', {
		errorMsg: 'Please use numbers only in this field. Please avoid spaces or other characters such as dots or commas.', 
		test: function(element) {
			return FormValidator.getValidator('IsEmpty').test(element) || 
				(!/[^a-zA-Z]/.test(element.getValue()) && /[\d]/.test(element.getValue()));
		}
	}],
/*	Property: validate-alpha
		Validates that the entry contains only letters 

		Error Msg - 'Please use letters only (a-z) in this field.'
	*/
	['validate-alpha', {
		errorMsg: 'Please use letters only (a-z) in this field.', 
		test: function (element) {
			return FormValidator.getValidator('IsEmpty').test(element) ||  /^[a-zA-Z]+$/.test(element.getValue())
		}
	}],
/*	Property: validate-alphanum
		Validates that the entry is letters and numbers only

		Error Msg - 'Please use only letters (a-z) or numbers (0-9) only in this field. No spaces or other characters are allowed.'
	*/
	['validate-alphanum', {
		errorMsg: 'Please use only letters (a-z) or numbers (0-9) only in this field. No spaces or other characters are allowed.', 
		test: function(element) {
			return FormValidator.getValidator('IsEmpty').test(element) || !/\W/.test(element.getValue())
		}
	}],
/*	Property: validate-date
		Validates that the entry parses to a date.

		Error Msg - 'Please use this date format: mm/dd/yyyy. For example 03/17/2006 for the 17th of March, 2006.'
	*/
	['validate-date', {
		errorMsg: 'Please use this date format: mm/dd/yyyy. For example 03/17/2006 for the 17th of March, 2006.',
		test: function(element) {
			if(FormValidator.getValidator('IsEmpty').test(element)) return true;
	    var regex = /^(\d{2})\/(\d{2})\/(\d{4})$/;
	    if(!regex.test(element.getValue())) return false;
	    var d = new Date(element.getValue().replace(regex, '$1/$2/$3'));
	    return (parseInt(RegExp.$1, 10) == (1+d.getMonth())) && 
        (parseInt(RegExp.$2, 10) == d.getDate()) && 
        (parseInt(RegExp.$3, 10) == d.getFullYear() );
		}
	}],
/*	Property: validate-email
		Validates that the entry is a valid email address.

		Error Msg - 'Please enter a valid email address. For example fred@domain.com .'
	*/
	['validate-email', {
		errorMsg: 'Please enter a valid email address. For example fred@domain.com .', 
		test: function (element) {
			return FormValidator.getValidator('IsEmpty').test(element) || /\w{1,}[@][\w\-]{1,}([.]([\w\-]{1,})){1,3}$/.test(element.getValue());
		}
	}],
/*	Property: validate-url
		Validates that the entry is a valid url

		Error Msg - 'Please enter a valid URL.'
	*/
	['validate-url', {
		errorMsg: 'Please enter a valid URL.', 
		test: function (element) {
			return FormValidator.getValidator('IsEmpty').test(element) || /^(http|https|ftp):\/\/(([A-Z0-9][A-Z0-9_-]*)(\.[A-Z0-9][A-Z0-9_-]*)+)(:(\d+))?\/?/i.test(element.getValue());
		}
	}],
/*	Property: validate-date-au
		Validates that the entry matches dd/mm/yyyy.

		Error Msg - 'Please use this date format: dd/mm/yyyy. For example 17/03/2006 for the 17th of March, 2006.'
	*/
	

	['validate-date-au', {
		errorMsg: 'Please use this date format: dd/mm/yyyy. For example 17/03/2006 for the 17th of March, 2006.',
		test: function(element) {
			if(FormValidator.getValidator('IsEmpty').test(element)) return true;
	    var regex = /^(\d{2})\/(\d{2})\/(\d{4})$/;
	    if(!regex.test(element.getValue())) return false;
	    var d = new Date(element.getValue().replace(regex, '$2/$1/$3'));
	    return (parseInt(RegExp.$2, 10) == (1+d.getMonth())) && 
        (parseInt(RegExp.$1, 10) == d.getDate()) && 
        (parseInt(RegExp.$3, 10) == d.getFullYear() );
		}
	}],
/*	Property: validate-currency-dollar
		Validates that the entry matches any of the following:
			- [$]1[##][,###]+[.##]
			- [$]1###+[.##]
			- [$]0.##
			- [$].##
		
		Error Msg - 'Please enter a valid $ amount. For example $100.00 .'
	*/
	['validate-currency-dollar', {
		errorMsg: 'Please enter a valid $ amount. For example $100.00 .', 
		test: function(element) {
			// [$]1[##][,###]+[.##]
			// [$]1###+[.##]
			// [$]0.##
			// [$].##
			return FormValidator.getValidator('IsEmpty').test(element) ||  /^\$?\-?([1-9]{1}[0-9]{0,2}(\,[0-9]{3})*(\.[0-9]{0,2})?|[1-9]{1}\d*(\.[0-9]{0,2})?|0(\.[0-9]{0,2})?|(\.[0-9]{1,2})?)$/.test(element.getValue());
		}
	}],
/*	Property: validate-one-required
		Validates that all the entries within the same node are not empty.

		Error Msg - 'Please enter something for at least one of the above options.'
		
		Note:
		This validator will get the parent element for the input and then check all its children.
		To use this validator, enclose all the inputs you want to group in another element (doesn't
		matter which); you only need apply this class to *one* of the elements.
		
		Example:
(start code)
<div>
	<input ....>
	<input ....>
	<input .... className="validate-one-required">
</div>(end)
	*/
	['validate-one-required', {
		errorMsg: 'Please enter something for at least one of the above options.', 
		test: function (element) {
			var p = element.parentNode;
			var options = p.getElements('input');
			return $A(options).some(function(el) {
				return el.getValue();
			});
		}
	}]
]);

/* do not edit below this line */   
/* Section: Change Log 

$Source: /cvs/main/flatfile/html/rb/js/global/cnet.global.framework/common/js.widgets/form.validator.js,v $
$Log: form.validator.js,v $
Revision 1.15  2007/06/02 01:35:56  newtona
*** empty log message ***

Revision 1.14  2007/05/29 22:01:53  newtona
Split element.cnet.js into seperate files; updated docs in files to note this
Changed element.visible to element.isVisible (left old namespace for legacy support)
Fixed Element.empty in prototype.compatibility.js
Removed as many dependencies in common code to element.*.js as possible (espeically element.shortcuts.js)

Revision 1.13  2007/04/13 00:22:57  newtona
fixed a typo in FormValidator.hideAdvice (display: none instead of display: block)

Revision 1.12  2007/04/06 00:43:51  newtona
slight syntax update

Revision 1.11  2007/04/06 00:37:40  newtona
tweaked the way serial works

Revision 1.10  2007/04/05 23:48:55  newtona
FormValidator now has numerous new features: instance-level validators, .stop, .start, .ignoreField, .enforceField, and warnings

Revision 1.9  2007/04/05 23:01:26  newtona
FormValidator now has numerous new features: instance-level validators, .stop, .start, .ignoreField, .enforceField, and warnings

Revision 1.8  2007/03/02 00:28:37  newtona
advice is now inserted into the DOM in it's own method so it can be easily overriden
makeAdvice no longer inserts the advice.

Revision 1.7  2007/02/22 18:18:42  newtona
typo in the docs

Revision 1.6  2007/02/07 20:51:41  newtona
implemented Options class
implemented Events class
StickyWin now uses Element.position

Revision 1.5  2007/02/06 18:10:36  newtona
updated the error displays to use the new element.smoothshow function

Revision 1.4  2007/02/03 01:36:17  newtona
added multi-select support
shortened validate-number
updated validate-date essage and fixed a bug in it

Revision 1.3  2007/01/26 05:48:03  newtona
docs update

Revision 1.2  2007/01/22 22:00:15  newtona
numerous bug fixes to modalizer, stickywin, and popupdetails
updated for mootools 1.0
fixed date validation in form.validator

Revision 1.1  2007/01/19 01:22:05  newtona
*** empty log message ***


*/
/*	Script: modalizer.js
		Provides functionality to overlay the window contents with a semi-transparent layer that prevents interaction with page content until it is removed.
		
		Dependencies:
		Mootools - <Moo.js>, <Array.js>, <String.js>, <Function.js>, <Utility.js>, <Dom.js>, <Element.js>, <Window.Size.js>, <Event.js>, <Window.Base.js>
		
		Author:
		Aaron Newton (aaron [dot] newton [at] cnet [dot] com)

		Class: Modalizer
		Provides functionality to overlay the window contents with a semi-transparent layer that prevents interaction with page content until it is removed. This class is intended to be implemented into other classes to provide them access to this functionality.
	*/
var Modalizer = new Class({
	defaultModalStyle: {
		'display':'block',
		'position':'fixed',
		'top':'0px',
		'left':'0px',	
		'z-index':5000,
		'background-color':'#333',
		'opacity':.8
	},
/*	Property: setOptions
		Sets the options for the modal overlay.
		
		Arguments:
		options - an object with name/value definitions
		
		See <modalShow> for options list.
	*/
	setModalOptions: function(options){
		this.modalOptions = $merge({
			'width':(window.getScrollWidth()+300)+'px',
			'height':(window.getScrollHeight()+300)+'px',
			elementsToHide: 'select',
			onModalHide: Class.empty,
			onModalShow: Class.empty,
			hideOnClick: true,
			modalStyle: {}
		}, this.modalOptions, options || {});
	},
/*	Property: setModalStyle
		Sets the style of the modal overlay to those in the object passed in.
		
		Arguments:
		styleObject - object with key/value css properties
		
		Default styleObject:
(start code){
	'display':'block',
	'position':'fixed',
	'top':'0px',
	'left':'0px',	
	'width':'100%',
	'height':'100%',
	'z-index':this.modalOptions.zIndex,
	'background-color':this.modalOptions.color,
	'opacity':this.modalOptions.opacity
}(end)

	The object you pass in can contain any portion of this object, and the options you specify will overwrite the defaults; any option you do not specify will remain.		
	*/
	setModalStyle: function (styleObject){
		this.modalOptions.modalStyle = styleObject;
		this.modalStyle = $merge(this.defaultModalStyle, {
			'width':this.modalOptions.width,
			'height':this.modalOptions.height
		}, styleObject);
		if($('modalOverlay'))$('modalOverlay').setStyles(this.modalStyle);
		return(this.modalStyle);
	},
/*	Property: modalShow
		Shows the modal window.
		
		Arguments:
		options - key/value options object
		
		Options:
		elementsToHide - comma seperated string of selectors to hide when the overlay is applied;
			example: 'select, input, img.someClass'; defaults to 'select'
		modalHide - the funciton that hides the modal window; defaults to 
			"function(){if($('modalOverlay'))$('modalOverlay').hide();}"
		modalShow - the function that shows the modal window; defaults to
			"function(){$('modalOverlay').setStyle('display','block');}"
		onModalHide - function to execute when the modal window is removed
		onModalShow - function to execute when the modal window appears
		hideOnClick - allow the user to click anywhere on the modal layer to close it; defaults to true.
		modalStyle - a css style object to apply to the modal overlay. See <setModalStyle>.
	*/
	modalShow: function(options){
		this.setModalOptions(options||{});
		var overlay = null;
		if($('modalOverlay')) overlay = $('modalOverlay');
		if(!overlay) overlay = new Element('div').setProperty('id','modalOverlay').injectInside(document.body);
		overlay.setStyles(this.setModalStyle(this.modalOptions.modalStyle));
		if(window.ie6) overlay.setStyle('position','absolute');
		$('modalOverlay').removeEvents('click').addEvent('click', function(){
			this.modalHide(this.modalOptions.hideOnClick);
		}.bind(this));
		this.modalOptions.onModalShow();
		this.togglePopThroughElements(0);
		overlay.setStyle('display','block');
		return this;
	},
/*	Property: modalHide
		Hides the modal layer.
		
	*/
	modalHide: function(override){
		if(override === false) return; //this is internal, you don't need to pass in an argument
		this.togglePopThroughElements(1);
		this.modalOptions.onModalHide();
		if($('modalOverlay'))$('modalOverlay').setStyle('display','none');
		return this;
	},
	togglePopThroughElements: function(opacity){
		if((window.ie6 || (window.gecko && navigator.userAgent.test('mac', 'i')))) {
			$$(this.modalOptions.elementsToHide).each(function(sel){
				sel.setStyle('opacity', opacity);
			});
		}
	}
});
//legacy namespace
var modalizer = Modalizer;
/* do not edit below this line */   
/* Section: Change Log 

$Source: /cvs/main/flatfile/html/rb/js/global/cnet.global.framework/common/js.widgets/modalizer.js,v $
$Log: modalizer.js,v $
Revision 1.17  2007/06/29 21:54:27  newtona
fixed a bug with the hideOnClick option

Revision 1.16  2007/05/29 22:01:53  newtona
Split element.cnet.js into seperate files; updated docs in files to note this
Changed element.visible to element.isVisible (left old namespace for legacy support)
Fixed Element.empty in prototype.compatibility.js
Removed as many dependencies in common code to element.*.js as possible (espeically element.shortcuts.js)

Revision 1.15  2007/05/03 18:24:24  newtona
iframeshim: removed a dbug line
modalizer: only hide select lists for browsers that need it
product picker: added a try/catch, updated cnet api link/code

Revision 1.14  2007/04/13 19:06:11  newtona
dependency update in the docs

Revision 1.13  2007/03/27 16:05:40  newtona
fixed bug where select elements were not being hidden on modal show

Revision 1.12  2007/03/20 20:59:54  newtona
fixed a problem where the modal stickywin didn't close when the modal layer was clicked.

Revision 1.11  2007/03/19 17:34:38  newtona
fixed a bug; modal overlay width/height wasn't being set.

Revision 1.10  2007/03/05 23:33:38  newtona
moved width declarations to setModalOptions function; fixed a bug in opera

Revision 1.9  2007/03/05 19:36:00  newtona
now setModalOptions is always called, even if options are not supplied (so the defaults will be used)

Revision 1.8  2007/02/27 21:46:43  newtona
docs update; fixing references

Revision 1.7  2007/02/21 00:27:28  newtona
better option handling

Revision 1.6  2007/02/07 20:51:41  newtona
implemented Options class
implemented Events class
StickyWin now uses Element.position

Revision 1.5  2007/02/06 18:11:29  newtona
refactored to re-use the overlay div because IE hogs memory for each one. god I hate IE.

Revision 1.4  2007/01/26 05:48:22  newtona
docs update

Revision 1.3  2007/01/22 22:00:15  newtona
numerous bug fixes to modalizer, stickywin, and popupdetails
updated for mootools 1.0
fixed date validation in form.validator

Revision 1.2  2007/01/11 20:55:23  newtona
changed the way options are set, split up stickywin into 4 files, refactored popupdetails to use stickywin and modalizer

Revision 1.1  2007/01/09 02:39:35  newtona
renamed addons directory to "common" directory

Revision 1.3  2007/01/09 01:25:05  newtona
numerous improvements; ability to set individual css styles, some other tweaks

Revision 1.2  2007/01/05 18:31:51  newtona
fixed documentation syntax
added cvs footer


*/
/*	Script: stickyWin.default.layout.js
		Creates an html holder for in-page popups using a default style.
		
		Author:
		Aaron Newton <aaron [dot] newton [at] cnet [dot] com>
		
		Dependencies:
		mootools - <Moo.js>, <Utility.js>, <Common.js>, <Function.js>, <Element.js>, <Array.js>, <String.js>
		cnet - <simple.template.parser.js>
		
		Function: stickyWinHTML
		Returns a DOM element for in-page popups (<stickyWin>) with a default style.
		
		Arguments:
		caption - (string) the caption for the popup window
		body - (string or DOM element) content for the popup
		options - a key/value set of options
		
		Options:
		width - (string) width for the box; defaults to 300px.
		css - (string) override for the css styles for the default html
		cssClass - (string; optional) adds a css class in addition to "DefaultStickyWin" to the container
		baseHref: (string) url to the path where the images in the default style are located.
							defaults to http://www.cnet.com/html/rb/assets/global/stickyWinHTML/.
		buttons - (array) array of key/value set of button options (see below)
		
		Buttons:
		text - (string) the text of the button
		onClick - (function) function to execute on click
		properties - (object) a name/value set of properties applied to the element using <Element.setProperties>
		properties.class - (string) a css class name for the button; defaults to "closeSticky" which closes the popup. You can give a different class name, and the button won't close the sticky when clicked. You can also give it an additional class name (className: 'closeSticky button') so that it will have your additional class but will still close the popup.
		
		Example:
(start code)
stickyWinHTML('the caption', 'this is the body', {
  width: '400px',
	buttons: [
		{
			text: 'close', 
			onClick: function(){alert('closed!')}
		},
		{
			text: 'okey-dokey', 
			onClick: function(){alert('ok!')},
			properties: {class: 'ok'} //don't close though
		},
		{
			text: 'blah', 
			onClick: function(){alert('blah!')},
			properties: {
				class: 'closeSticky blah', //still closes
				style: 'width: 100px, border: 1px solid red',
				title: 'blah! I say!'
			}
		}
	]
});
(end)
		
		Resulting HTML:
		The HTML generated by this function looks like this:
(start code)
<div class="DefaultStickyWin">
	<div class="top">
		<div class="top_ul"></div>
		<div class="top_ur">
			<div class="closeButton closeSticky"></div>
			<h1 style="width: 335px;" class="caption">the caption</h1>
		</div>
	</div>
	<div class="middle">
		<div class="body">this is the body</div>
	</div>
	<div class="closeBody">
		<div class="closeButtons">
			<a class="closeSticky button">close</a>
			<a class="ok button">okey-dokey</a>
			<a class="closeSticky blah button" title="blah! I say!" style="width: 100px, border; 1px solid red">blah</a>
		</div>
	</div>
	<div class="bottom">
		<div class="bottom_ll"></div>
		<div class="bottom_lr"></div>
	</div>
</div>
(end)
	*/
function stickyWinHTML (caption, body, options){
	this.options = $merge({
		width: '300px',
		css: "div.DefaultStickyWin div.body{font-family:verdana; font-size:11px; line-height: 13px;}"+
			"div.DefaultStickyWin div.top_ul{background:url(%baseHref%full.png) top left no-repeat; height:30px; width:15px; float:left}"+
			"div.DefaultStickyWin div.top_ur{position:relative; left:0px !important; left:-4px; background:url(%baseHref%full.png) top right !important; height:30px; margin:0px 0px 0px 15px !important; margin-right:-4px; padding:0px}"+
			"div.DefaultStickyWin h1.caption{margin:0px 35px 0px 0px; overflow: hidden; padding:0; font-weight:bold; color:#555; font-size:14px; position:relative; top:8px; left:5px; float: left; height: 22px;}"+
			"div.DefaultStickyWin div.middle, div.DefaultStickyWin div.closeBody {background:url(%baseHref%body.png) top left repeat-y; margin:0px 20px 0px 0px !important;	margin-bottom: -3px; position: relative;	top: 0px !important; top: -3px;}"+
			"div.DefaultStickyWin div.body{background:url(%baseHref%body.png) top right repeat-y; padding:8px 30px 8px 0px; margin-left:5px; position:relative; right:-20px}"+
			"div.DefaultStickyWin div.bottom{clear:both}"+
			"div.DefaultStickyWin div.bottom_ll{background:url(%baseHref%full.png) bottom left no-repeat; width:15px; height:15px; float:left}"+
			"div.DefaultStickyWin div.bottom_lr{background:url(%baseHref%full.png) bottom right; position:relative; left:0px !important; left:-4px; margin:0px 0px 0px 15px !important; margin-right:-4px; height:15px}"+
			"div.DefaultStickyWin div.closeButtons{text-align: center; background:url(%baseHref%body.png) top right repeat-y; padding: 0px 30px 8px 0px; margin-left:5px; position:relative; right:-20px}" +
			"div.DefaultStickyWin a.button:hover{background:url(%baseHref%big_button_over.gif) repeat-x}"+
			"div.DefaultStickyWin a.button {background:url(%baseHref%big_button.gif) repeat-x; margin: 2px 8px 2px 8px; padding: 2px 12px; cursor:pointer; border: 1px solid #999 !important; text-decoration:none; color: #000 !important;}"+
			"div.DefaultStickyWin div.closeButton{width:13px; height:13px; background:url(%baseHref%closebtn.gif) no-repeat; position: absolute; right: 0px; margin:10px 15px 0px 0px !important; cursor:pointer}",
		cssClass: '',
		baseHref: 'http://www.cnet.com/html/rb/assets/global/stickyWinHTML/',
		buttons: []
/*	These options are deprecated:		
		closeTxt: false,
		onClose: Class.empty,
		confirmTxt: false,
		onConfirm: Class.empty	*/
	}, options);
	//legacy support
	if(this.options.confirmTxt) this.options.buttons.push({text: this.options.confirmTxt, onClick: this.options.onConfirm || Class.empty});
	if(this.options.closeTxt) this.options.buttons.push({text: this.options.closeTxt, onClick: this.options.onClose || Class.empty});

	window.addEvent('domready', function(){
		try {
			if(!$('defaultStickyWinStyle')) {
				var css = simpleTemplateParser.parseTemplate(this.options.css, this.options);
				if(window.ie) css = css.replace(new RegExp('png', 'gi'),'gif');
				var styler = new Element('style').setProperty('id','defaultStickyWinStyle').injectInside($$('head')[0]);
				if (!styler.setText.attempt(css, styler)) styler.appendText(css);
			}
		}catch(e){dbug.log('error: %s',e);}
	}.bind(this));

	caption = $pick(caption, '%caption%');
	body = $pick(body, '%body');
	var container = new Element('div').setStyle('width', this.options.width).addClass('DefaultStickyWin');
	if(options.cssClass) container.addClass(options.cssClass);
	//header
	var h1Caption = new Element('h1').addClass('caption');

	if($(caption)) h1Caption.adopt(caption);
	else h1Caption.setHTML(caption);
	
	var bodyDiv = new Element('div').addClass('body');
	if($(body)) bodyDiv.adopt(body);
	else bodyDiv.setHTML(body);
	
	
	container.adopt(
		new Element('div').addClass('top').adopt(
				new Element('div').addClass('top_ul')
			).adopt(
				new Element('div').addClass('top_ur').adopt(
						new Element('div').addClass('closeButton').addClass('closeSticky')
					).adopt(h1Caption)
			)
	);
	//body
	container.adopt(new Element('div').addClass('middle').adopt(bodyDiv));
	//close buttons
	if(this.options.buttons.length > 0){
		var closeButtons = new Element('div').addClass('closeButtons');
		this.options.buttons.each(function(button){
			if(button.properties && button.properties.className){
				button.properties['class'] = button.properties.className;
				delete button.properties.className;
			}
			var properties = $merge({'class': 'closeSticky'}, button.properties);
			new Element('a').addEvent('click',
				button.onClick || Class.empty).appendText(
				button.text).injectInside(closeButtons).setProperties(properties).addClass('button');
		});
		container.adopt(new Element('div').addClass('closeBody').adopt(closeButtons));
	}
	//footer
	container.adopt(
		new Element('div').addClass('bottom').adopt(
				new Element('div').addClass('bottom_ll')
			).adopt(
				new Element('div').addClass('bottom_lr')
		)
	);
	return container;
};

/* do not edit below this line */   
/* Section: Change Log 

$Source: /cvs/main/flatfile/html/rb/js/global/cnet.global.framework/common/js.widgets/stickyWin.default.layout.js,v $
$Log: stickyWin.default.layout.js,v $
Revision 1.25  2007/07/18 16:23:35  newtona
a little style tweaking with stickywin default html

Revision 1.24  2007/07/18 16:15:21  newtona
forgot to bind the style objects in the setText.attempt method...

Revision 1.23  2007/07/16 21:00:21  newtona
using Element.setText for all style injection methods (fixes IE6 problems)
moving Element.setText to element.legacy.js; this function is in Mootools 1.11, but if your environment is running 1.0 you'll need this.

Revision 1.22  2007/07/07 01:26:44  newtona
stickyWinHTML's caption no longer has it's width defined by the width option; it's all css. this means we can resize the box or set the width to 100% or whatever.

Revision 1.21  2007/06/28 23:24:58  newtona
adding some css important rules to stickyWinHTML

Revision 1.20  2007/06/28 23:15:22  newtona
working around a bug in Mootools 1.11: Element.setText

Revision 1.19  2007/06/21 20:08:44  newtona
IE7 ignored the css definition; implemented Element.setText for anyone using Mootools < 1.11 and use that to set the css properties

Revision 1.18  2007/06/07 18:43:37  newtona
added CSS to autocompleter.js
removed string.cnet.js dependencies from template parser and stickyWin.default.layout.js

Revision 1.17  2007/05/17 19:45:43  newtona
product picker: hide() now hides tooltips; onPick passes in a 3rd argument that is the picker
stickyWinHTML: fixed a bug with className options for buttons
html.table: fixed a bug with className options for buttons

Revision 1.16  2007/05/16 20:09:41  newtona
adding new js files to redball.common.full
product.picker.js now has no picklets; these are in the implementations/picklets directory
ProductPicker now detects if there is no doctyp and, if not, sets the position of the picker to be fixed (no IE6 support)
small docs update in element.cnet.js
added new picklet: CNETProductPicker_PricePath
added new picklet: NewsStoryPicker_Path
new file: clipboard.js (allows you to insert text into the OS clipboard)
new file: html.table.js (automates building html tables)
new file: element.forms.js (for managing text inputs - get selected text information, insert content around selection, etc.)

Revision 1.15  2007/05/11 00:10:48  newtona
syntax fix

Revision 1.14  2007/05/07 21:37:12  newtona
docs update

Revision 1.13  2007/05/05 01:01:24  newtona
stickywinHTML: tweaked the options for buttons
element.cnet: tweaked smoothshow/hide css handling

Revision 1.12  2007/05/04 00:32:39  newtona
stickwinHTML: added the ability for buttons to not close the sticky (className option)
stickyWin: added .pin (see Element.pin.js)

Revision 1.11  2007/04/05 00:13:12  newtona
local.vars.js: removing $type.isNumber dependency
login.status.js: no change; fixed typo in docs
search.functions.js: removing $type.isNumber dependency
stickyWinDefaultLayout: infinite buttons!
iframeShim.js: fixed an ie bug that caused it to abort the page

Revision 1.10  2007/03/29 23:12:00  newtona
confirmer now checks for a bg color in IE6 to use crossfading (see Element.fxOpacityOk)
fixed an IE7 css layout issue in stickyDefaultHTML
StickyWin now uses Element.flush
StickyWinFx.Drag now temporarily shows the sticky win (with opacity 0) to execute makeDraggable and makeResizable to prevent a Safari bug

Revision 1.9  2007/03/13 19:09:29  newtona
fixed a typy - added event "close" instead of "click". duh.

Revision 1.8  2007/03/13 18:57:17  newtona
syntax fix

Revision 1.7  2007/03/13 18:49:56  newtona
added onClose action

Revision 1.6  2007/03/08 02:38:50  newtona
added close and confirm buttons

Revision 1.5  2007/03/02 01:31:53  newtona
fixed some css bugs in IE
fixed a bug where all blocks inherited the width of the first created

Revision 1.4  2007/02/24 00:21:56  newtona
fixed  a css bug

Revision 1.3  2007/02/22 21:27:43  newtona
moved product picker from utilities dir
fixed missing ; in stickywin html

Revision 1.2  2007/02/22 18:19:48  newtona
fixed a bug with the style writer
added a missing bind()

Revision 1.1  2007/02/21 00:41:48  newtona
*** empty log message ***


*//*	Script: stickyWin.js
		Creates a div within the page with the specified contents at the location relative to the element you specify; basically an in-page popup maker.

		Dependencies:
		Moo - <Moo.js>, <Common.js>, <Utility.js>, <Element.js>, <Function.js>, <Dom.js>, <Array.js>, <Window.Base.js>, <Window.Size.js>, <Events.js>
		CNET - <element.shortcuts.js>, <element.dimensions.js>, <element.position.js>, <element.pin.js>, <IframeShim.js>
		
		Author:
		Aaron Newton (aaron [dot] newton [at] cnet [dot] com)
		
		Class: StickyWin
		Creates a div within the page with the specified contents at the location relative to the element you specify; basically an in-page popup maker.

		Arguments:
		options - an object with key/value options
		
		Options:
			onDisplay - function to execute when the popup is shown
			onClose - function to execute when the popup is closed
			closeClassName - class name of the element(s) in your popup content that, 
					when clicked, should close the window; defaults to "closeSticky"
			pinClassName - class name of the elements(s) in your popup content that,
					when clicked, should pin the sticky in place; defaults to "pinSticky"
			content - the content of your popup; this should be layout html and your message or a dom element
			zIndex - the zIndex of the popup; defaults to 10000
			id - the id of the wrapper div that gets created that will contain your content; 
					defaults to 'StickyWin' + the date (so it's unique)
			className - optional class name for the wrapper dive that gets created that will
					contain your content
			position - "center", "upperRight", "bottomRight", "upperLeft", "bottomLeft"; the point in the popup that is positioned;
					defaults to 'center'
			edge - same options as position (center, upperRight, etc.) but specifies the edge of the stickyWin to position
				to the point specified in position. see <Element.setPosition> for details. Optional; defaults to the
				<Element.setPosition> default state.
			offset - object containing {x: # and y: #} (integers) the top and left offset from the element in the 
					page that the popup is relative to; this offset is applied to the center of the popup 
					or the corner, depending on  the value you specify in the 'position' option.
			relativeTo - a dom element to position the popup relative to; defaults to document.body (i.e. the window)
			width - an optional width for the wrapper div for your popup
			height - an optional height for the wrapper div for your popup
			timeout - (integer) an optional timeout interval to hide the popup after a specified time
			allowMultiple - (boolean) allow multiple instance of StickyWin on the page; defaults to true
			allowMultipleByClass - (boolean) allow multiple popups that share the same className as specified in 
				the className option; defaults to false
			showNow - display the popup on instantiation; defaults to true
			useIframeShim - use an <IframeShim> to mask content below the element; defaults to true.
			iframeShimSelector - the css selector to find the element within your popup under which 
				the iframe shim should be placed to obscure select lists and the like (see <IframeShim>)
			
	Example:
(start code)
var myWin = new StickyWin({
	content: '<div id="myWin">hi there!<br><a href="javascript:void(0);" class="closeSticky">close</a></div>'
});
//popups up a box in the middle of the window with "hi there" and a close link(end)
	*/
var StickyWin = new Class({
	options: {
		onDisplay: Class.empty,
		onClose: Class.empty,
		closeClassName: 'closeSticky',
		pinClassName: 'pinSticky',
		content: '',
		zIndex: 10000,
		className: '',
		//id: ... set above in initialize function
		edge: false, //see Element.setPosition in element.cnet.js
		position: 'center', //center, corner == upperLeft, upperRight, bottomLeft, bottomRight
		offset: {x:0,y:0},
		relativeTo: document.body, 
		width: false,
		height: false,
		timeout: -1,
		allowMultipleByClass: false,
		allowMultiple: true,
		showNow: true,
		useIframeShim: true,
		iframeShimSelector: ''
	},
	css: '.SWclearfix:after {content: "."; display: block; height: 0; clear: both; visibility: hidden;}'+
			 '.SWclearfix {display: inline-table;}'+
			 '* html .SWclearfix {height: 1%;}'+
			 '.SWclearfix {display: block;}',
	initialize: function(options){
		options.id = options.id || 'StickyWin_'+new Date().getTime();
		this.setOptions(options);
		this.makeWindow();
		if(this.options.content) this.setContent(this.options.content);
		if(this.options.showNow) this.show();
		//add css for clearfix
		window.addEvent('domready', function(){
			try {
				if(!$('StickyWinClearfix')) {
					var style = new Element('style').setProperty('id','StickyWinClearfix').injectInside($$('head')[0]);
					if (!style.setText.attempt(this.css, style)) style.appendText(this.css);
				}
			}catch(e){dbug.log('error: %s',e);}
		}.bind(this));
	},
	makeWindow: function(){
		this.destroyOthers();
		if(!$(this.options.id)) {
			this.win = new Element('div').setProperty('id',			this.options.id).addClass(this.options.className).addClass('StickyWinInstance').addClass('SWclearfix').setStyles({
				 	'display':'none',
					'position':'absolute',
					'zIndex':this.options.zIndex
				}).injectInside(document.body);
		} else this.win = $(this.options.id);
		if(this.options.width && $type(this.options.width.toInt())=="number") this.win.setStyle('width', this.options.width.toInt() + 'px');
		if(this.options.height && $type(this.options.height.toInt())=="number") this.win.setStyle('height', this.options.height.toInt() + 'px');
		return this;
	},
/*	Property: show
		Shows the popup.
	*/
	show: function(){
		this.fireEvent('onDisplay');
		if(!this.positioned) this.position();
		this.showWin();
		if(this.options.useIframeShim) this.showIframeShim();
		this.visible = true;
		return this;
	},
	showWin: function(){
		this.win.setStyle('display','block');
	},
/*	Property: hide
		Hides the popup.
	*/
	hide: function(){
		this.fireEvent('onClose');
		this.hideWin();
		if(this.options.useIframeShim) this.hideIframeShim();
		this.visible = false;
		return this;
	},
	hideWin: function(){
		this.win.setStyle('display','none');
	},
	destroyOthers: function() {
		if(!this.options.allowMultipleByClass || !this.options.allowMultiple) {
			$$('div.StickyWinInstance').each(function(sw) {
				if(!this.options.allowMultiple || (!this.options.allowMultipleByClass && sw.hasClass(this.options.className))) 
					sw.remove();
			}, this);
		}
	},
/*	Property: setContent
		Replaces the content of the popup with the content passed in.
		
		Arguments:
		html - the new content
	*/
	setContent: function(html) {
		if(this.win.getChildren().length>0) this.win.empty();
		if($type(html) == "string") this.win.setHTML(html);
		else if ($(html)) this.win.adopt(html);
		this.win.getElements('.'+this.options.closeClassName).each(function(el){
			el.addEvent('click', this.hide.bind(this));
		}, this);
		this.win.getElements('.'+this.options.pinClassName).each(function(el){
			el.addEvent('click', this.togglepin.bind(this));
		}, this);
		return this;
	},
	
	position: function(){
		this.positioned = true;
		this.win.setPosition({
			relativeTo: this.options.relativeTo,
			position: this.options.position,
			offset: this.options.offset,
			edge: this.options.edge
		});
		if(this.shim) this.shim.position();
		return this;
	},
/*	Property: pin
		Affixes the stickywin to a fixed position, even if the window is scrolled. See <Element.pin>.
	*/
	pin: function(pin) {
		if(!this.win.pin) {
			dbug.log('you must include element.pin.js!');
			return false;
		}
		this.pinned = $pick(pin, true);
		return this.win.pin(pin);
	},
/*	Property: unpin
		Affixes the stickywin to a fixed position, even if the window is scrolled. See <Element.unpin>.
	*/
	unpin: function(){
		this.pin(false);
	},
/*	Property: togglepin
		Toggle the pinned state of the Sticky;
	*/
	togglepin: function(){
		this.pin(!this.pinned);
	},
	makeIframeShim: function(){
		if(!this.shim){
			this.shim = new IframeShim({
				element: (this.options.iframeShimSelector)?this.win.getElement(this.options.iframeShimSelector) : $('StickyWinOverlay') || this.win,
				display: false,
				name: 'StickyWinShim'
			});
		}
	},
	showIframeShim: function(){
		if(this.options.useIframeShim) {
			this.makeIframeShim();
			this.shim.show();
		}
	},
	hideIframeShim: function(){
		if(this.options.useIframeShim)
			this.shim.hide();
	},
/*	Property: destroy
		Removes the popup element.
	*/
	destroy: function(){
		this.win.remove();
		if(this.options.useIframeShim) this.shim.remove();
		if($('StickyWinOverlay'))$('StickyWinOverlay').remove();
	}
});
StickyWin.implement(new Options);
StickyWin.implement(new Events);

var stickyWin = StickyWin;
/* do not edit below this line */   
/* Section: Change Log 

$Source: /cvs/main/flatfile/html/rb/js/global/cnet.global.framework/common/js.widgets/stickyWin.js,v $
$Log: stickyWin.js,v $
Revision 1.26  2007/07/18 16:15:21  newtona
forgot to bind the style objects in the setText.attempt method...

Revision 1.25  2007/07/16 21:00:21  newtona
using Element.setText for all style injection methods (fixes IE6 problems)
moving Element.setText to element.legacy.js; this function is in Mootools 1.11, but if your environment is running 1.0 you'll need this.

Revision 1.24  2007/05/29 22:01:53  newtona
Split element.cnet.js into seperate files; updated docs in files to note this
Changed element.visible to element.isVisible (left old namespace for legacy support)
Fixed Element.empty in prototype.compatibility.js
Removed as many dependencies in common code to element.*.js as possible (espeically element.shortcuts.js)

Revision 1.23  2007/05/11 00:10:48  newtona
syntax fix

Revision 1.22  2007/05/11 00:05:29  newtona
adding clearfix css to all sticky win instances

Revision 1.21  2007/05/04 01:22:47  newtona
added togglepin

Revision 1.19  2007/05/04 00:32:39  newtona
stickwinHTML: added the ability for buttons to not close the sticky (className option)
stickyWin: added .pin (see Element.pin.js)

Revision 1.18  2007/04/13 19:06:11  newtona
dependency update in the docs

Revision 1.17  2007/03/30 19:32:20  newtona
changing .flush to .empty

Revision 1.16  2007/03/29 23:12:00  newtona
confirmer now checks for a bg color in IE6 to use crossfading (see Element.fxOpacityOk)
fixed an IE7 css layout issue in stickyDefaultHTML
StickyWin now uses Element.flush
StickyWinFx.Drag now temporarily shows the sticky win (with opacity 0) to execute makeDraggable and makeResizable to prevent a Safari bug

Revision 1.15  2007/03/23 23:01:16  newtona
stickywin: implemented current options options method (no functional change)
stickywinFx: removed an old function that was empty; should have been gone a long time ago (getDefaultOptions)

Revision 1.14  2007/03/23 20:19:48  newtona
Iframeshim: added className; updated docs
StickyWin: added edge support (see Element.setPosition)

Revision 1.13  2007/03/08 00:06:13  newtona
stickyWin now empties it's content before setContent adds new stuff

Revision 1.12  2007/02/21 00:24:56  newtona
no change, but cvs says it's different. maybe a space?

Revision 1.11  2007/02/08 22:54:00  newtona
removed a comment

Revision 1.10  2007/02/08 20:50:54  newtona
fixed a bug where .show repositioned the window every time

Revision 1.9  2007/02/08 01:29:51  newtona
fixed syntax error

Revision 1.8  2007/02/07 20:51:41  newtona
implemented Options class
implemented Events class
StickyWin now uses Element.position

Revision 1.7  2007/01/26 18:24:41  newtona
docs update

Revision 1.6  2007/01/26 05:49:10  newtona
syntax update for mootools 1.0

Revision 1.5  2007/01/23 20:54:24  newtona
a little better position handling

Revision 1.4  2007/01/22 22:00:15  newtona
numerous bug fixes to modalizer, stickywin, and popupdetails
updated for mootools 1.0
fixed date validation in form.validator

Revision 1.3  2007/01/19 01:22:32  newtona
fixed a few syntax errors

Revision 1.2  2007/01/11 20:55:23  newtona
changed the way options are set, split up stickywin into 4 files, refactored popupdetails to use stickywin and modalizer

Revision 1.1  2007/01/09 02:39:35  newtona
renamed addons directory to "common" directory

Revision 1.2  2007/01/09 01:26:04  newtona
docs syntax fix

Revision 1.1  2007/01/05 19:29:30  newtona
first check in


*/

/*	Script: stickyWinFx.js
		Extends <StickyWin> to create popups that fade in and out and can be dragged and resized (requires <stickyWinFx.Drag.js>).
	
		Author:
		Aaron Newton (aaron [dot] newton [at] cnet [dot] com)

		Dependencies:
		mootools - <Fx.Base.js>
		cnet - <stickyWin.js> and all its dependencies.
		optional - <stickyWinFx.Drag.js> and <Drag.Base.js>

		Class: StickyWinFx
		Creates a <StickyWin> that optionally fades in and out, is draggable, and is resizable (requires <stickyWinFx.Drag.js>).
		
		Arguments:
		options - an object with key/value options
		
		Options:
		see <StickyWin>; inherits all those options in addition to the following.
		
			fade - (boolean) fade in and out; defaults to true
			fadeDuration - (integer) the duration of the fade effect; defaults to 150
			fadeTransition - an <Fx.Transitions> to use for the fade effect; defaults to <Fx.Transitions.sineInOut>
		
		Additional Options:
		These options depend on <stickyWinFx.Drag.js> and <Drag.Base.js>; so they don't do anything if those
		files are not included in your environment.
		
			draggable - (boolean) make the popup draggable, defaults to false
			dragOptions - (object) optional options to pass to the drag effect
			dragHandleSelector - optional css selector to select the element(s) within in 
				your popup to use as a drag handle.
			resizable - (boolean) make the popup resizable or not; defaults to false
			resizeOptions - (object) optional options to pass to the resize effect
			resizeHandleSelector - optional css selector to select the element(s) within in 
				your popup to use as a resize handle.
		
		Example:
(start code)
var myWin = new StickyWinFx({
	content: '<div id="myWin">hi there!<br><a href="javascript:void(0);" class="closeSticky">close</a></div>',
	fadeDuration: 500,  //slow it down
	draggable: true, //make it draggable
	dragHandleSelector: 'img.handle' //get the img with the class "handle" for the handle
});
//fades in a box in the middle of the window with "hi there" and a close link(end)
//window is draggable using the image(s) with the class "handle"
(end)		
	*/
var StickyWinFx = StickyWin.extend({
	initialize: function(options){
		this.parent($merge({
			fade: true,
			fadeDuration: 150,
			fadeTransition: Fx.Transitions.sineInOut,
			draggable: false,
			dragOptions: {},
			dragHandleSelector: 'h1.caption',
			resizable: false,
			resizeOptions: {},
			resizeHandleSelector: ''
		}, options));
	},
	setContent: function(html){
		this.parent(html);
		if(this.options.draggable) this.makeDraggable();
		if(this.options.resizable) this.makeResizable();
		return this;
	},	
	hideWin: function(){
		if(this.options.fade) this.fade(1,0);
		else this.win.hide();
	},
	showWin: function(){
		if(this.options.fade) {
			this.fade(0,1);
		} else this.win.show();
	},
	fade: function(from,to){
		if(!this.fadeFx) {
			this.win.setStyles({
				'opacity':'0',
				'display':'block'
			});
			this.fadeFx = this.win.effect('opacity', {
				duration: this.options.fadeDuration,
				transition: this.options.fadeTransition
			});
		}
		if (to > 0) this.win.setStyle('display','block');
		this.fadeFx.custom(from,to).chain(function(){
			if(to == 0) this.win.setStyle('display', 'none');
		}.bind(this));
		
		return this;
	},
	makeDraggable: function(){
		dbug.log('you must include Drag.js, cannot make draggable');
	},
	makeResizable: function(){
		dbug.log('you must include Drag.js, cannot make resizable');
	}
});
/* do not edit below this line */   
/* Section: Change Log 

$Source: /cvs/main/flatfile/html/rb/js/global/cnet.global.framework/common/js.widgets/stickyWinFx.js,v $
$Log: stickyWinFx.js,v $
Revision 1.13  2007/03/29 23:12:00  newtona
confirmer now checks for a bg color in IE6 to use crossfading (see Element.fxOpacityOk)
fixed an IE7 css layout issue in stickyDefaultHTML
StickyWin now uses Element.flush
StickyWinFx.Drag now temporarily shows the sticky win (with opacity 0) to execute makeDraggable and makeResizable to prevent a Safari bug

Revision 1.12  2007/03/23 23:01:16  newtona
stickywin: implemented current options options method (no functional change)
stickywinFx: removed an old function that was empty; should have been gone a long time ago (getDefaultOptions)

Revision 1.11  2007/03/08 02:42:15  newtona
fixed an error where the handle was being assigned before the content

Revision 1.10  2007/02/27 21:46:43  newtona
docs update; fixing references

Revision 1.9  2007/02/22 18:20:18  newtona
fixed bug where the element faded out, but wasn't set to display-none

Revision 1.8  2007/02/21 00:26:52  newtona
added a default drag handle

Revision 1.7  2007/02/08 01:30:07  newtona
fixed syntax error

Revision 1.6  2007/02/07 20:51:41  newtona
implemented Options class
implemented Events class
StickyWin now uses Element.position

Revision 1.5  2007/01/26 05:50:58  newtona
added footer cvs tags


*/
/*	Script: stickyWinFx.Drag.js
		Implements drag and resize functionaity into <StickyWinFx>. See <StickyWinFx> for the options.
		
		Author:
		Aaron Newton (aaron [dot] newton [at] cnet [dot] com)

		Dependencies:
		<stickyWin.js>, <stickyWinFx.js>, and all their dependencies plus <Drag.Base.js>.
*/
if(typeof Drag != "undefined"){
	StickyWinFx.implement({
		makeDraggable: function(){
			var toggled = this.toggleVisible(true);
			if(this.options.useIframeShim) {
				this.makeIframeShim();
				var dragComplete = this.options.dragOptions.onComplete || Class.empty;
				this.options.dragOptions.onComplete = function(){
					dragComplete();
					this.shim.position();
				}.bind(this);
			}
			if(this.options.dragHandleSelector) {
				var handle = this.win.getElement(this.options.dragHandleSelector);
				if (handle) {
					handle.setStyle('cursor','move');
					this.options.dragOptions.handle = handle;
				}
			}
			this.win.makeDraggable(this.options.dragOptions);
			if (toggled) this.toggleVisible(false);
		}, 
		makeResizable: function(){
			var toggled = this.toggleVisible(true);
			if(this.options.useIframeShim) {
				this.makeIframeShim();
				var resizeComplete = this.options.resizeOptions.onComplete || Class.empty;
				this.options.resizeOptions.onComplete = function(){
					resizeComplete();
					this.shim.position();
				}.bind(this);
			}
			if(this.options.resizeHandleSelector) {
				var handle = this.win.getElement(this.options.resizeHandleSelector);
				if(handle) this.options.resizeOptions.handle = this.win.getElement(this.options.resizeHandleSelector);
			}
			this.win.makeResizable(this.options.resizeOptions);
			if (toggled) this.toggleVisible(false);
		},
		toggleVisible: function(show){
			if(!this.visible && window.khtml && $pick(show, true)) {
				this.win.setStyles({
					display: 'block',
					opacity: 0
				});
				return true;
			} else if(!$pick(show, false)){
				this.win.setStyles({
					display: 'none',
					opacity: 1
				});
				return false;
			}
			return false;
		}
	});
}
/* do not edit below this line */   
/* Section: Change Log 

$Source: /cvs/main/flatfile/html/rb/js/global/cnet.global.framework/common/js.widgets/stickyWinFx.Drag.js,v $
$Log: stickyWinFx.Drag.js,v $
Revision 1.5  2007/03/29 23:12:00  newtona
confirmer now checks for a bg color in IE6 to use crossfading (see Element.fxOpacityOk)
fixed an IE7 css layout issue in stickyDefaultHTML
StickyWin now uses Element.flush
StickyWinFx.Drag now temporarily shows the sticky win (with opacity 0) to execute makeDraggable and makeResizable to prevent a Safari bug

Revision 1.4  2007/02/21 00:26:33  newtona
drag handle now gets cursor: move

Revision 1.3  2007/01/26 05:49:42  newtona
added docs

Revision 1.2  2007/01/22 22:00:15  newtona
numerous bug fixes to modalizer, stickywin, and popupdetails
updated for mootools 1.0
fixed date validation in form.validator

Revision 1.1  2007/01/11 20:55:23  newtona
changed the way options are set, split up stickywin into 4 files, refactored popupdetails to use stickywin and modalizer


*/
/*	Script: stickyWin.Modal.js
		This script extends <StickyWin> and <StickyWinFx> classes to add <Modalizer> functionality.

		Author:
		Aaron Newton (aaron [dot] newton [at] cnet [dot] com)

		Dependencies:
		cnet - <stickyWin.js>, and all their dependencies plus <modalizer.js>.
		optional - <stickyWinFx.js> 
	*/
var modalWinBase = {
	initialize: function(options){
		options = options||{};
		this.setModalOptions($merge(options.modalOptions||{}, {
			onModalHide: function(){
					this.hide(false);
				}.bind(this)
			}));
		this.parent(options);
	},
	show: function(showModal){
		if($pick(showModal, true))this.modalShow();
		this.parent();
	},
	hide: function(hideModal){
		if($pick(hideModal, true))this.modalHide();
		this.parent();
	}
};

/*	Class: StickyWinModal
		Creates a <StickyWin> that uses the functionality in <Modalizer> to overlay the document.
		
		Argument:
		options - an object with key/value options defined in <StickyWin> and <Modalizer>
	
		Options:
		inherits all the options of <StickyWin>
		modalOptions - options object for <Modalizer>
	*/
var StickyWinModal = StickyWin.extend(modalWinBase);
StickyWinModal.implement(new Modalizer);

/*	Class: StickyWinFxModal
		Creates a <StickyWinFx> that uses the functionality in <Modalizer> to overlay the document.
		
		Argument:
		options - an object with key/value options defined in <StickyWin>, <StickyWinFx> and <Modalizer>
		
		Argument:
		options - an object with key/value options defined in <StickyWin> and <Modalizer>
	
		Options:
		inherits all the options of <StickyWinFx>
		modalOptions - options object for <Modalizer>

	*/
var StickyWinFxModal = (typeof StickyWinFx != "undefined")?StickyWinFx.extend(modalWinBase):Class.empty;
try { StickyWinFxModal.implement(new Modalizer()); }catch(e){}
/* do not edit below this line */   
/* Section: Change Log 

$Source: /cvs/main/flatfile/html/rb/js/global/cnet.global.framework/common/js.widgets/stickyWin.Modal.js,v $
$Log: stickyWin.Modal.js,v $
Revision 1.7  2007/02/21 00:25:29  newtona
updated options handling

Revision 1.6  2007/02/06 18:11:53  newtona
fixed a bug where the iframeshim was being left behind.

Revision 1.5  2007/01/27 08:40:21  newtona
that last fix didn't work. now it handles no options passed in.

Revision 1.4  2007/01/27 08:37:03  newtona
handle the posibility that no options are applied

Revision 1.3  2007/01/26 05:49:26  newtona
added docs

Revision 1.2  2007/01/22 22:00:15  newtona
numerous bug fixes to modalizer, stickywin, and popupdetails
updated for mootools 1.0
fixed date validation in form.validator

Revision 1.1  2007/01/11 20:55:23  newtona
changed the way options are set, split up stickywin into 4 files, refactored popupdetails to use stickywin and modalizer


*/
/*	Script: simple.error.popup.js
		The function in this script just makes a little alert box with a close button.
		
		Dependencies:
		CNET - <stickyWin.js>, <stickyWin.Modal.js>, <stickyWin.default.layout.js>
		Assets - images located at www.cnet.com/html/rb/js/assets/global/simple.error.popup (see css inline)
		
		Function: simpleErrorPopup
		This function just makes a little alert box with a close button.
		
		Arguments:
		msghdr - the caption for the window
		msg - the error message
		
		Example:
		>simpleErrorPopup('Woops!', 'Oh nos! I've got five Internets open!');
		
		Returns: 
		An instance of <StickyWinModal>
	*/
var simpleErrorPopup = function(msghdr, msg) {
	msg = '<p class="errorMsg SWclearfix">' +
						'<img src="http://www.cnet.com/html/rb/assets/global/simple.error.popup/icon_problems_sm.gif"'+
						' class="bang clearfix" style="float: left; width: 30px; height: 30px; margin: 3px 5px 5px 0px;">'
						 + msg + '</p>';
	var body = stickyWinHTML(msghdr, msg, {width: '250px'});
	return new StickyWinModal({
		modalOptions: {
			modalStyle: {
				zIndex: 11000
			}
		},
		zIndex: 110001,
		content: body,
		position: 'center' //center, corner
	});
};
/* do not edit below this line */   
/* Section: Change Log 

$Source: /cvs/main/flatfile/html/rb/js/global/cnet.global.framework/common/utilities/simple.error.popup.js,v $
$Log: simple.error.popup.js,v $
Revision 1.10  2007/05/11 00:05:29  newtona
adding clearfix css to all sticky win instances

Revision 1.9  2007/05/07 21:38:33  newtona
zindex tweak so that error popup is higher than the default stickywin (in case there's an error on a page with a stickywin)

Revision 1.8  2007/03/08 23:31:22  newtona
strict javascript warnings cleaned up
removed deprecated dbug loadtimers
dbug enables on debug.cookie()

Revision 1.7  2007/02/21 00:42:30  newtona
docs update

Revision 1.6  2007/02/21 00:31:32  newtona
using default sticky win html now.

Revision 1.5  2007/02/06 18:12:26  newtona
changed the way that the css was added to the dom. only does this once now, even if you execute the function numerous times.

Revision 1.4  2007/01/26 05:56:03  newtona
syntax update for mootools 1.0
docs update

Revision 1.3  2007/01/22 21:55:18  newtona
changed image paths to www.cnet.com
updated stickyWin namespace to StickyWin

Revision 1.2  2007/01/11 20:56:05  newtona
docs change

Revision 1.1  2007/01/09 02:39:35  newtona
renamed addons directory to "common" directory

Revision 1.2  2007/01/05 18:54:51  newtona
some documentation changes

Revision 1.1  2007/01/05 18:32:04  newtona
first check in


*/
/*	Script: date.picker.js
		Allows the user to enter a date in any popuplar format or choose from a calendar.
		
		Dependencies:
		mootools - <Moo.js>, <Utility.js>, <Common.js>, <Function.js>, <Element.js>, <Array.js>, <String.js>, <Event.js>
		cnet - <stickyWin.js> and all its dependencies
		optional - <Drag.Base.js>, <stickyWinFx.js>
		
		Authors:
		Paul Anderson
		Aaron Newton <aaron [dot] newton [at] cnet [dot] com>
		
		Class: DatePicker
		Allows the user to enter a date in any popuplar format or choose from a calendar.
		
		Arguments:
		input - the id of a text input, or a reference to the element itself
		options - an object with key/value settings
		
		Options:
		calendarId - (string) the id of the calendar to show; defaults to "popupCalendar" + the date (so it’s unique)
		months - (array) the months of the year. Defaults to ["Janurary", "February", etc.]
		days - (array) the days of the week. Defaults to ["Su", "Mo", "Tu", etc.]
		stickyWinOptions - (object) options to pass along to the stickyWin popup object. Defaults to {position: 'bottomLeft', offset: {x:10, y:10}}
		stickyWinToUse - which <StickyWin> class to use (<StickyWin>, <StickyWinFx>, etc.)
		draggable: (boolean) whether or not the popup is draggable. Requires <Drag.Base.js>. Defautls to true (if <Drag.Base.js> is not present, the element won't be draggable, but it won't throw an error.
		dragOptions: (object) options to pass on to <Drag.Base>
		additionalShowLinks - (array) collection of dom elements (or ids) that should show the calendar for the input
		showOnInputFocus - (boolean) show the calendar when the input is focused. Defaults to true. If set to false, you must specify at least one object in additionalShowLinks if you want the calendar to be accessible. **NOTE: you can set this to false and specy no additional show links that this class will still auto-format date inputs for you**
		useDefaultCss - (boolean) use the default css described in this class. If false, you must define your own css. Defaults to true.
		hideCalendarOnPick - (boolean) hide the calendar when the user chooses a date. Defaults to true.
		onPick - function to execute when the user choose a date
		onShow - function to execute when the calendar appears
		onHide - function to execute when the calendar is hidden
		CSS:
		The calendar popup builds a table with all the dates and months and whatnot. You may style this table using the following descriptors:

		div.calendarHolder - the div containing the calendar table.
		table.datePicker - the table with the calendar values
		tr.dateNav - the row containing the forward, back, and close buttons, and the month name
		tr.dayNames - the row containing the names of the days of the week
		tr.dayRow - one of the rows containing actual dates
		td.today - the td that contains today's date
		td.selectedDate - the td that contains the user's selection
		td.otherMonthDate - tds that contain dates before or after the current selected month
		
		Autoformatting and Date format: 
		This class will take a user's input of a date value and convert it into MM/DD/YYYY. If the user inputs 01.02.03,
		this class will update it to 01/02/2003 on the blur event of the field. The same is true for 01.02.2003, 01/02/03,
		01 02 2003, 2003.02.01, and so on.
		
		If you need this class to return a different format, you can use <Class.implement> to create your own formatter. If
		javascript had a better date object, we wouldn't have to do it like this, but what are ya gonna do?
		
		Example:
(start code)
<input type="text" name="date" id="dateInput"> <img src="calendar.gif" id="calendarImg">
<script>
new DatePicker('dateInput', {
	additionalShowLinks: ['calendarImg'],
	showOnInputFocus: false
});
(end)
	*/
	var DatePicker = new Class({
		defaultCss: 'div.calendarHolder{width:210px; height:182px; padding-left:8px; padding-top:1px; '+
			'background:url(http://www.cnet.com/html/rb/assets/global/datePicker/calendar.back.png) no-repeat} '+
		  '* html div.calendarHolder {background:url(http://www.cnet.com/html/rb/assets/global/datePicker/calendar.back.gif) no-repeat}'+
			'table.datePicker * {font-size:11px; line-height:16px;} '+
			'table.datePicker{margin:6px 0px 0px 0px; width:190px; padding:0px 5px 0px 5px} '+
			'table.datePicker td{cursor:pointer; text-align:center} '+
			'table.datePicker img.closebtn{margin-top:2px} '+
			'tr.dateNav{height:22px; margin-top:8px} '+
			'tr.dayNames td{color:#666; font-weight:bold; border-bottom:1px solid #ddd} '+
			'table.datePicker tr.dayRow td:hover{background:#ccc} '+
			'td.today{color:#bb0904} '+
			'td.otherMonthDate{border:1px solid #fff; color:#666; background:#f3f3f3} '+
			'td.selectedDate{border:1px solid #20397b; background:#dcddef}',
		fullDay: 86400000,
		initialize: function(input, options){
			var StickyWinToUse = (typeof StickyWinFx == "undefined")?StickyWin:StickyWinFx;
			this.setOptions({
				calendarId: false,
				months: ["January","February","March","April","May","June","July",
									"August","September","October","November","December"],
				days: ["Su","Mo","Tu","We","Th","Fr","Sa"],
				stickyWinOptions: {
					position: "bottomLeft",
					offset: {x:10, y:10},
					fadeDuration: 400
				},
				stickyWinToUse: StickyWinToUse,
				draggable: true,
				dragOptions: {},
				additionalShowLinks:[],
				showOnInputFocus: true,
				useDefaultCss: true,
				hideCalendarOnPick: true,
				onPick: Class.empty,
				onShow: Class.empty,
				onHide: Class.empty
			}, options);
			if(!this.options.calendarId) this.options.calendarId = "popupCalendar" + new Date().getTime();
			this.input = $(input);
			if(this.options.useDefaultCss)this.writeCss();
			this.setUpObservers();
			this.getCalendar();
		},
		setUpObservers: function(){
			if (this.options.showOnInputFocus) this.input.addEvent('focus', this.show.bind(this));
			try {this.input.addEvent('blur', this.updateInput.bind(this));}catch(e){} //ie sometimes doesn't like this.
			this.options.additionalShowLinks.each(function(lnk){$(lnk).addEvent('click', this.show.bind(this))}, this);
		},
		writeCss: function(css) {
			css = $pick(css,this.defaultCss);
			window.addEvent('domready', function(){
				try {
					if(!$('datePickerStyle')) {
						var style = new Element('style').setProperty('id','datePickerStyle').injectInside($$('head')[0]);
						if (!style.setText.attempt(css, style)) style.appendText(css);
					}
				}catch(e){dbug.log('error: %s',e);}
			});
		},
/*	Property: updateInput
		Takes a given date and updates the input field with its value.
		
		Arguments:
		date - a date or a string that is parsable as a date (see <validDate>)
	*/
		updateInput: function(date){
			if(!$type(date) == "string" || (date && !date.getTime)) date = this.input.getValue();
			var dateStr = this.formatDate(this.validDate(date));
			if($type(dateStr) == "string") {
				this.input.value = dateStr;
				return dateStr;
			}
			return date;
		},
/*	Property: validDate
		Parses a string into a Date object and returns it.
		
		Arguments:
		val - (optional) the date to parse. a string or a date object. If no value is specified, the input 
			value will be used instead.
		
		Accepted formats:
		01.02.03, 01.02.2003, 01/02/03, 01 02 2003, 2003.02.01, and so on.
	*/		
		validDate: function(val) {
			val = $pick(val, this.input.getValue());
			val = val.replace(/^\s+|\s+$/g,"");
			var asDate = Date.parse(val);
			if (isNaN(asDate)) asDate = Date.parse(val.replace(/[^\w\s]/g,"/"));
			if (isNaN(asDate)) asDate = Date.parse(val.replace(/[^\w\s]/g,"/") + "/" + new Date().getFullYear());
			if (!isNaN(asDate)) {
				var newDate = new Date(asDate);
				if (newDate.getFullYear() < 2000 && val.indexOf(newDate.getFullYear()) < 0) {
					newDate.setFullYear(newDate.getFullYear() + 100);
				}
				return newDate;
			} else return asDate;
		},
/*	Property: formatDate
		formats a date object into MM/DD/YYYY.
		
		Arguments:
		date - (Date object) the date to format.
	*/
		formatDate: function (date) {
			try {
				// always "get" as UTC, without timezone, so there's no confusion over the calendar day
					var fd = ((date.getUTCMonth() < 9) ? "0" : "") + (date.getUTCMonth()+1) + "/";
					fd += ((date.getUTCDate() < 10) ? "0" : "") + date.getUTCDate() + "/";
					fd += date.getUTCFullYear();
					return fd;
			} catch(e){return date}
		},
		
		zeroHourGMT: function(date) {
			date.setTime(date.getTime() - date.getTime() % 86400000);
			return date;
		},
		
		getCalendar: function() {
			if(!this.calendar) {
				var cal = new Element("table").setProperties({
					'id': this.options.calendarId,
					'border':'0',
					'cellpadding':'0',
					'cellspacing':'0'
				});
				cal.addClass('datePicker');
		    $(cal.insertRow(0).insertCell(0)).appendText("x");
				for (var c=0;c<6;c++) $(cal.rows[0]).adopt(cal.rows[0].cells[0].cloneNode(true));
				for (var r=0;r<7;r++) $(cal.rows[0].parentNode).adopt(cal.rows[0].cloneNode(true));
				$(cal.rows[1]).addClass('dayNames');
				for (var r=2;r<8;r++) $(cal.rows[r]).addClass('dayRow');
				for (var d=0;d<7;d++) cal.rows[1].cells[d].firstChild.data = this.options.days[d];
				for (var t=6;t>3;t--) cal.rows[0].deleteCell(t);
				$(cal.rows[0]).addClass('dateNav');
				if(!window.ie6)cal.rows[0].cells[0].firstChild.data=String.fromCharCode(9668);
				else cal.rows[0].cells[0].firstChild.data="<";
				cal.rows[0].cells[1].colSpan=4;
				if(!window.ie6) cal.rows[0].cells[2].firstChild.data=String.fromCharCode(9658);
				else cal.rows[0].cells[2].firstChild.data=">";
				cal.rows[0].cells[3].firstChild.data=String.fromCharCode(215);
				$(cal.rows[0].cells[3].setHTML('')).adopt(this.getCloseImg());
					//xb.adopt(xb.previousSibling);
				cal.addEvent('click', this.clickCalendar.bind(this));
				this.calendar = cal;
				this.container = new Element('div').adopt(cal).addClass('calendarHolder');
				//make stickywin
				this.options.stickyWinOptions.content = this.container;
				this.options.stickyWinOptions.showNow = false;
				this.options.stickyWinOptions.relativeTo = this.input;
				this.stickyWin = new this.options.stickyWinToUse(this.options.stickyWinOptions);
				if(this.options.draggable) {
					try {
						this.stickyWin.win.makeDraggable(Object.extend(this.options.dragOptions, {
							handle:cal.rows[0].cells[1],
							onDrag:function(){
								if(this.stickyWin.shim) this.stickyWin.shim.show.bind(this.stickyWin)
							}.bind(this)
						}));
						cal.rows[0].cells[1].setStyle('cursor', 'move');
					} catch(e) {}//drag isn't available
				}
			}
			return this.calendar;
		},
/*	Properties: getCloseImg
		Returns an img object to use for the close funciton.
		
		You can use <Class.implement> to redefine this so that it returns a dom element of your choosing.
		You will need to add your own call to <DatePicker.hide>.
	*/
		getCloseImg: function(){
			var closer = new Element("img").setProperty('src',
					'http://www.cnet.com/html/rb/assets/global/simple.error.popup/closebtn.gif');
			closer.addEvents({
				'mouseover': function(){
					closer.src = closer.src.replace('.gif', '_over.gif');
				},
				'mouseout':function(){
					closer.src = closer.src.replace('_over.gif', '.gif');
				},
				'click': this.hide.bind(this)
			}).setStyles({
				width: '13px',
				height: '13px'
			}).addClass('closebtn');
			return closer;
		},
		
/*	Property: hide
		Hides the calendar popup.
	*/
		hide: function(){
			this.stickyWin.hide();
			this.fireEvent('onHide');
		},
/*	Property: show
		Shows the calendar popup. This will reposition the popup and display the date that the user has entered or today's date if they have not entered anything.
	*/
		show: function(){
	    this.today = this.zeroHourGMT(new Date());
			this.inputDate = new Date(this.updateInput());
	    this.refDate = isNaN(this.inputDate) ? this.today : this.zeroHourGMT(new Date(this.inputDate));
			this.getCalendar();
	    this.fillCalendar(this.refDate);
			this.stickyWin.show();
			this.fireEvent('onShow');
		},
		clickCalendar: function(e) {
			e = new Event(e);
			if (!e.target.firstChild || !e.target.firstChild.data) return;
			var val = e.target.firstChild.data;
			if (val.charCodeAt(0) > 9600 || val == "<" || val == ">") {
				var newRef = this.calendar.rows[2].cells[0].refDate - this.fullDay;
				if (val.charCodeAt(0) != 9668 && val != "<") newRef = this.calendar.rows[7].cells[6].refDate + this.fullDay;
				this.fillCalendar(new Date(newRef));
				return;
			}
			if (e.target.refDate) {
				var newDate = new Date(e.target.refDate);
				this.input.value = this.formatDate(newDate);
				/* trip onchange events in text field */
				this.input.fireEvent("change");
				this.input.fireEvent("blur");
				this.fireEvent('onPick');
				if(this.options.hideCalendarOnPick) this.hide();
			}
		},
		fillCalendar: function (forDate) {
			var startDate = new Date(forDate.getTime());
			startDate.setUTCDate(1);
			startDate.setTime(startDate.getTime() - (this.fullDay * startDate.getUTCDay()));
			this.calendar.rows[0].cells[1].firstChild.data = this.options.months[forDate.getUTCMonth()] + " " + forDate.getUTCFullYear();
			var atDate = startDate;
			this.calendar.getElements('td').each(function (el){
				el.removeClass('selectedDate').removeClass('otherMonthDate').removeClass('today');
			});
			for (var w=2; w<8; w++) for (var d=0; d<7; d++) {
				var td = this.calendar.rows[w].cells[d];
				td.firstChild.data = atDate.getUTCDate();
				td.refDate = atDate.getTime();
				if(atDate.getTime() == this.today.getTime()) td.addClass('today');
				if(atDate.getTime() == this.refDate.getTime()) td.addClass('selectedDate');
				if(atDate.getUTCMonth() != forDate.getUTCMonth()) td.addClass('otherMonthDate');
				atDate.setTime(atDate.getTime() + this.fullDay);
			}
		}
	});
/*	Note:
		DatePicker implements <Options> and <Events>.
	*/
	DatePicker.implement(new Options);
	DatePicker.implement(new Events);
/* do not edit below this line */   
/* Section: Change Log 

$Source: /cvs/main/flatfile/html/rb/js/global/cnet.global.framework/common/js.widgets/date.picker.js,v $
$Log: date.picker.js,v $
Revision 1.11  2007/07/18 16:15:21  newtona
forgot to bind the style objects in the setText.attempt method...

Revision 1.10  2007/07/16 21:00:21  newtona
using Element.setText for all style injection methods (fixes IE6 problems)
moving Element.setText to element.legacy.js; this function is in Mootools 1.11, but if your environment is running 1.0 you'll need this.

Revision 1.9  2007/05/16 20:17:52  newtona
changing window.onDomReady to window.addEvent('domready'

Revision 1.8  2007/03/08 23:29:31  newtona
date picker: strict javascript warnings cleaned up
popup details strict javascript warnings cleaned up
product.picker: strict javascript warnings cleaned up, updating input now fires onchange event
confirmer: new file

Revision 1.7  2007/02/27 21:46:43  newtona
docs update; fixing references

Revision 1.6  2007/02/21 00:27:08  newtona
switched Class.create to Class.empty

Revision 1.5  2007/02/07 20:51:41  newtona
implemented Options class
implemented Events class
StickyWin now uses Element.position

Revision 1.4  2007/02/03 01:36:41  newtona
fixed a fireevent bug

Revision 1.3  2007/01/29 23:50:53  newtona
additional bug fixes and tweaks. stable now.

Revision 1.2  2007/01/27 01:51:36  newtona
numerous ie6 fixes.

Revision 1.1  2007/01/26 21:55:04  newtona
*** empty log message ***


*/
/*
Script: popupdetails.js
Handles popup detail templated elements.

Dependancies:
	 mootools - 	<Moo.js>, <Utility.js>, <Function.js>, <Array.js>, <String.js>, <Element.js>
	 cnet libraries - <dbug.js>, <simple.template.parser.js>, <stickyWin.js>, <stickyWinFx.js>
	 optional - <stickyWin.Modal.js>, <stickyWinFx.Drag.js>

Author:
	Aaron Newton, <aaron [dot] newton [at] cnet [dot] com>
	
Class: PopupDetail
A PopupDetail instance is a <StickyWin> that uses <simpleTemplateParser> to parse details into a template, all of which is related to a dom element. When the user mouses over (or clicks, depending on the options you specify) the dom element, a <StickyWin> will appear near it (again, you specify the offset and whatnot). The <StickyWin> will then disappear if the user mouses out of the element, unless they move their mouse over the <StickyWin>, in which case it will hang around until they mouse out of that (this is yet another option).

Arguments:
html - html contents for the popup; can be a template (see <simpleTemplateParser>)
options - an object with key/value options

Options (optional unless noted otherwise):
observer - (required) the dom element that this PopupDetail relates to
observerAction - either "mouseover" or "click"; the action the user must perform 
	on the obsever to make the <StickyWin> appear; defaults to "mouseover"
closeOnMouseOut - close the <StickyWin> when the user mouses out of the dom element
  or the <StickyWin>; defaults to true
linkPopup - make the whole <StickyWin> popup clickable; true will take the user to
  the .href value of the observer element, a (string) url will use that instead, and
	false makes the <StickyWin> not clickable. Note that if you want the user to be
	able to interact with content in the <StickyWin> (even to select it), this must be
	false; defaults to false
data - optional a key/value object of data to parse into the html of the popup; see <simpleTemplateParser>
templateOptions - options for the <simpleTemplateParser>
useAjax - get the data from an ajax request; defaults to false
ajaxOptions - optional key/value object for use with the <Ajax> call; see that object for option details
ajaxLink - url to use to get json values for data; defaults to the observer.href
delayOn - (integer) time to wait after the user interacts with the observer before showing the popup; defaults to 0
delayOff - (integer) time to wait after the user mouses out (if that is in effect) 
	the observer before hiding the popup; defaults to 0
stickyWinOptions - the options object to pass to the instance of <stickWin>
stickyWinToUse: - the StickyWin Class to use; either <StickyWin>, <StickyWinFx>, <StickyWinModal> or <StickyWinFxModal>; note, this value is *not* in quotes. It is not a string, it is a variable pointing to the class.
showNow - show the PopupDetail on instantiation
*/

var PopupDetail = new Class({
	visible: false,
	observed: false,
	hasData: false,
	initialize: function(html, options){
		this.setOptions({
			observer: false, //or element
			observerAction: 'mouseover', //or click
			closeOnMouseOut: true,
			linkPopup: false, //or true to use observer href, or url
			data: {}, //key/value parse to parse in to html
			templateOptions: {}, //see simple template parser
			useAjax: false,
			ajaxOptions:{},
			ajaxLink: false, //defaults to use observer.src
			delayOn: 100,
			delayOff: 100,
			stickyWinOptions:{},
			stickyWinToUse: StickyWinFx,
			showNow: false
		}, options);
		this.html = ($(html))?$(html).innerHTML:html;
		if(this.options.showNow) this.show.delay(this.options.delayOn, this);
		this.setUpObservers();
	},
	setUpObservers: function(){
		var opt = this.options; //saving bytes here
		if($(opt.observer) && opt.observerAction) {
			$(opt.observer).addEvent(opt.observerAction, function(){
				this.observed = true;
				this.show.delay(opt.delayOn, this);
			}.bind(this));
			if(opt.observerAction == "mouseover" && this.options.closeOnMouseOut){
				$(opt.observer).addEvent("mouseout", function(){
					this.observed = false;
					this.hide.delay(opt.delayOff, this);
				}.bind(this));
			}
		}
		return this;
	},
	makePopup: function(){
		if(!this.stickyWin){
			var opt = this.options;//saving bytes
			this.content = this.parseTemplate(this.html, opt.data);
			this.stickyWin = new opt.stickyWinToUse($merge(opt.stickyWinOptions, {
				relativeTo: opt.observer || document.body,
				showNow: false,
				content: this.content,
				allowMultipleByClass: true
			}));
			if($(opt.linkPopup) || $type(opt.linkPopup)=='string') {
				this.stickyWin.win.setStyle('cursor','pointer').addEvent('click', function(){
					window.location.href = ($type(url)=='string')?url:url.src;
				});
			}
			this.stickyWin.win.addEvent('mouseover', function(){
				this.observed = true;
			}.bind(this));
			this.stickyWin.win.addEvent('mouseout', function(){
				this.observed = false;
				if(opt.closeOnMouseOut) this.hide.delay(opt.delayOff, this);
			}.bind(this));
		}
		return this;
	},
	getContent: function(){
	//todo, check for data already
		try {
			new Ajax((this.options.ajaxLink || this.options.observer.href), $merge(this.options.ajaxOptions, {
					onComplete: this.show.bind(this)
				})
			).request();
		} catch(e) {
			dbug.log('ajax error on PopupDetail: %s', e);
		}
	},
/*	Property: show
		Makes the PopupDetail visible.
		
		Arguments:
		jsonObject - (optional) jsonData to parse into the contents of the popup
		
		Note: 
		The jsonObject is really meant to be passed in for ajax requests. This is internal
		to the class; you should just call .show() with no arguments.
	*/
	show: function(jsonObject){
		var opt = this.options;
		if(jsonObject) this.jsonData = jsonObject;
		if(this.observed && !this.visible) {
			if(opt.useAjax && !this.jsonData) {
				this.cursorStyle = $(opt.observer).getStyle('cursor');
				$(opt.observer).setStyle('cursor', 'wait');
				this.getContent();
				return false;
			} else {
				if(this.cursorStyle) $(opt.observer).setStyle('cursor', this.cursorStyle);
				if(opt.useAjax) opt.data = Json.evaluate(this.jsonData);
				this.makePopup();
				this.stickyWin.show();
				this.visible = true;
				return this;
			}
		}
		return this;
	},
/*	Property: hide
		Hides the popup.
	*/
	hide: function(){
		if(!this.observed){
			if(this.stickyWin)this.stickyWin.hide();
			this.visible = false;
		}
		return this;
	}
});
PopupDetail.implement(simpleTemplateParser);
PopupDetail.implement(new Options);
//legacy namespace
var popupDetail = PopupDetail;
/*
Class: PopupDetailCollection
Creates a collection of <PopupDetail> objects with the arrays of dom elements and data objects you specify, using a common template.

Arguments:
	options - an object containing options.

Options:
	details - (array, required) an array of objects containing key/value data for each popup (see below)
	observers - (array, required) the items you want the user to interact with to show the popup
	links - (array, optional) an array of links or of anchor tags to link the whole popup to; defaults to
		observer.href
	ajaxLinks - (array, optional) if in popupDetailOptions you specify useAjax = true, you must also pass
	  it a url; ajaxLinks is an array of links, one for each PopupDetail, to retrieve the data for the
		popup from the server
	template - (string or dom element, required) the html template or an id of a DOM element 
		(or a DOM element reference) that contains it. This template will be parsed with the data 
		of each item (see <simpleTemplateParser>) and then displayed
	popupDetailOptions - (optional) key/value options object to be passed to each instance of
		<PopupDetail> that is created for each observer. Note that this class overrides this 
		options object with the data, observer, template, and link using the template specified 
		in the options object and the corresponding values in the details, observers, and links
		arrays you pass in.

	
Example:
(start code)
var fruitDetails = [
	{name: 'apple',
	 color: 'red'
	},
	{name: 'lemon',
	 color: 'yellow'
	}
];

<div id="popupDetailHTML">
	<dl>
		<dt>%name%</dt>
		<dd>%color%</dd>
	</dl>
</div>

<a href="http://all.about.apples.com">apples</a>
<a href="http://all.about.lemons.com">lemons</a>

window.onDomReady(function(){ //wait for the DOM to be ready
	new PopupDetailCollection({
		details: fruitDetails,
		observers: $$('a'), //all the links
		template: 'popupDetailHTML',

		//the rest here is entirely optional
		popupDetailOptions: {	//configure the PopupDetail object
			linkPopup: true,
			delayOn: 100,
			delayOff: 200,
			stickyWinOptions: {
				zIndex: 999,
				className: 'fruitStickyWin',
				position: 'upperRight',
				offset: {x: 100, y: 200},
				//limit the dimensions of the iframe shim to the first dl object
				//in the popup
				iframeShimSelector: 'dl' 
			}
		}
	});
});
(end)

Now when the user mouses over the link for 100ms, a popup will appear 100px to the right and 200px below the upper right corner of the link with the appropriate content.
 */
var PopupDetailCollection = new Class({
	popupDetailObjs: [],
	initialize: function(options) {
		this.setOptions({
			details: [],
			observers: [],
			links: [],
			ajaxLinks: [],
			template: '',
			popupDetailOptions: {}
		}, options);
		var ln = this.options.ajaxLinks.length;
		if(ln <= 0) ln = this.options.details.length;
		if (this.options.observers.length != ln) 
			dbug.log("warning: observers and details are out of synch");
		this.makePopupDetails();
	},
	makePopupDetails: function(){
		this.popupDetailObjs = this.options.observers.map(function(observer, index){
			var opt = this.options.popupDetailOptions;//saving bytes
			var pd = new PopupDetail(this.options.template, $merge(opt, {
				data: $pick(this.options.details[index], {}),
				observer: this.options.observers[index],
				linkItem: $pick(this.options.links[index], $pick(opt.linkItem, false)),
				ajaxLink: $pick(this.options.ajaxLinks[index], false)
			}));
			return pd;
		}, this);
	}
});
PopupDetailCollection.implement(new Options);

//legacy names
/*	Class: popupDetails (deprecated)
Fades in a DHTML popup with html formatted details.

IMPORTANT:
*This is deprecated; use <popupDetailCollection>.*

Arguments:
	options - an object containing options.

Options:
	details - the object of details (see below)
	observers - the items you want the user to interact with to show the popup
	links - an array of links or of anchor tags to link the whole popup to
	observerAction - either "mouseover" or "click" to determine what the user
	            has to do to show the item. Note: if you choose click, you must
							set your own overserver to hide the item. Defaults to mouseover.
	listName - (deprecated)
	template - the html template or an id of a DOM element that contains it. This template
							will be parsed with the data of each item and then displayed.
	offsety - the vertical offset of the item from the observer
	offsetx - the horizontal offset
	effectDurationOn - (deprecated)
	effectDurationOff - (deprecated)
	effectDelayOn - for mouseover, how long to wait after the user mousesover before you show the element
	effectDelayOff - for mouseout, how long to wait after the user mousesout before you show the element
	iframeShimSelector - the css selector *within your template* that should have 
											 an iframe shim under it to obscure select lists and the like.
	linkItems - boolean; make the whole popup clickable (defaults to true)
	
Example:
(start code)
	var titleDetails = [
		{name: 'apple',
		 color: 'red'
		},
		{name: 'lemon',
		 color: 'yellow'
		}
	];
	
	<div id="popupDetailHTML">
		<dl>
			<dt>%name%</dt>
			<dd>%color%</dd>
		</dl>
	</div>


	var dlListingPopups = null;
  window.onDomReady(function(){
		//instantiate our object
		dlListingPopups = new popDetailsList({
			details: titleDetails, 
			observers: $$("table#dl-tbl-list th.titleCell a.prod"), 
			links: prodLinks, 
			observeCorner: "upperRight", 
			observerAction: "mouseover", 
			listName: "dlListingPopups", 
			template: "popupDetailHTML", 
			offsety: -145, 
			offsetx: 30, 
			effectDurationOn: 250, 
			effectDurationOff: 150, 
			effectDelayOn: 500, 
			effectDelayOff: 1000, 
			iframeShimSelector: "div.popupDetails" 
		});
	}); 
(end)	*/
var popupDetails = new Class({
	initialize: function(options){
		var pdcOptions = Object.extend(options,{
				popupDetailOptions: {
					stickyWinOptions: {
						position: $pick(options.observeCorner, 'upperLeft'),
						offset: {
							x: options.offsetx || 0,
							y: options.offsety || 0
						},
						useIframeShim: (options.iframeShimSelector)?true:false
					}
				},
				delayOn: $pick(options.effectDelayOn, 0),
				delayOff: $pick(options.effectDelayOff, 0)
			});
		var pdc = new popupDetailCollection(pdcOptions);
		return pdc;
	}
});
var popDetailsList = popupDetails;
/* do not edit below this line */   
/* Section: Change Log 

$Source: /cvs/main/flatfile/html/rb/js/global/cnet.global.framework/common/js.widgets/popupdetails.js,v $
$Log: popupdetails.js,v $
Revision 1.12  2007/03/08 23:29:31  newtona
date picker: strict javascript warnings cleaned up
popup details strict javascript warnings cleaned up
product.picker: strict javascript warnings cleaned up, updating input now fires onchange event
confirmer: new file

Revision 1.11  2007/03/08 02:42:32  newtona
removed $copy; old and deprecated function

Revision 1.10  2007/02/08 01:29:36  newtona
fixed syntax error

Revision 1.9  2007/02/07 20:51:41  newtona
implemented Options class
implemented Events class
StickyWin now uses Element.position

Revision 1.8  2007/01/29 23:51:10  newtona
using $copy now

Revision 1.7  2007/01/26 05:48:45  newtona
syntax update for mootools 1.0

Revision 1.6  2007/01/23 20:54:43  newtona
numerous bug fixes. tested and seems stable now.

Revision 1.5  2007/01/22 22:00:15  newtona
numerous bug fixes to modalizer, stickywin, and popupdetails
updated for mootools 1.0
fixed date validation in form.validator

Revision 1.4  2007/01/19 01:22:21  newtona
fixed a few syntax errors

Revision 1.3  2007/01/11 22:31:23  newtona
doc changes

Revision 1.2  2007/01/11 20:55:23  newtona
changed the way options are set, split up stickywin into 4 files, refactored popupdetails to use stickywin and modalizer

Revision 1.1  2007/01/09 02:39:35  newtona
renamed addons directory to "common" directory

Revision 1.5  2007/01/09 01:25:24  newtona
changed $S to $$

Revision 1.4  2007/01/05 18:08:19  newtona
swapped Event.onDomReady for Window.onDomReady,
removed template parsing into new class (SimpleTemplateParser) and integrated
removed references to Fx.Opacity
fixed bug with setStyle command

Revision 1.3  2006/11/04 00:53:25  newtona
added better options handling, documentation

Revision 1.2  2006/11/03 18:56:06  newtona
added some documentation

Revision 1.1  2006/11/02 21:28:08  newtona
checking in for the first time.


*/
/*	
Script: carousel.js
	Builds a carousel object that manages the basic functions of a generic carousel (a carousel
	here being a collection of "slides" that play from one to the next, with a collection of
	"buttons" that reference each slide).
	
Dependancies:
	 mootools - <Moo.js>, <String.js>, <Array.js>, <Function.js>, <Element.js>, <Dom.js>
	
Author:
	Aaron Newton, <aaron [dot] newton [at] cnet [dot] com>
	


Class: CNETcarousel
	This class is for the standard cnet carousel for doors on our
	network. Instantiate this carousel class, configured to your names and
	preferences, and you're done. You can have as many on a page as
	you like.
	
	This class should work for any type of layout provided that:
	- The carousel is made up of buttons and slides, and there are
		an equal amount of both.
	- The buttons have an "on state" class and an "off state" class
	- The slides are "on top" of each other; this class fades one
		out and fades another in. It does not create a slide or position
		it.
	
Arguments:
  container - a DOM element containing the slides and buttons
	options - optional, an object containing options.

Options:
	carouselContainer - the id of the parent element that contains
											the carousel (which is typically hidden in css
											with visibility: hidden)
											default: "Carousel"
	slidesSelector 		- the css selector for the slide elements
											note: this is relative to the carouselContainer object,
											so only elements that match this selector within that object
											will be included in the carousel
											default: ".slide"
	buttonsSelector 	- the css selector for the buttons; same rules as slidesSelector
											default: ".button"
	startIndex 				- the first item to show
											default: 0
	buttonOnClass 		- the class for the "on" state of the buttons
											default: "selected",
	buttonOffClass	 	- the class for the "off" state of the buttons
											default: "off",
	rotateAction 			- the action the user takes to rotate to the next button;
											options: mouseover, click, or none
											default: 'none'
	rotateActionDuration - the duration to use when the user interacts with the buttons
	 										if rotateAction != "none". default: 100
	slideInterval 		- the interval between slide rotations in the slideshow
											default: 4000
	transitionDuration - the duration of the transition effect
											default: 700
	autoplay 					-  turn the slideshow on on instantiation
											default: true	
	
	
	Examples:
	>var testCrsl = null;
	>window.addEvent('domready', {
	>	testCrsl = new CNETcarousel({});
	>});
	>
	>OR
	>...
	>	testCrsl = new CNETcarousel({
	> 	slideInterval: 8000,
	>		rotateAction: 'mouseover',
	>		etc...
	>	});
	
	HTML layout example:
	(start code)
		<div id="Carousel">
			<!-- SLIDE #1 -->
			<div class="slide dark">
				...slide stuff goes here...
			</div>
			<!-- SlIDE #2 -->
			...
			<!-- SlIDE #3 -->
			...
			<!-- SlIDE #4 -->
			...
		
			<div class="bubbles">
				<div class="button">
					... bubble text or whatever goes here...
				</div>
			</div>
			<!-- BUTTON #2 -->
			<!-- BUTTON #3 -->
			<!-- BUTTON #4 -->
		</div>
	(end)
-- */
var CNETcarousel = new Class({
	initialize: function(container, options){
		this.container = $(container);
		if(!this.container.hasClass('hasCarousel')){
			this.container.addClass('hasCarousel');
			this.slides = [];
			this.buttons = [];
			this.setOptions({
				onRotate: Class.empty,
				onStop: Class.empty,
				onAutoPlay: Class.empty,
				onShowSlide: Class.empty,
				slidesSelector: ".slide",
				buttonsSelector: ".button",
				slideInterval: 4000,
				transitionDuration: 700,
				startIndex: 0,
				buttonOnClass: "selected",
				buttonOffClass: "off",
				rotateAction: "none",
				rotateActionDuration: 100,
				autoplay: true
			}, options);
			this.slides = $(container).getElements(this.options.slidesSelector);
			this.buttons = $(container).getElements(this.options.buttonsSelector);
			this.createFx();
			this.showSlide(this.options.startIndex);
			if(this.options.autoplay) this.autoplay();
			if(this.options.rotateAction != 'none') this.setupAction(this.options.rotateAction);
			return this;
		} else return false;
	},
/*
Property: setupAction
	*Private internal function; do not call directly.*
	Applies <showSlide>	to the user action.
	
Arguments:
	string - the action to apply the slide change to; 'click' or 'mouseover'
	*/
	setupAction: function(action) {
		this.buttons.each(function(el, idx){
			$(el).addEvent(action, function() {
				this.slideFx.setOptions(this.slideFx.options, {duration: this.options.rotateActionDuration});
				if(this.currentSlide != idx) this.showSlide(idx);
				this.stop();
			}.bind(this));
		}, this);
	},
/*	
Property: createFx
	*Private internal function; do not call directly.*
	Creates the effects objects for each slide and stores them in this.slideFx array.	*/
	createFx: function(){
		this.slideFx = new Fx.Elements(this.slides, {duration: this.options.transitionDuration});
		this.slides.each(function(slide){
			slide.setStyle('opacity',0);
		});
	},
/*	
Property: showSlide
	*Private internal function; do not call directly.*
	Shows a slide (and hides the others).
		
Arguments:
	slideIndex - the slide index to show
		
Example:
	>myCarousel.showSlide(0) //shows the first slide
	*/
	showSlide: function(slideIndex){
		var action = {};
		this.slides.each(function(slide, index){
			if(index == slideIndex && index != this.currentSlide){ //show
				$(this.buttons[index]).removeClass(this.options.buttonOffClass).addClass(this.options.buttonOnClass);
				action[index.toString()] = {
					'opacity': [1]
				};
			} else {
				$(this.buttons[index]).removeClass(this.options.buttonOnClass).addClass(this.options.buttonOffClass);
				action[index.toString()] = {
					'opacity':[0]
				};
			}
		}, this);
		this.fireEvent('onShowSlide', slideIndex);
		this.currentSlide = slideIndex;
		this.slideFx.start(action);
	},
	
/*	
Property: autoplay
	Turns autoplay on.
	
Example:
	>myCarousel.autoplay() //start cycling slides
	*/	
	autoplay: function(){
		this.createFx();
		this.slideshowInt = this.rotate.periodical(this.options.slideInterval, this);
		this.fireEvent('onAutoPlay');
	},
/*	
Property: stop
	Stops autoplaying the slides.
	
Example:
	>myCarousel.stop() //stop cycling slides
	*/
	stop: function(){
		clearInterval(this.slideshowInt);
		this.fireEvent('onStop');
	},
/*	
Property: rotate
	*Private internal function; do not call directly.*
	Progresses to the next slide.	*/
	rotate: function(){
		current = this.currentSlide;
		next = (current+1 >= this.slides.length) ? 0 : current+1;
		this.showSlide(next);
		this.fireEvent('onRotate');
	},
/*	
Property: show
	Shows the carousel component (visibility: visible);	
	
	>myCarousel.show() //makes the carousel visible
	*/
	show: function() {
		$(this.options.carouselContainer).setStyle('visibility','visible');
		if(!$(this.options.carouselContainer).isVisible())$(this.options.carouselContainer).setStyle('display','block');
	},
/*	
Property: hide
	Hides the carousel component (visibility: hidden);
	
Example:
	>myCarousel.hide() //makes the carousel invisible
		*/
	hide: function(){
		$(this.options.carouselContainer).setStyle('visibility','hidden');
	}
});
CNETcarousel.implement(new Options);
CNETcarousel.implement(new Events);

/*	Class: CNETcarouselWithButtons
		Extends <CNETcarousel> to include button imgs that are rotated with the slides.
		
		Arguments:
		el - the element containing the carousel
		options - the options object
		
		Options:
		bubbleButtonBGImgSelector - (optional) the selector to find the images inside the carousel container.
				defaults to ".bbg".
		buttonOnGifSrc - (optional) the url to the "on" button. defaults to
				http://i.i.com.com/cnwk.1d/i/fd/c/green_button.gif
		buttonOffSrc - (optional) the url to the "off" button. defaults to
				http://i.i.com.com/cnwk.1d/i/fd/c/gray_button.gif

		See <CNETcarousel> for additional options.
			*/

var CNETcarouselWithButtons = CNETcarousel.extend({
	initialize:function(el, options){
		this.parent(el, $merge({
			bubbleButtonBGImgSelector: '.bbg',
			buttonOnGifSrc: 'http://i.i.com.com/cnwk.1d/i/fd/c/green_button.gif',
			buttonOffGifSrc: 'http://i.i.com.com/cnwk.1d/i/fd/c/gray_button.gif'
		}, options));
	},
	showSlide: function(slideIndex){
		this.buttons.each(function(button, index){
			$(button).getElement(this.options.bubbleButtonBGImgSelector).src = (index == slideIndex)?this.options.buttonOnGifSrc:this.options.buttonOffGifSrc;
		}, this);
		this.parent(slideIndex);
	}
});
var carousel = null;
window.addEvent('domready', function(){
	if($('Carousel')) {
		carousel = new CNETcarouselWithButtons($('Carousel'),{buttonsSelector:'.bubble', rotateAction:'mouseover'});
	}
});
/* do not edit below this line */   
/* Section: Change Log 

$Source: /cvs/main/flatfile/html/rb/js/global/cnet.global.framework/common/layout.widgets/carousel.js,v $
$Log: carousel.js,v $
Revision 1.8  2007/05/29 22:01:53  newtona
Split element.cnet.js into seperate files; updated docs in files to note this
Changed element.visible to element.isVisible (left old namespace for legacy support)
Fixed Element.empty in prototype.compatibility.js
Removed as many dependencies in common code to element.*.js as possible (espeically element.shortcuts.js)

Revision 1.7  2007/05/16 20:17:52  newtona
changing window.onDomReady to window.addEvent('domready'

Revision 1.6  2007/02/21 00:27:50  newtona
switched Class.create to Class.empty

Revision 1.5  2007/02/07 20:51:55  newtona
implemented Options class
implemented Events class

Revision 1.4  2007/01/26 05:51:42  newtona
syntax update for mootools 1.0
refactored to use Fx.Elements.js
docs update

Revision 1.3  2007/01/22 21:56:08  newtona
updated for mootools version 1.0

Revision 1.2  2007/01/19 01:22:54  newtona
changed event.ondomready > window.ondomready

Revision 1.1  2007/01/09 02:39:35  newtona
renamed addons directory to "common" directory

Revision 1.3  2006/12/06 20:14:59  newtona
carousel - improved performance, changed some syntax, actually deployed into usage and tested
cnet.nav.accordion - improved css selectors for time
multiple accordion - fixed a typo
dbug.js - added load timers
element.cnet.js - changed syntax to utilize mootools more effectively
function.cnet.js - equated $set to $pick in preparation for mootools v1

Revision 1.2  2006/12/04 18:36:32  newtona
fixed a few syntax bugs, added subclass version with background images

Revision 1.1  2006/11/02 21:28:08  newtona
checking in for the first time.


*/
/*	Script: multiple.open.accordion.js
		Creates a Mootools <Fx.Accordion> that allows the user to open more than one element.
		
		Dependancies:
			 mootools - 	<Moo.js>, <Function.js>, <Array.js>, <String.js>, <Element.js>, <Fx.Base.js>, <Fx.Elements.js>, <Fx.Styles.js>, <Fx.Style.js>
			
		Author:
			Aaron Newton, <aaron [dot] newton [at] cnet [dot] com>

		
		Class: MultipleOpenAccordion
		Extends the <Fx.Elements> class from Mootools for an accordion element that allows
		the user to open more than one element.
		
		Arguments:
		togglers - elements that activate each section
		elements - the elements to resize
		options - the options object of key/value settings
		
		Options:
		openAll - (boolean) open all elements on startup; defaults to true.
		allowMultipleOpen - (boolean) allows users to open more than one element at a time; defaults to true.
		firstElementsOpen - (array) an array of elements to open on startup;
				only used if openAll = false and allowMultipleOpen = true;
				defaults to [0]; can be empty ([]) to signifiy that all should be closed;
		start - (string) 'first-open' slides open each element in firstElementsOpen;
										 'open-first' opens each element in firstElementsOpen immediately using no effects (default)
		fixedHeight - integer, if you want your accordion to have a fixed height. defaults to false.
		fixedWidth - integer, if you want your accordion to have a fixed width. defaults to false.
		alwaysHide - boolean, if you want the ability to close your only-open item. defaults to true.
		wait - boolean. means that open and close transitions can cancel current ones (so if you click
		 on items before the previous finishes transitioning, the clicked transition will fire canceling the previous). 
		 true means that if one element is sliding open or closed, clicking on another will have no effect. 
		 for Accordion defaults to false.
		onActive - function to execute when an element starts to show; passed arguments: (toggler, section)
		onBackground - function to execute when an element starts to hide; passed arguments: (toggler, section)
		height - boolean, will add a height transition to the accordion if true. defaults to true.
		opacity - boolean, will add an opacity transition to the accordion if true. defaults to true.
		width - boolean, will add a width transition to the accordion if true. defaults to false, 
						css mastery is required to make this work!
	*/
var MultipleOpenAccordion = Fx.Elements.extend({
	options: {
		openAll: true,
		allowMultipleOpen: true,
		firstElementsOpen: [0],
		start: 'open-first',
		fixedHeight: false,
		fixedWidth: false,
		alwaysHide: true,
		wait: false,
		onActive: Class.empty,
		onBackground: Class.empty,
		height: true,
		opacity: true,
		width: false
	},
	initialize: function(togglers, elements, options){
		this.parent(elements, options);
		this.setOptions(options);
		this.previousClick = null;
		this.elementsVisible = [];
		togglers.each(function(tog, i){
			$(tog).addEvent('click', function(){this.toggleSection(i)}.bind(this));
		}, this);
		this.togglers = togglers;
		this.h = {}; 
		this.w = {};
		this.o = {};
		this.now = [];
		this.elements.each(function(el, i){
			el = $(el);
			this.now[i] = {};
			el.setStyle('overflow','hidden');
			if(!(this.options.openAll && this.options.allowMultipleOpen)) el.setStyle('height', 0);
		}, this);
		if(!this.options.openAll || !this.options.allowMultipleOpen) {
			switch(this.options.start){
				case 'first-open': this.showSection(this.options.firstElementsOpen[0]); break;
				case 'open-first': this.toggleSection(this.options.firstElementsOpen[0]); break;
			}
		}
		if (this.options.openAll && this.options.allowMultipleOpen) this.showAll();
		else if (this.options.allowMultipleOpen) this.openSections(this.options.firstElementsOpen);
	},
	hideThis: function(i){ //sets up the effects for hiding an element
		this.elementsVisible[i] = false;
		if (this.options.height) this.h = {'height': [this.elements[i].offsetHeight, 0]};
		if (this.options.width) this.w = {'width': [this.elements[i].offsetWidth, 0]};
		if (this.options.opacity) this.o = {'opacity': [this.now[i]['opacity'] || 1, 0]};
		this.fireEvent("onBackground", [this.togglers[i], this.elements[i]]);
	},

	showThis: function(i){ //sets up the effects for showing an element
		this.elementsVisible[i] = true;
		if (this.options.height) this.h = {'height': [this.elements[i].offsetHeight, this.options.fixedHeight || this.elements[i].scrollHeight]};
		if (this.options.width) this.w = {'width': [this.elements[i].offsetWidth, this.options.fixedWidth || this.elements[i].scrollWidth]};
		if (this.options.opacity) this.o = {'opacity': [this.now[i]['opacity'] || 0, 1]};
		this.fireEvent("onActive", [this.togglers[i], this.elements[i]]);
	},
/*	Property: toggleSection
		Opens or closes a section depending on its state and the options of the Accordion.
		
		Argumetns:
		iToToggle - (integer) the index of the section to open or close
	*/
	toggleSection: function(iToToggle){
		//let's open an object, or close it, depending on it's state
		//now, if the index to toggle isn't the previous click
		//or we're going to allow items to be closed (so that all of them are closed
		//or we're allowing more than one item to be open at a time, continue
		//otherwise, we're looking at an item that was just clicked, and it should already be open
		if(iToToggle != this.previousClick || this.options.alwaysHide || this.options.allowMultipleOpen) {
			//save the previous click
			this.previousClick = iToToggle;
			var objObjs = {};
			var err = false;
			//go through each element
			this.elements.each(function(el, i){
				var update = false;
				//set up it's now state
				this.now[i] = this.now[i] || {};
				//if the element is the one clicked
				if(i==iToToggle){
					//if the element is visible, hide it if we allow alwaysHide or multiple
					if (this.elementsVisible[i] && (this.options.allowMultipleOpen || this.options.alwaysHide)){
						//if ! wait and timer
						if(!(this.options.wait && this.timer)) {
							//hide it
							update = true;
							this.hideThis(i);
						} else {
							this.previousClick = null;
							err = true;
						}
					} else if(!this.elementsVisible[i]){
					//else if hidden, show it
						//if ! wait and timer
						if(!(this.options.wait && this.timer)) {
							//show it
							update = true;
							this.showThis(i);
						} else {
							this.previousClick = null;
							err = true;
						}
					}
				} else if(this.elementsVisible[i] && !this.options.allowMultipleOpen) {
				//else (not clicked) if it's visible, hide it, unless we allow multiple open
					//if ! wait and timer
					if(!(this.options.wait && this.timer)) {
						//hide it
						update = true;
						this.hideThis(i);
					} else {
						this.previousClick = null;
						err = true;
					}
				} //else it's not clicked, it's not open, so leave it alone because we allow multiples
				//set up the effect instructions
				if(update) objObjs[i] = $merge(this.h, $merge(this.o, this.w));
			}, this);
			//if there's an error, just stop
			if (err) return false;
			//execute the custom function, which resizes everything.
			return this.custom(objObjs);
		}
		return false;
	},
/*	Property: showSection
		Opens a section of the accordion if it's not open already.
		
		Arguments:
		i - (integer) the index of the section to show
		useFx - (boolean) open it immediately (false) or slide it open using the effects (true);  defaults to false;
	*/
	showSection: function(i, useFx){
		if($pick(useFx, false)) {
			if (!this.elementsVisible[i]) this.toggleSection(i);
		} else {
			this.setSectionStyle(i,$(this.elements[i]).scrollWidth, $(this.elements[i]).scrollHeight, 1);
			this.elementsVisible[i] = true;
			this.fireEvent("onActive", [this.togglers[i], this.elements[i]]);
		}
	},
/*	Property: hideSection
		Closes a section of the accordion if it's not closed already.
		
		Arguments:
		i - (integer) the index of the section to hide
		useFx - (boolean) close it immediately (false) or slide it closed using the effects (true);  defaults to false;
	*/
	hideSection: function(i, useFx){
		if($pick(useFx, false)) {	
			if (this.elementsVisible[i]) this.toggleSection(i);
		} else {
			this.setSectionStyle(i,0,0,0);
			this.elementsVisible[i] = false;
			this.fireEvent("onBackground", [this.togglers[i], this.elements[i]]);
		}
	},
	//internal function; sets a section (i) to the width (w), height (h), and opacity (o) passed in
	setSectionStyle: function(i,w,h,o){ 
			if (this.options.opacity) $(this.elements[i]).setOpacity(o);
			if (this.options.height) $(this.elements[i]).setStyle('height',h+'px');
			if (this.options.width) $(this.elements[i]).setStyle('width',w+'px');
	},
/*	Property: showAll
		Opens all the elements in the accordion immediately; used on startup	*/
	showAll: function(){
		if(this.options.allowMultipleOpen){
			this.elements.each(function(el,idx){
					this.showSection(idx, false);
			}, this);
		}
	},
/*	Property: hideAll
		Closes all the elements in the accordion immediately; used on startup	*/
	hideAll: function(){
		if(this.options.allowMultipleOpen){
			this.elements.each(function(el,idx){
				this.hideSection(idx, false);
			}, this);
		}
	},
/*	Property: openSection
		Opens specific sections of the accordion immediately; used on startup.
		
		Arguments:
		sections - array of indexes to open.
	*/
	openSections: function(sections) {
		if(this.options.allowMultipleOpen){
			this.elements.each(function(el,idx){
				if(sections.test(idx)) this.showSection(idx, false);
				else this.hideSection(idx, false);
			}, this);
		}
	}
});
MultipleOpenAccordion.implement(new Options);
MultipleOpenAccordion.implement(new Events);
/* do not edit below this line */   

/* Section: Change Log 

$Source: /cvs/main/flatfile/html/rb/js/global/cnet.global.framework/common/layout.widgets/multiple.open.accordion.js,v $
$Log: multiple.open.accordion.js,v $
Revision 1.8  2007/06/21 20:20:29  newtona
multiopenaccordion showall and hideall weren't working; closing bug 296095

Revision 1.7  2007/05/29 20:34:34  newtona
refactored a lot; fixed issues with onBackground and onActive events.

Revision 1.6  2007/04/04 17:28:53  newtona
subtle syntax error fix.

Revision 1.5  2007/03/08 23:29:59  newtona
strict javascript warnings cleaned up

Revision 1.4  2007/02/27 21:46:43  newtona
docs update; fixing references

Revision 1.3  2007/02/07 20:51:55  newtona
implemented Options class
implemented Events class

Revision 1.2  2007/01/26 05:53:47  newtona
syntax update for mootools 1.0

Revision 1.1  2007/01/22 21:59:03  newtona
moved from fx.multiple.open.accordion.js

Revision 1.1  2007/01/09 02:39:35  newtona
renamed addons directory to "common" directory

Revision 1.5  2006/12/06 20:14:59  newtona
carousel - improved performance, changed some syntax, actually deployed into usage and tested
cnet.nav.accordion - improved css selectors for time
multiple accordion - fixed a typo
dbug.js - added load timers
element.cnet.js - changed syntax to utilize mootools more effectively
function.cnet.js - equated $set to $pick in preparation for mootools v1

Revision 1.4  2006/11/06 19:19:31  newtona
fixed a bug and removed some dbug.log statements

Revision 1.3  2006/11/04 01:35:27  newtona
removing a dbug line

Revision 1.2  2006/11/04 00:53:45  newtona
no change

Revision 1.1  2006/11/02 21:28:08  newtona
checking in for the first time.


*/
/*
Script: mouseovers.js
Collection of mouseover behaviours (images, class toggles, etc.).
These functions handle standard mouseover behaviour.
		
Dependancies:
	 mootools - <Moo.js>, <Utility.js>, <String.js>, <Array.js>, <Function.js>, <Element.js>, <Dom.js>
	
Author:
	Aaron Newton, <aaron [dot] newton [at] cnet [dot] com>


Function: imgMouseOverEvents
		handles hover states for images. Producers simply author all their 
		images to have an on version and an off version with same naming 
		conventions, then call this function with those conventions and 
		a css selector. All images that match that selector will get the 
		mouseover behavior applied to them automatically.

Example:
		(start code)
		<img src="myimg_off.gif" class="autoMouseOver">
		assuming that my hover image is the same path with _off
		   substituted with _on; so: myimg_on.gif is the hover version
		<script>
			imgMouseOverEvents('_off', '_on', 'img.autoMouseOver');
		</script>
		(end)
		You can call this function as soon as the DOM is ready.
		
		Note:
		The default instance of this function is included in this library.
		If producers name their on/off state files with "_on" and "_off"
		in the file names and give their images the class "autoMouseOver"
		then they don't have to write any javascript. This also works for
		inputs.
		
		Automatically executed versions:
		img.autoMouseOverOff - swaps '_off' for '_over'
		img.autoMouseOver - swaps '_off' for '_on'
		input.autoMouseOver - swaps '_off' for '_on'
		
		Arguments:
		outString - the string to substitute for the on string when the user mouses out
		overString - the string to substitute for the out string when the users mouses over
		selector - css selector to apply this behaviour
		
		See Also: <tabMouseOvers>
	*/
function imgMouseOverEvents(outString, overString, selector) {
	$$(selector).each(function(image) {
		image = $(image);
		if ($type(image.src)) {
			if (image.src.indexOf(outString) > 0) {
				image.addEvent('mouseover',function(){
					image.src = image.src.replace(outString, overString);
				}).addEvent('mouseout', function(){ 
					image.src = image.src.replace(overString, outString);
				});
			}
		}
	});
};
window.addEvent('domready', function(){imgMouseOverEvents('_off', '_over', 'img.autoMouseOverOff, input.autoMouseOverOff');});
window.addEvent('domready', function(){imgMouseOverEvents('_off', '_on', 'img.autoMouseOver, input.autoMouseOver');});

/*	
Function: tabMouseOvers
		tabMouseOvers are almost identical to <imgMouseOverEvents>.
		this function will swap out one css class for another when the
		user mouses over a dom element (doesn't have to be a tab layout)
		You also have the option of having the class of the DOM element
		change when the user mouses over a child of the DOM element that's
		supposed to toggle (for instance, if your tab has a link in it,
		you can have the tab change when the user mouses over the anchor
		instead of the whole tab).
		
		pass in the css class for the 'on' and 'off states, as well as 
		the css selector for the DOM element, and, optionally, the selector
		for the sub elements for the mouseover action.
		
		you can also optionally set applyToBoth to set the mouseover to both
		the selector and the subselector if you like
		
		Arguments:
		cssOn - the "on" state for the tab; this css class will be added 
						when the user mouses over the element.
		cssOff - the "off" state for the tab
		selector - the selector for all the tabs
		subselector - the selector for any sub elements that you wish to attach
						the mouseover behavior to
		applyToBoth - a boolean; if you want to apply the mouseover behavior
						to both the selector and the subselector; false = just the
						subselector
		
		example:
		><ul id="myTabs">
		>	<li><a href="1">one</a></li>
		>	<li><a href="2">two</a></li>
		>	<li><a href="3">three</a></li>
		></ul>
		><script>
		>	tabMouseOvers('on', 'off', '#myTabs li", "a", false);
		></script>
		
		now, when the user mouses over the anchor tags, the parent li object
		will get the class "on" added to it.
		
		note that those last two, the subselector and the applyToBoth are optional
*/
function tabMouseOvers(cssOn, cssOff, selector, subselector, applyToBoth){
	$$(selector).each(function(el){
		el.applyToBoth = $pick(applyToBoth, false);
		if(applyToBoth && subselector) {
			el.getElementsBySelector(subselector).each(function(el){
				el.addClass(cssOff).removeClass(cssOn);
			});
		}
		el.addClass(cssOff).removeClass(cssOn);
		el.addEvent('mouseover', function(){
			this.addClass(cssOn).removeClass(cssOff);
			if(applyToBoth && subselector) {
				this.getElementsBySelector(subselector).each(function(subel){
					subel.addClass(cssOn).removeClass(cssOff);
				});
			}
		});
		el.addEvent('mouseout', function(){
			this.addClass(cssOff).removeClass(cssOn);
			if(applyToBoth && subselector) {
				$A(this.getElementsBySelector(subselector)).each(function(subel){
					subel.addClass(cssOff).removeClass(cssOn);
				});
			}
		});
	});
};
/* do not edit below this line */   
/* Section: Change Log 

$Source: /cvs/main/flatfile/html/rb/js/global/cnet.global.framework/common/layout.widgets/mouseovers.js,v $
$Log: mouseovers.js,v $
Revision 1.8  2007/05/16 20:17:52  newtona
changing window.onDomReady to window.addEvent('domready'

Revision 1.7  2007/02/27 19:41:10  newtona
accidentally tied the wrong css class to the default states for mouseovers. fixed.

Revision 1.6  2007/02/03 01:38:53  newtona
cleaned up the default entries (autoMouseOverOff)
added some docs about these default entries

Revision 1.5  2007/01/26 05:52:32  newtona
syntax update for mootools 1.0
fixed a bug

Revision 1.4  2007/01/23 00:11:59  newtona
fixed a syntax error

Revision 1.3  2007/01/22 21:59:36  newtona
updated for mootools 1.0

Revision 1.2  2007/01/11 20:55:47  newtona
fixed syntax error with Window.onDomReady

Revision 1.1  2007/01/09 02:39:35  newtona
renamed addons directory to "common" directory

Revision 1.5  2007/01/09 01:26:49  newtona
changed $S to $$

Revision 1.4  2007/01/05 19:31:30  newtona
swapped out Event.onDomReady for Window.onDOMReady

Revision 1.3  2006/11/03 18:45:36  newtona
found conflict on tips page
http://help.dldev2.cnet.com:8006/9611-12576_39-0.html?tag=button1&nodeId=6501&jsdebug=true

in imgMouseOverEvents

added this line:

image = $(image);

To apply Mootools Element properties to each image as I apply them

Revision 1.2  2006/11/02 21:26:42  newtona
checking in commerce release version of global framework.

notable changes here:
cnet.functions.js is the only file really modified, the rest are just getting cvs footers (again).

cnet.functions adds numerous new classes:

$type.isNumber
$type.isSet
$set

*//*
Script: tabswapper.js
Handles the scripting for a common UI layout; the tabbed box.

Dependancies:
	mootools - 	<Moo.js>, <Utility.js>, <Function.js>, <Array.js>, <String.js>, <Element.js>, <Fx.Base.js>, <Dom.js>, <Cookie.js>
	cnet - <element.shortcuts.js>
	
Author:
	Aaron Newton, <aaron [dot] newton [at] cnet [dot] com>

Class: TabSwapper
		Handles the scripting for a common UI layout; the tabbed box.
		If you have a set of dom elements that are going to toggle visibility based
		on the related tabs above them (they don't have to be above, but usually are)
		you can instantiate a TabSwapper and it's handled for you.
		
		Example:
		
		><ul id="myTabs">
		>	<li><a href="1">one</a></li>
		>	<li><a href="2">two</a></li>
		>	<li><a href="3">three</a></li>
		></ul>
		><div id="myContent">
		>	<div>content 1</div>
		>	<div>content 2</div>
		>	<div>content 3</div>
		></div>
		><script>
		>	var myTabSwapper = new TabSwapper({
		>		selectedClass: "on",
		>		deselectedClass: "off",
		>		mouseoverClass: "over",
		>		mouseoutClass: "out",
		>		tabSelector: "#myTabs li",
		>		clickSelector: "#myTabs li a",
		>		sectionSelector: "#myContent div",
		>		smooth: true,
		>		cookieName: "rememberMe"
		>	});
		></script>
		
		Notes:
		 - you don't have to specify the classes for mouseover/out
		 - you don't have to specify a click selector; it'll just
		   use the tab DOM elements if you don't give it the click
			 selector
		 - the click selector is NOT a subselector of the tabs; be sure
		   to specify a full css selector for these
		 - smooth: is off by default; adds some nice transitional effects
		 - cookieName: will store the users's last selected tab in a cookie
		   and restore this tab when they next visit
			 
Arguments:
	options - optional, an object containing options.

Options:
			selectedClass - the class for the tab when it is selected
			deselectedClass - the class for the tab when it isn't selected
			mouseoverClass - the class for the tab when the user mouses over
			tabs - (array) an array of DOM elements for the tabs (these get the above classes added to them when the user interacts with the interface); can also be a <$$> selector (string).
			clickers - (optional, array) an array of DOM elements for the clickers; if your tab contains a child DOM element that the user clicks - not the whole tab but an element within it - to switch the content, pass in an array of them here. If you don't pass these in, the array of tabs is used instead (the default). Can also be a <$$> selector (string).
			sections - (array) an array of DOM elements for the sections (these change when the clickers are clicked); can also be a <$$> selector (string).
			initPanel - the panel to show on init; 0 is default (optional)
			smooth - use effects to smooth transitions; false is default (optional)
			cookieName - if defined, the browser will remember their previous selection
					 	using a cookie (optional)
			cookieDays - how many days to remember this? default is 999, but it's
						ignored if cookieName isn't set (optional)
			effectOptions - the options to pass on to the transition effect if the "smooth" option is set to true; defaults to {duration: 500}
			onBackground - callback executed when a section is hidden; passed three arguments: the index of the section, the section, and the tab
			onActive - callback executed when a section is shown; passed three arguments: the index of the section, the section, and the tab
			onActiveAfterFx - callback executed when a section is shown but after the effects have completed (so it's visible to the user); passed three arguments: the index of the section, the section, and the tab
	*/

var TabSwapper = new Class({
	tabs: [],
	sections: [],
	clickers: [],
	options: {
		selectedClass: 'tabSelected',
		mouseoverClass: 'tabOver',
		deselectedClass: '',
		tabs: [],
		clickers: [],
		sections: [],
		initPanel: 0, 
		smooth: false, 
		effectOptions: {
			duration: 500
		},
		cookieName: null, 
		cookieDays: 999,
		onActive: Class.empty,
		onActiveAfterFx: Class.empty,
		onBackground: Class.empty
		
	},
	initialize: function(options){
		options = this.compatability(options);
		this.setOptions(options);
		this.sectionOpacities = [];
		this.setup();

		if(this.options.cookieName && this.recall()) this.swap(this.recall().toInt());
		else this.swap(this.options.initPanel);
	},
	compatability: function(options){
		if(options.tabSelector){
			options.tabs = $$(options.tabSelector);
			options.sections = $$(options.sectionSelector);
			options.clickers = $$(options.clickSelector);
		}
		return options;
	},
	setup: function(){
		var opt = this.options;
		sections = $$(opt.sections);
		tabs = $$(opt.tabs);
		clickers = $$(opt.clickers);
		tabs.each(function(tab, index){
			this.addTab(tab, sections[index], clickers[index], index);
		}, this);
	},
/*	Property; addTab
		Adds a tab to the interface.
		
		Arguments:
		tab - (DOM element) the tab; (see Options)
		clicker - (DOM element) the clicker
		section - (DOM element) the section
		index - (integer, optional) where to insert this tab; defaults to the last place (i.e. push)
	*/
	addTab: function(tab, section, clicker, index){
		tab = $(tab); clicker = $(clicker); section = $(section);
		//if the tab is already in the interface, just move it
		if(this.tabs.indexOf(tab) >= 0 && tab.getProperty('tabbered') && this.tabs.indexOf(tab) != index) {
			this.moveTab(this.tabs.indexOf(tab), index);
			return;
		}
		//if the index isn't specified, put the tab at the end
		if(!$defined(index)) index = this.tabs.length;
		//if this isn't the first item, and there's a tab
		//already in the interface at the index 1 less than this
		//insert this after that one
		if(index > 0 && this.tabs[index-1]) {
			tab.injectAfter(this.tabs[index-1]);
			section.injectAfter(this.sections[index-1]);
		}
		this.tabs.splice(index, 0, tab);
		this.sections.splice(index, 0, section);
		clicker = clicker || tab;
		this.clickers.splice(index, 0, clicker);

		tab.addEvent('mouseout',function(){
			tab.removeClass(this.options.mouseoverClass);
		}.bind(this)).addEvent('mouseover', function(){
			tab.addClass(this.options.mouseoverClass);
		}.bind(this));

		clicker.addEvent('click', function(){
			this.swap(this.clickers.indexOf(clicker));
		}.bind(this));

		tab.setProperty('tabbered', true);
		this.hideSection(index);
		return;
	},
/*	Property: removeTab
	Removes a tab from the TabSwapper; does NOT remove the DOM elements for the tab or section from the DOM.

	Arguments:
	index - (integer) the index of the tab to remove.
 */
	removeTab: function(index){
		var now = this.tabs[this.now];
		if(this.now == index){
			if(index > 0) this.swap(index - 1);
			else if (index < this.tabs.length) this.swap(index + 1);
		}
		this.sections.splice(index, 1);
		this.tabs.splice(index, 1);
		this.clickers.splice(index, 1);
		this.sectionOpacities.splice(index, 1);
		this.now = this.tabs.indexOf(now);
	},
/*	Property: moveTab
		Moves a tab's index from one location to another.
		
		Arguments:
		from - (integer) the index of the tab to move
		to - (integer) its new location
	*/
	moveTab: function(from, to){
		var tab = this.tabs[from];
		var clicker = this.clickers[from];
		var section = this.sections[from];
		
		var toTab = this.tabs[to];
		var toClicker = this.clickers[to];
		var toSection = this.sections[to];
		
		this.tabs.remove(tab).splice(to, 0, tab);
		this.clickers.remove(clicker).splice(to, 0, clicker);
		this.sections.remove(section).splice(to, 0, section);
		
		tab.injectBefore(toTab);
		clicker.injectBefore(toClicker);
		section.injectBefore(toSection);
	},
/*	Property: swap
		Swaps the view from one tab to another.
		
		Arguments:
		swapIdx - (integer) the index of the tab to show.
	*/
	swap: function(swapIdx){
		this.sections.each(function(sect, idx){
			if(swapIdx == idx) this.showSection(idx);
			else this.hideSection(idx);
		}, this);
		if(this.options.cookieName) this.save(swapIdx);
	},
	save: function(index){
		Cookie.set(this.options.cookieName, index, {duration:this.options.cookieDays});
	},
	recall: function(){
		return $pick(Cookie.get(this.options.cookieName), false);
	},
	hideSection: function(idx) {
		this.sections[idx].setStyle('display','none');
		this.tabs[idx].removeClass(this.options.selectedClass).addClass(this.options.deselectedClass);
		this.fireEvent('onBackground', [idx, this.sections[idx], this.tabs[idx]]);
	},
	showSection: function(idx) {
		var sect = this.sections[idx];
		if(this.now != idx) {
			if (!this.sectionOpacities[idx]) this.sectionOpacities[idx] = this.sections[idx].effect('opacity', this.options.effectOptions);
			sect.setStyles({
				display:'block',
				opacity: 0
			});
			if(this.options.smooth && (!window.ie6 || (window.ie6 && sect.fxOpacityOk())))
				this.sectionOpacities[idx].start(0,1).chain(function(){
					this.fireEvent('onActiveAfterFx', [idx, this.sections[idx], this.tabs[idx]]);
				}.bind(this));
			else if(sect.getStyle('opacity') < 1) {
				this.sectionOpacities[idx].set(1);
				this.fireEvent('onActiveAfterFx', [idx, this.sections[idx], this.tabs[idx]]);
			}
			this.now = idx;
			this.fireEvent('onActive', [idx, this.sections[idx], this.tabs[idx]]);
		}
		this.tabs[idx].addClass(this.options.selectedClass).removeClass(this.options.deselectedClass);
	}
});
TabSwapper.implement(new Options);
TabSwapper.implement(new Events);
//legacy namespace
var tabSwapper = TabSwapper;
/* do not edit below this line */   
/* Section: Change Log 

$Source: /cvs/main/flatfile/html/rb/js/global/cnet.global.framework/common/layout.widgets/tabswapper.js,v $
$Log: tabswapper.js,v $
Revision 1.17  2007/07/05 16:40:10  newtona
dramatic refactor of tabswapper; now tabs can be added, removed, moved. Additionally, you can now pass in for tabs, sections, and clickers a dom collection or a selector.

Revision 1.16  2007/06/28 00:33:28  newtona
dangit. typo (extra close paren)

Revision 1.15  2007/06/28 00:31:03  newtona
tweaking the event timing in tabswapper

Revision 1.14  2007/06/28 00:11:21  newtona
typo in tabswapper; index instead of idx

Revision 1.13  2007/06/27 22:56:47  newtona
doc update in tabswapper

Revision 1.12  2007/06/27 22:45:21  newtona
docs update to overfiew.js
tabswapper gets some events action
fixed a typo in the docs for smoothmove

Revision 1.11  2007/05/29 22:01:53  newtona
Split element.cnet.js into seperate files; updated docs in files to note this
Changed element.visible to element.isVisible (left old namespace for legacy support)
Fixed Element.empty in prototype.compatibility.js
Removed as many dependencies in common code to element.*.js as possible (espeically element.shortcuts.js)

Revision 1.10  2007/04/12 23:47:34  newtona
fixed a bug where if you defined tabSelector but not clickSelector, things went whacky; now it acts as it should - if !clickSelector then clickSelector = tabSelector

Revision 1.9  2007/03/28 18:08:35  newtona
tabswapper now uses Element.fxOpacityOk to deal with the IE bug where text gets blurry when you fade an element in and out without a bgcolor set

Revision 1.8  2007/03/23 17:59:39  newtona
tabswapper no longer cmplains about this.recall() on load

Revision 1.7  2007/03/16 17:18:41  newtona
transitions no longer used for ie6

Revision 1.6  2007/02/27 19:40:42  newtona
enforcing element.show to use display block

Revision 1.5  2007/02/07 20:51:55  newtona
implemented Options class
implemented Events class

Revision 1.4  2007/01/26 05:53:33  newtona
syntax update for mootools 1.0
docs update
renamed tabSwapper - > TabSwapper

Revision 1.3  2007/01/22 22:49:48  newtona
updated cookie.set syntax

Revision 1.2  2007/01/22 21:59:19  newtona
updated for mootools 1.0

Revision 1.1  2007/01/09 02:39:35  newtona
renamed addons directory to "common" directory

Revision 1.4  2007/01/09 01:26:49  newtona
changed $S to $$

Revision 1.3  2006/11/21 23:55:56  newtona
optimization update

Revision 1.2  2006/11/02 21:26:42  newtona
checking in commerce release version of global framework.

notable changes here:
cnet.functions.js is the only file really modified, the rest are just getting cvs footers (again).

cnet.functions adds numerous new classes:

$type.isNumber
$type.isSet
$set

*//*	Script: simple.slideshow.js
		Makes a very, very simple slideshow gallery with a collection of dom elements and previous and next buttons.
		
		Author:
		Aaron Newton
		
		Dependencies:
		mootools - 	<Moo.js>, <Utility.js>, <Common.js>, <Function.js>, <Array.js>, <String.js>, <Element.js>, <Fx.Base.js>, <Dom.js>, <Cookie.js>

		Class: SimpleSlideShow
		Makes a very, very simple slideshow gallery with a collection of dom elements and previous and next buttons.
		
		Arguments:
		options - an object with key/value settings.
		
		Options:
		startIndex - (integer) the first image to show
		slides - (array) a collection of elements already in the dom.
		currentSlideClass - (string; optional) class to assign the currently visible slide; defaults to "currentSlide"
		currentIndexContainer - (dom element or id) container to display the the currently shown slide index
			(i.e. "showing *2* of 3"); optional
		maxContainer - (dom element or id) container to display the maximum number of slides available; optional
		nextImg - (dom element or id) image to capture clicks to show the next image; optional, but if 
			not supplied you'll have to execute <cycleForward> yourself.
		prevImg - (dom element or id) image to capture clicks to show the next image; optional, but if 
			not supplied you'll have to execute <cycleBack> yourself.
		wrap - (boolean) when the user clicks next at the end of a set, go back to the start 
			(and if they click prev at the begining, go to the end); defaults to true
		disabledLinkClass - (string) class to add to next/prev links when there are no next or prev slides;
			defaults to "disabled"
		onNext - (function) callback for when the user clicks next; optional
		onPrev - (function) callback for when the user clicks prev; optional
		onSlideClick - (function) callback for when the user clicks a slide, this function will 
			be passed the slide clicked and the index of the slide. optional
		crossFadeOptions - (object) options object to be passed to the opacity effects.
		
		Example:
(start code)
new SimpleSlideShow({
  startIndex: 0,
	slides: $$('.slide'),
  currentIndexContainer: 'slideNow', //an element or it's id
  maxContainer: 'slideMax',
  nextLink: 'nextImg',
  prevLink: 'prevImg'
});
(end)
	*/
	
	var SimpleSlideShow = new Class({
		options: {
			startIndex: 0,
			slides: [],
			currentSlideClass: 'currentSlide',
			currentIndexContainer: false,
			maxContainer: false,
			nextLink: false,
			prevLink: false,
			wrap: true,
			disabledLinkClass: 'disabled',
			onNext: Class.empty,
			onPrev: Class.empty,
			onSlideClick: Class.empty,
			crossFadeOptions: {}
		},
		initialize: function(options){
			this.setOptions(options);
			this.slides = this.options.slides;
			this.makeSlides();
			this.setCounters();
			this.setUpNav();
			this.now = this.options.startIndex;
			if(this.slides.length > 0) this.showSlide(this.now);
		},
		setCounters: function(){
			if($(this.options.currentIndexContainer))$(this.options.currentIndexContainer).setHTML(this.now+1);
			if($(this.options.maxContainer))$(this.options.maxContainer).setHTML(this.slides.length);
		},
		makeSlides: function(){
			//hide them all
			this.slides.each(function(slide, index){
				if(index != this.now) slide.setStyle('display', 'none');
				else slide.setStyle('display', 'block');
				this.makeSlide(slide);
			}, this);
		},
		makeSlide: function(slide){
			slide.addEvent('click', function(){ this.fireEvent('onSlideClick'); }.bind(this));
		},
		setUpNav: function(){	
			if($(this.options.nextLink)) $(this.options.nextLink).addEvent('click', function(){
					this.cycleForward();
				}.bind(this));
			if($(this.options.prevLink)) $(this.options.prevLink).addEvent('click', function(){
					this.cycleBack();
				}.bind(this));
		},
/*	Property: cycleForward
		Shows the next slide.
	*/
		cycleForward: function(){
			if($type(this.now) && this.now < this.slides.length-1) this.showSlide(this.now+1);
			else if($type(this.now) && this.options.wrap) this.showSlide(0);
			else this.showSlide(this.options.startIndex);
			this.fireEvent('onNext');
			if(this.now == this.slides.length && !this.options.wrap && $(this.options.nextLink))
				$(this.options.nextLink).addClass(this.options.disabledLinkClass);
			else if ($(this.options.nextLink)) $(this.options.nextLink).removeClass(this.options.disabledLinkClass);
		},
/*	Property: cycleBack
		Shows the prev slide.
	*/
		cycleBack: function(){
			if(this.now > 0) this.showSlide(this.now-1);
			else if(this.options.wrap) this.showSlide(this.slides.length-1);
			this.fireEvent('onPrev');
			if(this.now == 0 && !this.options.wrap && $(this.options.prevSlide))
				$(this.options.prevSlide).addClass(this.options.disabledLinkClass);
			else if ($(this.options.prevSlide)) 
				$(this.options.prevSlide).removeClass(this.options.disabledLinkClass);
		},
/*	Property: showSlide
		Shows a specific slide.
		
		Arguments:
		iToShow - (integer) index of the slide to show.
	*/
		showSlide: function(iToShow){
			var now = this.now;
			var s = this.slides[iToShow]; //saving bytes
			function fadeIn(s, resetOpacity){
				s.setStyle('display','block');
				if(s.fxOpacityOk()) {
					if(resetOpacity) s.setStyle('opacity', 0);
					s.effect('opacity', this.options.crossFadeOptions).start(1);
				}
			};
			if(s) {
				if($type(this.now) && this.now != iToShow){
					if(s.fxOpacityOk()) {
						this.slides[this.now].effect('opacity', this.options.crossFadeOptions).start(0).chain(function(){
							this.slides[now].setStyle('display','none');
							s.addClass(this.options.currentSlideClass);
							fadeIn.bind(this, [s, true])();
						}.bind(this));
					} else {
						this.slides[this.now].setStyle('display','none');
						fadeIn.bind(this, s)();
					}
				} else fadeIn.bind(this, s)();
				this.now = iToShow;
				this.setCounters();
			}
		},
		slideClick: function(){
			this.fireEvent('onSlideClick', [this.slides[this.now], this.now]);
		}
	});
	SimpleSlideShow.implement(new Events);
	SimpleSlideShow.implement(new Options);

/*	Class: SimpleImageSlideShow
		Extends <SimpleSlideShow> to make a slideshow of images.
		
		Arguments:
		options - a key/value options object; inherits options from <SimpleSlideShow>.
		
		Options:
		imgUrls - (array; optional) an array of image urls to add to the dom and to the slideshow
		imgClass - (string; optional) a class to add to the images that get created on the fly
		container - (element; optional) if you are adding images to the dom either using <addImg> or
			the imgUrls array above, then "container" is required to know where to put them.
	*/
	var SimpleImageSlideShow = SimpleSlideShow.extend({
		options: {
			imgUrls: [],
			imgClass: 'screenshot',
			container: false
		},
		initialize: function(options){
			this.parent(options);
			this.options.imgUrls.each(function(url){
				this.addImg(url);
			}, this);
			this.showSlide(this.options.startIndex);
		},
/*	Property: addImg
		Adds a new image to the group
	*/
		addImg: function(url){
			if($(this.options.container)) {
				var img = new Element('img').setProperties({
							'src': url,
							'id': this.options.imgClass+this.slides.length
							}).addClass(this.options.imgClass).setStyle(
							'display', 'none').injectInside($(this.options.container)).addEvent(
							'click', this.slideClick.bind(this));
				this.slides.push(img);
				this.makeSlide(img);
				this.setCounters();
			}
		}
	});

/* do not edit below this line */   
/* Section: Change Log 

$Source: /cvs/main/flatfile/html/rb/js/global/cnet.global.framework/common/layout.widgets/simple.slideshow.js,v $
$Log: simple.slideshow.js,v $
Revision 1.9  2007/05/29 22:01:53  newtona
Split element.cnet.js into seperate files; updated docs in files to note this
Changed element.visible to element.isVisible (left old namespace for legacy support)
Fixed Element.empty in prototype.compatibility.js
Removed as many dependencies in common code to element.*.js as possible (espeically element.shortcuts.js)

Revision 1.8  2007/04/03 00:12:41  newtona
fixed a binding issue with slideshow

Revision 1.7  2007/03/29 22:37:54  newtona
simple slide show now only cross-fades in ie6 if the element has a bgcolor (see Element.fxOpacityOk)

Revision 1.6  2007/03/20 21:30:21  newtona
slideshow now checks to see if there are any slides before it attempts to show one.

Revision 1.5  2007/03/19 22:26:38  newtona
start slide is now shown on initialization

Revision 1.4  2007/03/08 23:29:59  newtona
strict javascript warnings cleaned up

Revision 1.3  2007/02/21 00:29:17  newtona
switched Class.create to Class.empty

Revision 1.2  2007/02/12 17:46:31  newtona
tweaking things, no significant functional changes

Revision 1.1  2007/02/09 20:23:19  newtona
moving simple.img.gallery.js to simple.slideshow.js
rewrote gallery to do dom elements or images


*/
/*	Script: jsonp.js
		Creates a Json request using a script tag include and handles the callbacks for you.
		
		Dependencies:
		Mootools - <Moo.js>, <Array.js>, <String.js>, <Function.js>, <Utility.js>, <Element.js>, <Common.js>
		
		Author:
		Aaron Newton <aaron [dot] newton [at] cnet [dot] com>
		
		Class: JsonP
		Creates a Json request using a script tag include and handles the callbacks for you.
		
		Arguments:
		url - the url to get the json data
		options - an object with key/value options
		
		Options:
		onComplete - (optional) function to execute when the data returns; it will be passed the data
		callBackKey - (string) the key in the url that the server uses to wrap the Json results. 
				So, for example, if you used "callBackKey: 'callback'" then the server is expecting
				something like http://..../?q=search+term&callback=myFunction
				defaults to "callback". This must be defined correctly.
		queryString - (string, optional) additional query string values to append to the url
		data - (object, optional) additional key/value data to append to the url
		
		Example:
(start code)
new JsonP('http://api.cnet.com/restApi/v1.0/techProductSearch', {
	data: {
		partTag: 'mtvo',
		iod: 'hlPrice',
		iewType: 'json',
		results: '100',
		query: 'ipod'
	},
	onComplete: myFunction.bind(someObject)
}).request();
(end)

		The above example would generate this url:
(start code) http://api.cnet.com/restApi/v1.0/techProductSearch?partTag=mtvo&iod=hlPrice&viewType=json&results=100&query=ipod&callback=JsonP.requestors[0].handleResults&
(end)

		It would embed this script tag (in the head of the document) and, when it loaded, execute the "myFunction"
		callback defined.
	*/
var JsonP = new Class({
	options: {
		onComplete: Class.empty,
		callBackKey: "callback",
		queryString: "",
		data: {}
	},
	initialize: function(url, options){
		this.setOptions(options);
		this.url = this.makeUrl(url);
		this.fired = false;
	},
/*	Property: request
		Executes the Json request.
	*/
	request: function(){
		dbug.log('retrieving by json script method: %s', this.url);
		var dl = (window.ie)?50:0; //for some reason, IE needs a moment here...
		(function(){
			this.script = new Asset.javascript(this.url);
			this.fired = true;
		}.bind(this)).delay(dl);
	},
	makeUrl: function(url){
		var index = JsonP.requestors.push(this) - 1;
		var separator = (url.test('\\?'))?'&':'?';
		var jurl = url + separator + this.options.callBackKey + "=JsonP.requestors[" +
				index+"].handleResults";
		if(this.options.queryString) jurl += "&"+this.options.queryString;
		jurl += "&"+Object.toQueryString(this.options.data);
		return jurl;
	},
	handleResults: function(data){
		this.fireEvent('onComplete', data);
		this.script.remove();
	}
});
JsonP.requestors = [];
JsonP.implement(new Options);
JsonP.implement(new Events);

/* do not edit below this line */   
/* Section: Change Log 

$Source: /cvs/main/flatfile/html/rb/js/global/cnet.global.framework/common/utilities/jsonp.js,v $
$Log: jsonp.js,v $
Revision 1.5  2007/06/21 17:44:04  newtona
fixed a typo; same line was duplicated and I removed the errant one.

Revision 1.4  2007/03/05 19:30:46  newtona
added a short (50ms) delay for IE

Revision 1.3  2007/02/27 21:46:43  newtona
docs update; fixing references

Revision 1.2  2007/02/22 23:58:33  newtona
fixed a bug with the queryString option

Revision 1.1  2007/02/21 00:30:59  newtona
first commit


*/
/*	Script: product.picker.js
		Allows the user to pick a product from a data source.
		
		Author:
		Aaron Newton <aaron [dot] newton [at] cnet [dot] com>
		
		Dependencies:
		mootools - <Moo.js>, <Utility.js>, <Common.js>, <Function.js>, <Element.js>, <Array.js>, <String.js>, <Event.js>
		cnet - <Drag.Base.js>, <stickyWinFx.js>, <jsonp.js>, <element.shortcuts.js>
		
		Note:
		This script contains no <Picklet>s. This means that it isn't very useful unless you write your own
		or include some (such as <CNETProductPicker> or <NewsStoryPicker>
		
		Class: Picklet
		Container for all the information required to allow the user to search a data source and pick a result.
		
		Arguments:
		className - (string, required) the className associated with the picklet
		options - (object, required) a key/value object of options for the picklet
		
		Options:
			(all options are required unless otherwise noted)
			
			url - (string) the base url for the data source, defaults to the CNET API
						(http://api.cnet.com/restApi/v1.0/techProductSearch)
			descriptiveName - (string) the name to show in the select list of picklets available for an input
			callBackKey - (string) the wrapper for <JsonP> (see it for details), defaults to 'callback'
			data - (object) an object of key/value pairs to pass along with the request in the url
			getQuery - (function) returns an <Ajax> or <JsonP> object that has not yet been executed. Will 
									be passed the data in the above object and the data generated by the user input into
									the search form.
			inputs - (object) an object of key/value pairs for inputs for the form. See below for details.
			previewHtml - (function) a function that is passed a data result from the ajax/json results that will
								 return the html for the preview using that data object.
			resultsList - (function) a function that is passed the response from the ajax/json object above that
								 will turn that response into a list of items (an array) to be chosen from.
			listItemName - (function) a function that is passed a single item from the resultsList and returns
								 the name to be displayed in the list (string).
			listItemValue - (function) a functino that is passed a single item from the resultsList and returns
									the value that will be set to the value of the input for the picker.
			updateInput - (function) a function that is passed the input and the data item that the user selected
									that then does something with that selection (typically updating the input)
									
		Inputs:

			The inputs for the search form in the picker can be anything. Each input is described in the inputs
			object that is an option of the picklet (see the options>inputs section above).
			
			Each item in the inputs must be defined thusly. First, the key of the object will be used for the
			name of the input (i.e. inputs: {query: ....} will result in <input name="query"...>).
			
			The values for the object will be translated into the input and its descriptors and properties.
			
			tagName - the name of the tag ('input', 'select', etc.);
			type - the type of the input ('text', 'hidden', etc'
			instructions - text that will be displayed to the left of the input
			tip - tool-tip info that is displayed on hover. Format: CAPTION::TIP DEFINITION
			value - the value of the input by default. If type is 'select', this is an array of values.
			optionNames - if the type is 'select', an array of the option text values (what the user sees)
			style - an object of style properties for the input (i.e. style: {width: "100%"})
			
		Example:
			Here is an example picklet in it's entirety:
(start code)
var CNETProductPicker = new Picklet('CNETProductPicker',{
	url: 'http://api.cnet.com/restApi/v1.0/techProductSearch',
	descriptiveName: 'CNET Product Picker Sortable',
	callBackKey: 'callback', //see <JsonP> options
	data: {
		partKey: 'YOUR PARTNER KEY FROM API.CNET.COM',
		iod: 'hlPrice',
		viewType: 'json',
		sortDesc: 'true'
	}, //static data
	getQuery: function(data){ //return <Ajax> or <JsonP>
		return new JsonP(this.options.url, {
			callBackKey: this.options.callBackKey,
			data: $merge(this.options.data, data)
		});
	},
	inputs: {
		query: {
			tagName: 'input',
			type: 'text',
			instructions: 'search for: ',
			tip: 'cnet product search::input a product name and hit &lt;enter&gt; to get results',
			value: '',
			style: {
				width: '100%'
			}
		},
		orderBy: {
			tagName: 'select',
			instructions: 'order by: ',
			style: {
				width: '100%'
			},
			value: ['pop9%2Bdesc', 'edRating7'],
			optionNames: ['most popular', 'editor\'s rating']
		},
		submit: {
			tagName: 'input',
			type: 'submit',
			style: {
				cssFloat: 'right'
			},
			instructions:'',
			value: 'submit'
		}		
	}, //form builder
	previewHtml: function(data){
		var editors = "";
		var html = '<div class="dataId" style="color: #999; font-weight:bold; margin: 0px; padding: 0px;">id: '+data['@id'] +'</div>'+
						'<div class="dataDetails" style="font-size: 10px;"><img height="45" width="'+data.ImageURL[0]["@width"]+'" style="margin-left: 10px" src="'
							+data.ImageURL[1].$+'"/>' + '<br /><b>' + data.Name.$ + '</b>';
		if(data.EditorsRating && data.EditorsRating.$) 
			html += "<br/>editors' rating: "+data.EditorsRating.$;
		html += "<div>";
		if(data.LowPrice && data.LowPrice.$) html += 
			"<span class='productPickerPrices'>"+data.LowPrice.$ +"</span>";
		if(data.HighPrice && data.HighPrice.$ && (data.LowPrice.$ != data.HighPrice.$))
				html += " to <span class='productPickerPrices'>"+data.HighPrice.$ +"</span>";
		html += "</div></div>";
		html += "<div>";
		if(data.Offers && data.Offers['@numFound'] > 0) 
			html += "resellers: " + data.Offers["@numFound"];
		html += "</div>";
		return html;
	}, //html template for returned json data
	resultsList: function(results){
		if(results.CNETResponse.TechProducts && results.CNETResponse.TechProducts["@numFound"] > 0)
			return results.CNETResponse.TechProducts.TechProduct;
		return false;
	},
	listItemName: function(data){
		return data.Name.$
	}, //line item name for the selection list
	listItemValue: function(data){
		return data['@id'];
	},
	//handle the click event; user chooses an item, and this function updates the input 
	//(or does something else)
	updateInput: function(input, data) {
		input.value = data['@id'];
	}	
});
(end)
	*/
var Picklet = new Class({
	initialize: function(className, options){
		this.setOptions(options);
		this.className = className;
		this.getQuery = this.options.getQuery;
	}
});
Picklet.implement(new Options);


/*	Class: ProductPicker
		Handles the UI for picking products; requires at least one <Picklet>.
		
		Arguments:
		input - (dom element or id) the input that the ProductPicker references
		picklets - (array) an array of <Picklets>
		options - a key/value set of options
		
		Options:
		onShow - (function) callback to execute when the ProductPicker is displayed
		onPick - (function) callback to execute when the user clicks an entry
		title - (string) caption for the <StickyWin> popup; defaults to "Product picker"
		showOnFocus - (boolean) true (the default) means show the product picker when the user
									focuses the input
		additionalShowLinks - (array) array of dom elements or ids that show the picker when clicked
		stickyWinToUse - (reference) a reference to a <StickyWin> class to use for the popup; default
										 is <StickyWinFx>
		stickyWinOptions - (object) a key/value set of options to pass along to the <StickyWin>; defaults
										 to: offset x:20, y:20, position: "upperRight" (of the input), draggable: true
		moveIntoView - (boolean) moves the picker to be on screen if it is partially obscured; defaults to true
	*/
var ProductPicker = new Class({
	options: {
		onShow: Class.empty,
		onPick: Class.empty,
		title: 'Product picker',
		showOnFocus: true,
		additionalShowLinks: [],
		stickyWinToUse: StickyWinFx,
		stickyWinOptions: {
			fadeDuration : 200,
			draggable : true
		},
		moveIntoView: true
	},
	initialize: function(input, picklets, options){
		this.setOptions(options);
		this.input = $(input);
		this.picklets = picklets; //array of picklets
		this.setUpObservers();
		this.writeCss();
	},
	//default css props for the picker
	writeCss: function(){
		var css = "div.productPickerProductDiv div.results { overflow: 'auto'; width: 100%; margin-top: 4px }"+
							"div.productPickerProductDiv select { margin: 4px 0px 4px 0px}"+
							"div.pickerPreview div.sliderContent img {border: 1px solid #000}"+
							"div.pickerPreview div.sliderContent a {color: #0d63a0}";
		try {
			if(!$('pickerStyles')) {
				var style = new Element('style').setProperty('id','pickerStyles').injectInside($$('head')[0]);
				if (!style.setText.attempt(css, style)) style.appendText(css);
			}
		}catch(e){dbug.log('error: %s',e);}
	},
	//returns a select box of all the picklets for the given input
	getPickletList: function(){
		//if more than one
		if(this.picklets.length>1) {
			//make a select list for each one
			var selector = new Element('select').setStyle('width', '399px');
			this.picklets.each(function(picklet, index){
				var opt = new Element('option').setProperty('value',index);
				opt.text = picklet.options.descriptiveName;
				selector.adopt(opt);
			}, this);
			//when changed, show the new form
			selector.addEvent('change', function(){
				this.showForm(this.picklets[selector.getValue()]);
				this.focusInput(true);
			}.bind(this));
			return selector;
		} else return false;
	},
	//builds the picker object (happens only once)
	buildPicker: function(picklet){
		var contents = new Element('div');
		this.formBody = new Element('div'); //holds the form for each picklet
		this.pickletList = this.getPickletList(); //the select list of picklets
		if(this.pickletList) contents.adopt(this.pickletList);
		contents.adopt(this.formBody);
		//the layout for the picker
		var body = stickyWinHTML(this.options.title, contents, {
				width: '450px',
				closeTxt: 'close'
			}).addClass('productPickerProductDiv');
		//add the first form in the list of picklets
		this.showForm();
		return body;
	},
	//shows the search form for a given picklet
	showForm: function(picklet){
		//if not specified, use the first picklet available
		this.form = this.makeSearchForm(picklet || this.picklets[0]);
		//empty the form body and adopt the new picklet form
		this.formBody.setHTML('').adopt(this.form);
		//results holder
		this.results = new Element('div').addClass('results');
		this.formBody.adopt(this.results);
		//set the fx object to null so that a new one will be created on show
		this.sliderFx = null;
	},
	makeSlider: function(){
		var png = (window.ie)?'gif':'png';
		//slider for the details
		this.slider = new Element('div').addClass('pickerPreview').setStyles({
background:'url(http://www.cnet.com/html/rb/assets/global/Picker/slider.'+png+') top right no-repeat',
				display: 'none',
				height:'250px',
				left:'439px',
				position:'absolute',
				top:'25px',
				width:'0px',
				overflow: 'hidden'
		}).injectInside(this.swin.win).addEvent('mouseover', function(){
			this.previewHover = true;
		}.bind(this)).addEvent('mouseout', function(){
			this.previewHover = false;
			(function(){if (!this.previewHover) this.hidePreview()}).delay(400, this);
		}.bind(this));
		//the content holder for the details within the slider
		this.sliderContent = new Element('div').injectInside(this.slider).setStyles({
			width: '130px',
			height: '200px',
			padding: '10px',
			margin: '10px 10px 0px 0px',
			overflow: 'auto',
			cssFloat: 'right'
		}).addClass('sliderContent');
	},
	//builds the form for searches for a given picklet
	makeSearchForm: function(picklet){
		//save which picklet the user is using at the moment
		this.currentPicklet = picklet;
		var formTable = new Element('table').setStyle('width','100%').setProperties({
			cellpadding: '0',
			cellspacing: '0'
		});
		var tBody = new Element('tbody').injectInside(formTable);
		var form = new Element('form').addEvent('submit', function(e){
			//when submitted, get the results for this picklet
			this.getResults(new Event(e).target, picklet);
		}.bind(this)).adopt(formTable).setProperty('action','javascript:void(0);');
		//for each input specified in the picklet, create an element
		$each(picklet.options.inputs, function(val, name){
			tBody.adopt(this.getSearchInputTr(val, name));
		}, this);
		return form;
	},
	//builds a table row for a given input in a picklet
	getSearchInputTr: function(val, name){
		try{
			var style = ($type(val.style))?val.style:{};
			//create the input object
			//this is I.E. hackery, because IE does not let you set the name of a DOM element.
			//thanks MSFT.
			var input = (window.ie)?new Element('<' + val.tagName + ' name="' + name + '" />'):
					new Element(val.tagName).setProperty('name', name);
			input.setStyles(style);
			//if the type is specified, set it
			if(val.type)input.setProperty('type', val.type);
			//if there's a tooltip, use it
			if(val.tip && Tips){
				input.setProperty('title', val.tip);
				new Tips([input], {
					onShow: function(tip){
						this.shown = true;
						(function(){
							if(this.shown)
								$(tip).setStyles({ display:'block', opacity: 0 }).effect('opacity', { duration: 300 }).start(0,.9);
						}).delay(500, this);
					},
					onHide: function(tip){
						tip.setStyle('visibility', 'hidden');
						this.shown = false;
					}
				});
			}
			//if it's a select list
			if(val.tagName == "select"){
				//create options for each input value
				val.value.each(function(option, index){
					var opt = new Element('option').setProperty('value',option);
					opt.text = (val.optionNames && val.optionNames[index])?$pick(val.optionNames[index], option):option;
					input.adopt(opt);
				});
			} else input.value = $pick(val.value,""); //else use the value...
			var holder = new Element('tr');
					var colspan=0;
					//if instructions are supplied, add them to the table
					if(val.instructions) holder.adopt(new Element('td').setHTML(val.instructions));
					else colspan=2; //otherwise make the input span the whold table width
					var inputTD = new Element('td').adopt(input);
					if(colspan)inputTD.setProperty('colspan', colspan);
					holder.adopt(inputTD);
			return holder;
		}catch(e){dbug.log(e); return false;}
	},
	//get results using the functions specified in the picklet
	getResults: function(form, picklet){
		//get the query object (JsonP or Ajax)
		var query = picklet.getQuery(form.toQueryString().parseQuery());
		//handle the results
		query.addEvent('onComplete', this.showResults.bind(this));
		//execute the request
		query.request();
	},
	//handle the results from the request
	showResults: function(data){
		var empty = false;
		if(this.results.innerHTML=='') { //no previous results
			empty = true;
			this.results.setStyles({
				height: '0px',
				border: '1px solid #666',
				padding: '0px',
				overflow: 'auto',
				opacity: 0
			});
		} else this.results.setHTML(''); //empty previous results
		//get the items from the result set - an array
		this.items = this.currentPicklet.options.resultsList(data);
		//if there are any
		if(this.items && this.items.length > 0) {
			//loop through them
			this.items.each(function(item, index){
				var name = this.currentPicklet.options.listItemName(item);
				var value = this.currentPicklet.options.listItemValue(item);
				//add it to the list in the picker
				this.results.adopt(this.makeProductListEntry(name, value, index));
			}, this);
		} else this.results.setHTML("Sorry, there don't seem to be any items for that search");
		//show the results
		this.results.effects().start({ height: 200, opacity: 1 });
		//apply the list styles to the list elements
		this.listStyles();
		//make sure the picker is entirely visible
		this.getOnScreen.delay(500, this);
	},
	//moves the picker to be entirely on screen
	getOnScreen: function(){
		if(document.compatMode == "BackCompat") return;
		var s = this.swin.win.getCoordinates();
		if(s.top < window.getScrollTop()) {
			this.swin.win.effect('top').start(window.getScrollTop()+50);
			return;
		}
		if(s.top+s.height > window.getScrollTop()+window.getHeight() && window.getHeight()>s.height) {
			this.swin.win.effect('top').start(window.getScrollTop()+window.getHeight()-s.height-100);
			return;
		}
		try{this.swin.shim.show.delay(500, this.swin.shim);}catch(e){}
		return;
	},
	listStyles: function(){
		var defaultStyle = {
				cursor: 'pointer',
				borderBottom: '1px solid #ddd',
				padding: '2px 8px 2px 8px',
				backgroundColor:'#fff',
				color: '#000',
				fontWeight: 'normal'
			};
		var hoverStyle = {
				backgroundColor:'#fcfbd1',
				color: '#d56a00'
			};
		var selectedStyle = $merge(defaultStyle, {
				color: '#D00000',
				fontWeight: 'bold',
				backgroundColor: '#eee'
			});
		//loop through the results and apply the appropriate style to each one
		this.results.getElements('div.productPickerProductDiv').each(function(p){
			var useStyle = (this.input.value.toInt() == p.getProperty('val').toInt())?selectedStyle:defaultStyle;
			p.setStyles(useStyle);
			if(!window.ie) {//ie doesn't like these mouseover behaviors...
				p.addEvent('mouseover', function(){ p.setStyles(hoverStyle); }.bind(this));
				p.addEvent('mouseout', function(){ p.setStyles(useStyle); });
			}
		}, this);
	},
	//returns a list item for the picker list
	makeProductListEntry: function(name, value, index){
		var pDiv = new Element("div").addClass('productPickerProductDiv').adopt(
				new Element("div").setHTML(name)
			).setProperty('val', value);
		//on mouseover show the details
		pDiv.addEvent('mouseover', function(e){
			this.preview = true;
			this.sliderContent.setHTML("");
			var content = this.getPreview(index);
			if($type(content)=="string") this.sliderContent.setHTML(content);
			else if($(content)) this.sliderContent.adopt(content);
			this.showPreview.delay(200, this);
		}.bind(this));
		//on mouseover hide the details
		pDiv.addEvent('mouseout', function(e){
			this.preview = false;
			(function(){if(!this.previewHover) this.hidePreview();}).delay(400, this);
		}.bind(this));
		//on click set the input value
		pDiv.addEvent('click', function(){
			this.currentPicklet.options.updateInput(this.input, this.items[index]);
			this.fireEvent('onPick', [this.input, this.items[index], this]);
			this.hide();
			this.listStyles.delay(200, this);
		}.bind(this));
		return pDiv;
	},
	//make the instance of the stickyWin
	makeStickyWin: function(){
		if(document.compatMode == "BackCompat") this.options.stickyWinOptions.relativeTo = this.input;
		this.swin = new this.options.stickyWinToUse($merge(this.options.stickyWinOptions, {
			draggable: true,
			content: this.buildPicker()
		}));
	},
	focusInput: function(force){
		if ((!this.focused || $pick(force,false)) && this.form.getElement('input')) {
			this.focused = true;
			try { this.form.getElement('input').focus(); } catch(e){}
		}
	},
/*	Property: show
		Shows the ProductPicker.
	*/
	show: function(){
		if (!this.swin) this.makeStickyWin();
		if (!this.slider) this.makeSlider();
		if (!this.swin.visible) this.swin.show();
		this.focusInput();
	},
/*	Property: hide
		Hides the ProductPicker.
	*/
	hide: function(){
		$$('.tool-tip').hide();
		this.swin.hide();
		this.focused = false;
	},
	//observe all the input and links
	setUpObservers: function(){
		try {
			if(this.options.showOnFocus) this.input.addEvent('focus', this.show.bind(this));
			if(this.options.additionalShowLinks.length>0) {
				this.options.additionalShowLinks.each(function(lnk){
					$(lnk).addEvent('click', this.show.bind(this));
				}, this);
			}
		}catch(e){dbug.log(e);}
	},
	//show the preview in the slider
	showPreview: function(index){
		width = this.currentPicklet.options.previewWidth || 150;
		this.sliderContent.setStyle('width', (width-30)+'px');
		if(!this.sliderFx) this.sliderFx = new Fx.Elements([this.slider, this.swin.win]);
		$extend(this.sliderFx.options, {
				duration: 1000, 
				transition: Fx.Transitions.elasticOut
			});
		if(this.preview && this.slider.getStyle('width').toInt() < width-5) {
			this.slider.show('block');
			this.sliderFx.start({
				'0':{//the slider
					'width':width
				},
				'1':{//the popup window (for ie)
					'width':width+450
				}
			});
		}
	},
	//hide the preview box
	hidePreview: function(){
		if(!this.preview) {
		$extend(this.sliderFx.options, {
				duration: 250, 
				transition: Fx.Transitions.backIn
			});
			this.sliderFx.start({
				'0':{//the slider
					'width':[this.slider.getStyle('width').toInt(),0]
				},
				'1':{//the popup window (for ie)
					'width':[this.swin.win.getStyle('width').toInt(), 450]
				}
			}).chain(function(){
				this.slider.hide();
			}.bind(this));
		}
	},
	//get the preview html from the picklet
	getPreview: function(index){
		return this.currentPicklet.options.previewHtml(this.items[index]);
	}
});
ProductPicker.implement(new Options);
ProductPicker.implement(new Events);


/*	Section: ProductPicker global functions
		These functions are available to the <ProductPicker> object itself, not instances of it.
		Use these functions to add <Picklets> to the ProductPicker object, which will be available
		to all instances of the ProductPicker class.
	*/
$extend(ProductPicker, {
	picklets: [],
/*	Property: add
		Adds a <Picklet> to the list of Picklets available to the <ProductPicker> class.
		
		Arguments:
		picklet - a <Picklet>
	*/
	add: function(picklet){
		if(! picklet.className) {
			dbug.log('error: cannot add Picklet %o; missing className: %s', picklet, picklet.className);
			return;
		}
		this.picklets[picklet.className] = picklet;
	},
/*	Property: addAllThese
		Adds several <Picklet>s to the list of Picklets available to the <ProductPicker> class.
		
		Arguments:
		picklets - an array of <Picklet>s
	*/
	addAllThese: function(picklets){
		picklets.each(function(picklet){
			this.add(picklet);
		}, this);
	},
/*	Property: getPicklet
		Returns a <Picklet> that matches the given className (or false, if none was found).
		
		Arguments:
		className - the className for the <Picklet>
	*/
	getPicklet: function(className){
		return ProductPicker.picklets[className]||false;
	}
});

/*	Class: FormPickers
		Adds the appropriate <ProductPickers> to all the inputs in a form as defined in the 
		classNames assigned to each input.
		
		Arguments:
		form - a form element or id
		options - a key/value set of options
		
		Options:
		inputs - (string) selector of input types to parse; defaults to 'input' 
						 (but could include textarea, select, etc.)
		additionalShowLinkClass - (string) className for links that should show the
						 <ProductPicker> when clicked. Each input in the form will be checked to
						 see if it's next sibling (i.e. the dom element right after the input) has
						 this class and, if so, the element will have an event attached so that the
						 picker is shown when it is clicked.
		pickletOptions - (object) options passed along to each ProductPicker created.
	*/
var FormPickers = new Class({
	options: {
		inputs: 'input',
		additionalShowLinkClass: 'openPicker',
		pickletOptions: {}
	},
	initialize: function(form, options){
		this.setOptions(options);
		this.form = $(form);
		this.inputs = this.form.getElementsBySelector(this.options.inputs);
		this.setUpInputs();
	},
	//add pickers for each input that needs one
	setUpInputs: function(inputs){
		inputs = $pick(inputs, this.inputs);
		inputs.each(this.addPickers.bind(this));
	},
	//add the appropriate pickers to an input
	addPickers: function(input){
		var picklets = [];
		//get all the class names
		input.className.split(" ").each(function(clss){
			//if the class is a picklet, add it to the list
			if(ProductPicker.getPicklet(clss)) picklets.push(ProductPicker.getPicklet(clss));
		}, this);
		//if there's a dom element next to the input and it has the link class
		if(input.getNext() && input.getNext().hasClass(this.options.additionalShowLinkClass))
			//add it to the options for this picker
			this.options.pickletOptions.additionalShowLinks = [input.getNext()];
		//make the picker
		if(picklets.length>0)  new ProductPicker(input, picklets, this.options.pickletOptions);
	}
});
FormPickers.implement(new Options);
/* do not edit below this line */   
/* Section: Change Log 

$Source: /cvs/main/flatfile/html/rb/js/global/cnet.global.framework/common/js.widgets/product.picker.js,v $
$Log: product.picker.js,v $
Revision 1.26  2007/07/18 16:15:21  newtona
forgot to bind the style objects in the setText.attempt method...

Revision 1.25  2007/07/16 21:00:21  newtona
using Element.setText for all style injection methods (fixes IE6 problems)
moving Element.setText to element.legacy.js; this function is in Mootools 1.11, but if your environment is running 1.0 you'll need this.

Revision 1.24  2007/05/29 22:01:53  newtona
Split element.cnet.js into seperate files; updated docs in files to note this
Changed element.visible to element.isVisible (left old namespace for legacy support)
Fixed Element.empty in prototype.compatibility.js
Removed as many dependencies in common code to element.*.js as possible (espeically element.shortcuts.js)

Revision 1.23  2007/05/17 19:45:43  newtona
product picker: hide() now hides tooltips; onPick passes in a 3rd argument that is the picker
stickyWinHTML: fixed a bug with className options for buttons
html.table: fixed a bug with className options for buttons

Revision 1.22  2007/05/16 20:09:41  newtona
adding new js files to redball.common.full
product.picker.js now has no picklets; these are in the implementations/picklets directory
ProductPicker now detects if there is no doctyp and, if not, sets the position of the picker to be fixed (no IE6 support)
small docs update in element.cnet.js
added new picklet: CNETProductPicker_PricePath
added new picklet: NewsStoryPicker_Path
new file: clipboard.js (allows you to insert text into the OS clipboard)
new file: html.table.js (automates building html tables)
new file: element.forms.js (for managing text inputs - get selected text information, insert content around selection, etc.)

Revision 1.21  2007/05/09 20:45:36  newtona
moving picklets into their own location in the implementations content

Revision 1.20  2007/05/07 21:37:45  newtona
product picker now shows up in the middle of the screen by default

Revision 1.19  2007/05/04 22:19:46  newtona
adding onPick event call

Revision 1.18  2007/05/04 22:17:23  newtona
updating cnet api stuff

Revision 1.17  2007/05/04 17:25:25  newtona
updating my default partner key stuff

Revision 1.16  2007/05/03 18:24:24  newtona
iframeshim: removed a dbug line
modalizer: only hide select lists for browsers that need it
product picker: added a try/catch, updated cnet api link/code

Revision 1.15  2007/03/13 19:17:08  newtona
added close button

Revision 1.14  2007/03/08 23:29:31  newtona
date picker: strict javascript warnings cleaned up
popup details strict javascript warnings cleaned up
product.picker: strict javascript warnings cleaned up, updating input now fires onchange event
confirmer: new file

Revision 1.13  2007/03/05 21:52:14  newtona
fixed a bug where the cnet api only returned 1 result; it's not an array

Revision 1.12  2007/03/05 19:55:07  newtona
css tweak for link color in preview

Revision 1.11  2007/03/05 19:45:55  newtona
removed a dbug line

Revision 1.10  2007/03/05 19:36:28  newtona
numerous interface fixes for IE (hurah)
fixed the query string handling for spaces

Revision 1.9  2007/03/01 23:21:06  newtona
tweaking focus logic

Revision 1.8  2007/03/01 23:11:00  newtona
product picker now focuses it's input when you open it.

Revision 1.7  2007/02/27 21:46:43  newtona
docs update; fixing references

Revision 1.6  2007/02/24 00:58:26  newtona
picklet updates - just look & feel stuff

Revision 1.5  2007/02/24 00:33:07  newtona
undoing my css change

Revision 1.4  2007/02/24 00:28:07  newtona
adjusting css location of preview

Revision 1.3  2007/02/22 23:33:46  newtona
added descriptive name to cnet product picker

Revision 1.2  2007/02/22 22:04:38  newtona
updating the input is now a function in the picklet options

Revision 1.1  2007/02/22 21:27:43  newtona
moved product picker from utilities dir
fixed missing ; in stickywin html

Revision 1.3  2007/02/22 20:36:04  newtona
changed references from Picker to ProductPicker

Revision 1.2  2007/02/22 20:01:44  newtona
fixed missing ;

Revision 1.1  2007/02/22 18:18:24  newtona
*** empty log message ***


*/
/*	Script: cnet.product.picklet.js
		This is a <Picklet> for the <ProductPicker> class that returns CNET Products for a given keyword.
		
		Author:
		Aaron Newton <aaron [dot] newton [at] cnet [dot] com>
		
		Dependencies:
		Everything listed in <product.picker.js>

		Note:
		Add the className to any input and then create a new <FormPickers> and these 
		will automatically be applied. See <ProductPicker.add> on how to add your own.
		
		Property: CNETProductPicker
		A simple query search for CNET Products (electronics, computers, software, etc.).
	*/
var CNETProductPickerBase = {
	previewWidth: 150,
	descriptiveName: 'CNET Product Picker',
	url: 'http://api.cnet.com/restApi/v1.0/techProductSearch',
	callBackKey: 'callback', //see <JsonP> options
	data: {
		partKey: '19926949750937665684988687810562', //this is my code - aaron newton
		iod: 'hlPrice',
		viewType: 'json'
	}, //static data
	getQuery: function(data){ //return <Ajax> or <JsonP>
		//clean any url encoding from the data, as JsonP encodes it again
		$each(data, function(val, key) { data[key] = unescape(val); });
		return new JsonP(this.options.url, {
			callBackKey: this.options.callBackKey,
			data: $merge(this.options.data, data)
		});
	},
	inputs: {
		query: {
			tagName: 'input',
			type: 'text',
			instructions: '',
			tip: 'cnet product search::input a product name and hit &lt;enter&gt; to get results',
			value: '',
			style: {
				width: '100%'
			}
		}
	}, //form builder
	previewHtml: function(data){
		var editors = "";
		var html = '<div class="dataId" style="color: #999; font-weight:bold; margin: 0px; padding: 0px;">id: '+data['@id'] +'</div>'+
						'<div class="dataDetails" style="font-size: 10px;"><a href="'+ data.ReviewURL.$ +'"><img height="45" width="'+data.ImageURL[0]["@width"]+'" style="margin-left: 10px" src="'
							+data.ImageURL[1].$+'"/></a><br /><b><a href="'+ data.ReviewURL.$ +'">' + data.Name.$ + '</a></b>';
		if(data.EditorsRating && data.EditorsRating.$) 
			html += "<br/>editors' rating: "+data.EditorsRating.$;
		html += "<div>";
		if(data.LowPrice && data.LowPrice.$) html += 
			"<span class='productPickerPrices'>"+data.LowPrice.$ +"</span>";
		if(data.HighPrice && data.HighPrice.$ && (data.LowPrice.$ != data.HighPrice.$))
				html += " to <span class='productPickerPrices'>"+data.HighPrice.$ +"</span>";
		html += "</div></div>";
		html += "<div>";
		if(data.Offers && data.Offers['@numFound'] > 0) 
			html += "resellers: " + data.Offers["@numFound"];
		html += "</div>";
		return html;
	}, //html template for returned json data
	resultsList: function(results){
		if(results.CNETResponse.TechProducts && results.CNETResponse.TechProducts["@numFound"] > 0) {
			if(results.CNETResponse.TechProducts["@numFound"] > 1) return results.CNETResponse.TechProducts.TechProduct;
			else return [results.CNETResponse.TechProducts.TechProduct];
		}
		return false;
	},
	listItemName: function(data){
		return data.Name.$
	}, //line item name for the selection list
	listItemValue: function(data){
		return data['@id'];
	},
	//handle the click event; user chooses an item, and this function updates the input 
	//(or does something else)
	updateInput: function(input, data) {
		input.value = data['@id'];
		input.fireEvent('change');
	}	
};
	
var CNETProductPicker = new Picklet('CNETProductPicker',CNETProductPickerBase);
ProductPicker.add(CNETProductPicker);

/*	Class: CNETProductPicker_ReviewPath 
		Extends <CNETProductPicker> to return a path to the review instead of the id.
 */
var CNETProductPicker_ReviewPath = new Picklet('CNETProductPicker_ReviewPath', $merge(CNETProductPickerBase, {
		descriptiveName: 'CNET Product Picker: Review URL',
		updateInput: function(input, data) {
			var url = data.ReviewURL.$;
			if (url.indexOf("?")>=0) url = url.substring(0,url.indexOf("?"));
			input.value = url;
			input.fireEvent('change');
		}
	})
);
ProductPicker.add(CNETProductPicker_ReviewPath);
/*	Class: CNETProductPicker_PricePath 
		Extends <CNETProductPicker> to return a path to the price page instead of the id.
 */
var CNETProductPicker_PricePath = new Picklet('CNETProductPicker_ReviewPath', $merge(CNETProductPickerBase, {
		descriptiveName: 'CNET Product Picker: Price URL',
		updateInput: function(input, data) {
			var url = data.PriceURL.$;
			if (url.indexOf("?")>=0) url = url.substring(0,url.indexOf("?"));
			input.value = url;
			input.fireEvent('change');
		}
	})
);
ProductPicker.add(CNETProductPicker_PricePath);
/* do not edit below this line */   
/* Section: Change Log 

$Source: /cvs/main/flatfile/html/rb/js/global/cnet.global.implementations/picklets/cnet.product.picklet.js,v $
$Log: cnet.product.picklet.js,v $
Revision 1.2  2007/05/16 20:09:45  newtona
adding new js files to redball.common.full
product.picker.js now has no picklets; these are in the implementations/picklets directory
ProductPicker now detects if there is no doctyp and, if not, sets the position of the picker to be fixed (no IE6 support)
small docs update in element.cnet.js
added new picklet: CNETProductPicker_PricePath
added new picklet: NewsStoryPicker_Path
new file: clipboard.js (allows you to insert text into the OS clipboard)
new file: html.table.js (automates building html tables)
new file: element.forms.js (for managing text inputs - get selected text information, insert content around selection, etc.)

Revision 1.1  2007/05/10 00:21:05  newtona
moved from product.picker.js


*/
/*	Script: confirmer.js
		Fades a message in and out for the user to tell them that some event (like an ajax save) has occurred.
		
		Author:
		Aaron Newton <aaron [dot] newton [at] cnet [dot] com>
		
		Dependencies:
		Mootools - <Moo.js>, <Utilities.js>, <Common.js>, <Array.js>, <String.js>, <Element.js>, <Function.js>, <Fx.Style.js>
		CNET - <element.shortcuts.js>, <element.dimensions.js>, <element.position.js>
		
		Class: Confirmer
		Fades a message in and out for the user to tell them that some event (like an ajax save) has occurred.
		
		Arguments:
		options - (object) a key/value set of options
		
		Options:
		reposition - (boolean) if the element that is going to fade in and out is already present in the 
									DOM and you want to leave it where it is, set this to false and it will just fade
									in and out; defaults to true
		positionOptions - (object) options to pass along to <Element.setPosition>; see below.
		msg - (string, DOM element, or DOM element id) default confirmation message; can be overwritten at the time of
						 prompting (see <Confirmer.prompt>). If the item is a DOM element (or id) then the element will get 
						 the transition, otherwise, the message string will be inserted into a new div element and positioned.
						 Defaults to "your changes have been saved"
		msgContainerSelector - (string; css selector) if the DOM element that's fading in and out contains more HTML,
									with a child element that contains the actual string of your message, this selector describes
									where that string is found within that html, so that new messages can be swapped in and out
									without altering your HTML. Defaults to ".body". If this element is not found, it'll replace 
									the innerHTML of the entire container with the string.
		delay: (integer) delay (in ms) to wait after <prompt> is called before the message fades in. This is useful when
									the user might create numerous prompt events in a row. If they create more than one event
									within this delay period, the prompt will wait until the last one to actually convey the message.
		pause: (integer) period to leave the message visible until fading back out
		effectOptions: (object) options object to be passed to Fx.Style; defaults to {duration: 500}
		prompterStyle: (object) css style object to apply to the style box; only used if the msg option is a string.
		
		
		positionOptions:
		relativeTo - (DOM element or ID) if repositioning (see above), what is it relative to. See
								 <Element.setPosition>. Defaults to document.body.
		position - (string) see <Element.setPosition>; defaults to "upperRight"; only used if reposition is true
		offset - (object) an offset object with x/y values; defaults to {x: -225, y:0}; only used if reposition is true
		zIndex - (integer) the zIndex of the prompter; only used if reposition is true
		onComplete - (function) function to execute when the message finishes fading out
		
		Notes & Examples:
		<Confirmer> concerns itself mostly with fading your message in and out. If your message is already in the DOM, you can create a Confirmer and then just fade that message in and out in place:
(start code)
<input id="myInput" ...> <span id="savedMsg" style="visibility: hidden">your changes have been saved</span>
<script>
var myConf = new Confirmer({
	msg: 'savedMsg'
});
$('myInput').addEvent('change', function(){
	new Ajax(..., {onSuccess: myConf.prompt});
});
</script>
(end)

	You can also position the confirmation element wherever you want it and, additionally, you can pass in a string for the message or a dom element.

(start code)
var myConf = new Confirmer({
	msg: 'your changes are saved!',
	positionOptions: {
		relativeTo: 'myInput',
		position: 'bottomLeft'
	}
});
...
myConf.prompt();
(end)

	The message can be changed at prompt time, so you can reuse an element as you like.
(start code)
var myConf = new Confirmer({
	msg: 'your changes are saved!',
	positionOptions: {
		relativeTo: 'myInput',
		position: 'bottomLeft'
	}
});
...
myConf.prompt({msg: 'your changes were NOT saved'});
(end)
	*/
var Confirmer = new Class({
	options: {
		reposition: true, //for elements already in the DOM
		//if position = false, just fade
		positionOptions: {
			relativeTo: false,
			position: 'upperRight', //see <Element.setPosition>
			offset: {x:-225,y:0},
			zIndex: 9999
		},
		msg: 'your changes have been saved', //string or dom element
		msgContainerSelector: '.body',
		delay: 250,
		pause: 500,
		effectOptions:{
			duration: 500
		},
		prompterStyle:{
			padding: '2px 6px',
			border: '1px solid #9f0000',
			backgroundColor: '#f9d0d0',
			fontWeight: 'bold',
			color: '#000',
			width: '210px'			
		},
		onComplete: Class.empty
	},
	initialize: function(options){
			this.setOptions(options);
			this.options.positionOptions.relativeTo = this.options.positionOptions.relativeTo || document.body;
			this.prompter = ($(this.options.msg))?$(this.options.msg):this.makePrompter(this.options.msg);
			if(this.options.reposition){
				this.prompter.setStyles({
					position: 'absolute',
					display: 'none',
					zIndex: this.options.positionOptions.zIndex
				});
				if(this.prompter.fxOpacityOk()) this.prompter.setStyle('opacity',0);
			} else if(this.prompter.fxOpacityOk()) this.prompter.setStyle('opacity',0);
			else this.prompter.setStyle('visibility','hidden');
			if(!this.prompter.getParent())window.addEvent('domready', function(){
					this.prompter.injectInside(document.body);
			}.bind(this));
		try {
			this.msgHolder = this.prompter.getElement(this.options.msgContainerSelector);
			if(!this.msgHolder) this.msgHolder = this.prompter;
		} catch(e){dbug.log(e)}
	},
	makePrompter: function(msg){
		try {
			return new Element('div').setStyles(this.options.prompterStyle).appendText(msg);
		}catch(e){dbug.log(e); return prompter}
	},
/*	Property: prompt
		Fades in and out the message.
		
		Arguments:
		options - a key/value set of options
		
		Options:
		msg - (string or DOM element) the message to display
		pause - (integer) the duration (in ms) to leave the message visible
		delay - (integer) the duration (in ms) to wait before displaying the message
		positionOptions - (object) options object to pass to <Element.setPosition>
		saveAsDefault - (boolean) overwrite the options specified at instantiation with 
										these new values; defaults to false
										
		Note:
		All of the above options are not required and will default to the values stored
		in the options of the instance. The saveAsDefault option will update the stored
		values with those passed in.
	*/
	prompt: function(options){
		if(!this.paused)this.stop();
		var msg = (options)?options.msg:false;
		options = $merge(this.options, {saveAsDefault: false}, options||{});
		if ($(options.msg) && msg) this.msgHolder.empty().adopt(options.msg);
		else if (!$(options.msg) && options.msg) this.msgHolder.empty().appendText(options.msg);
		if(!this.paused) {
			if(options.reposition) this.position(options.positionOptions);
			(function(){
				this.timer = this.fade(options.pause);
			}).delay(options.delay, this);
		}
		if(options.saveAsDefault) this.setOptions(options);
	},
	fade: function(pause){
		this.paused = true;
		pause = $pick(pause, this.options.pause);
		if(!this.fx && this.prompter.fxOpacityOk()) {
			this.fx = this.prompter.effect('opacity', this.options.effectOptions);
			this.fx.clearChain();
		}
		if(this.options.reposition) this.prompter.setStyle('display','block');
		if(this.prompter.fxOpacityOk()){
			this.prompter.setStyle('visibility','visible');
			this.fx.start(0,1).chain(function(){
				this.timer = (function(){
					this.fx.start(0).chain(function(){
						if(this.options.reposition) this.prompter.hide();
						this.paused = false;
					}.bind(this));
				}).delay(pause, this);
			}.bind(this));
		} else {
			this.prompter.setStyle('visibility','visible');
			this.timer = (function(){
				this.prompter.setStyle('visibility','hidden');
				this.fireEvent('onComplete');
				this.paused = false;
			}).delay(pause+this.options.effectOptions.duration, this);
		}
	},
/*	Property: stop
		Stops the element and hides it immediately.
	*/
	stop: function(){	
		this.paused = false;
		$clear($pick(this.timer, false));
		if(this.fx) this.fx.set(0);
		if(this.options.reposition) this.prompter.hide();
	},
	position: function(positionOptions){
		this.prompter.setPosition($merge(this.options.positionOptions, positionOptions));
	}
});
Confirmer.implement(new Options);
Confirmer.implement(new Events);

/* do not edit below this line */   
/* Section: Change Log 

$Source: /cvs/main/flatfile/html/rb/js/global/cnet.global.framework/common/js.widgets/confirmer.js,v $
$Log: confirmer.js,v $
Revision 1.12  2007/05/29 22:01:53  newtona
Split element.cnet.js into seperate files; updated docs in files to note this
Changed element.visible to element.isVisible (left old namespace for legacy support)
Fixed Element.empty in prototype.compatibility.js
Removed as many dependencies in common code to element.*.js as possible (espeically element.shortcuts.js)

Revision 1.11  2007/04/09 21:35:49  newtona
ok. actually fixed the DOM destruction bug...

Revision 1.10  2007/04/09 20:09:07  newtona
syntax problem - left a "this"

Revision 1.9  2007/04/09 20:01:25  newtona
fixed a nasty bug that destroyed the document object!

Revision 1.8  2007/03/30 19:32:20  newtona
changing .flush to .empty

Revision 1.7  2007/03/29 23:12:00  newtona
confirmer now checks for a bg color in IE6 to use crossfading (see Element.fxOpacityOk)
fixed an IE7 css layout issue in stickyDefaultHTML
StickyWin now uses Element.flush
StickyWinFx.Drag now temporarily shows the sticky win (with opacity 0) to execute makeDraggable and makeResizable to prevent a Safari bug

Revision 1.6  2007/03/28 18:08:02  newtona
confirmer now uses Element.fxOpacityOk

Revision 1.5  2007/03/15 18:32:01  newtona
removed a dbug line

Revision 1.4  2007/03/09 01:00:12  newtona
docs update

Revision 1.3  2007/03/09 00:59:26  newtona
numerous layout tweaks

Revision 1.2  2007/03/08 23:59:35  newtona
doc typo

Revision 1.1  2007/03/08 23:29:31  newtona
date picker: strict javascript warnings cleaned up
popup details strict javascript warnings cleaned up
product.picker: strict javascript warnings cleaned up, updating input now fires onchange event
confirmer: new file


*/
/*	Script: clipboard.js
		Provides access to the OS clipboard so that data can be copied to it (using a flash plugin).
		
		Author:
		Aaron Newton <aaron [dot] newton [at] cnet [dot] com>
		Original source: http://www.jeffothy.com/weblog/clipboard-copy/
		
		Dependencies:
		Mootools - <Moo.js>, <Utilities.js>, <Common.js>, <Array.js>, <String.js>, <Element.js>, <Function.js>
		CNET - (optional) <element.forms.js>
		
		Class: Clipboard
		Provides access to the OS clipboard so that data can be copied to it (using a flash plugin).
*/
var Clipboard = {
	swfLocation: 'http://www.cnet.com/html/rb/assets/global/clipboard/_clipboard.swf',
/*	Property: copyFromElement
		Copies the selected text in an element to the clipboard.
		
		Arguments:
		element - the element that has selected text.
	*/
	copyFromElement: function(element) {
		element = $(element);
		if(!element) return null;
		if (window.ie) {
			try {
				window.addEvent('domready', function() {
					var range = element.createTextRange();
					if(range) range.execCommand('Copy');
				});
			}catch(e){
				dbug.log('cannot copy to clipboard: %s', o)
			}
		} else {
			var text = (element.getSelectedText)?element.getSelectedText():element.getValue();
			if (text) Clipboard.copy(text);
		}
	},
/*	Property: copy
		Copies a string to the clipboard.
		
		Arguments:
		text - (string) value to be copied to the clipboard.
	*/
	copy: function(text) {
		if(window.ie){
			window.addEvent('domready', function() {
				var cb = new Element('textarea', {styles: {display: 'none'}}).injectInside(document.body);
				cb.setProperty('value', text).select();
				Clipboard.copyFromElement(cb);
				cb.remove();
			});
		} else {
			var swf = ($('flashcopier'))?$('flashcopier'):new Element('div').setProperty('id', 'flashcopier').injectInside(document.body);
			swf.empty();
			swf.setHTML('<embed src="'+this.swfLocation+'" FlashVars="clipboard='+escape(text)+'" width="0" height="0" type="application/x-shockwave-flash"></embed>');
		}
	}
};
/* do not edit below this line */	 
/* Section: Change Log 

$Source: /cvs/main/flatfile/html/rb/js/global/cnet.global.framework/common/js.widgets/clipboard.js,v $
$Log: clipboard.js,v $
Revision 1.2  2007/05/16 21:09:26  newtona
fixed element reference in clipboard (added $())

Revision 1.1  2007/05/16 20:09:41  newtona
adding new js files to redball.common.full
product.picker.js now has no picklets; these are in the implementations/picklets directory
ProductPicker now detects if there is no doctyp and, if not, sets the position of the picker to be fixed (no IE6 support)
small docs update in element.cnet.js
added new picklet: CNETProductPicker_PricePath
added new picklet: NewsStoryPicker_Path
new file: clipboard.js (allows you to insert text into the OS clipboard)
new file: html.table.js (automates building html tables)
new file: element.forms.js (for managing text inputs - get selected text information, insert content around selection, etc.)


*/
/*	Script: html.table.js
		Builds table elements with methods to add rows quickly.
		
		Author:
		Aaron Newton <aaron [dot] newton [at] cnet [dot] com>
		
		Dependencies:
		Mootools - <Moo.js>, <Utilities.js>, <Common.js>, <Array.js>, <String.js>, <Element.js>, <Function.js>

		Class: HtmlTable
		Builds table elements with methods to add rows quickly.
		
		Arguments: 
		options - (object) a key/value set of options
		
		Options:
		properties - a set of properties for the Table element (defaults to cellpadding: 0, cellspacing: 0, border: 0)
		rows - (array) an array of row objects (see <HtmlTable.push>)
	*/
var HtmlTable = new Class({
	options: {
		properties: {
			cellpadding: 0,
			cellspacing: 0,
			border: 0
		},
		rows: []
	},
	initialize: function(options) {
		this.setOptions(options);
		if(this.options.properties.className){
			this.options.properties['class'] = this.options.properties.className;
			delete this.options.properties.className;
		}
		this.table = new Element('table').setProperties(this.options.properties);
		this.tbody = new Element('tbody').injectInside(this.table);
		this.options.rows.each(this.push.bind(this));
	},
	//row = [{content: <content>, properties: {colspan: 2, rowspan: 3, class: "cssClass", style: "border: 1px solid blue"}]
	//OR
	//row = [<content>,<content>,etc.]

/*	Property: row
		Inserts a new table row.
		
		Arguments:
		row - (array) the data for the row.
		
		Row data:
		Row data can be in either of two formats.
		
		simple - an array of strings that will be inserted into each table data
		detailed - an array of objects with definitions for content and properties for each td
		
		Example:
(start code)
var myTable = new HtmlTable();
myTable.push(['value 1','value 2', 'value 3']); //new row
myTable.push([
	{
		content: 'value 4',
		properties: {
			colspan: 2,
			className: 'doubleWide',
			style: '1px solid blue'
	},
	{
		content: 'value 5'
	}
]);

RESULT:
<table cellpadding="0" cellspacing="0" border="0">
	<tr>
		<td>value 1</td>
		<td>value 2</td>
		<td>value 3</td>
	</tr>
	<tr>
		<td colspan="2" class="doubleWide" style="1px solid blue">value 4</td>
		<td>value 5</td>
	</tr>
</table>
(end)
	*/
	push: function(row) {
		var tr = new Element('tr').injectInside(this.tbody);
		row.each(function (tdata) {
			var td = new Element('td').injectInside(tr);
			if(tdata.properties) {
				if(tdata.properties.className){
					tdata.properties['class'] = tdata.properties.className;
					delete tdata.properties.className;
				}
				td.setProperties(tdata.properties);
			}
			function setContent(content){
				if($(content)) td.adopt($(content));
				else td.setHTML(content);
			};
			if(tdata.content) setContent(tdata.content);
			else setContent(tdata);
		}, this);
		return this;
	}
});
HtmlTable.implement(new Options);
/* do not edit below this line */   
/* Section: Change Log 

$Source: /cvs/main/flatfile/html/rb/js/global/cnet.global.framework/common/layout.widgets/html.table.js,v $
$Log: html.table.js,v $
Revision 1.3  2007/06/12 20:46:20  newtona
added tbody to html.table.js
added legacy argument support to Fx.SmoothShow

Revision 1.2  2007/05/17 19:45:43  newtona
product picker: hide() now hides tooltips; onPick passes in a 3rd argument that is the picker
stickyWinHTML: fixed a bug with className options for buttons
html.table: fixed a bug with className options for buttons

Revision 1.1  2007/05/16 20:09:41  newtona
adding new js files to redball.common.full
product.picker.js now has no picklets; these are in the implementations/picklets directory
ProductPicker now detects if there is no doctyp and, if not, sets the position of the picker to be fixed (no IE6 support)
small docs update in element.cnet.js
added new picklet: CNETProductPicker_PricePath
added new picklet: NewsStoryPicker_Path
new file: clipboard.js (allows you to insert text into the OS clipboard)
new file: html.table.js (automates building html tables)
new file: element.forms.js (for managing text inputs - get selected text information, insert content around selection, etc.)


*/
/*	Script: Autocompleter.js
		3rd party script for managing autocomplete functionality.
		
		Details:
		Author - Harald Kirschner <mail [at] digitarald.de>
		Refactored by - Aaron Newton <aaron [dot] newton [at] cnet [dot] com>
		License - MIT-style license
		Version - 1.0rc3
		Source - http://digitarald.de/project/autocompleter/
		
		Dependencies:
		Mootools 1.1 - <Class.Extras>, <Element.Event>, <Element.Selectors>, <Element.Form>, <Element.Dimensions>, <Fx.Style>, <Ajax>, <Json>
		Autocompleter - <Observer.js>
		
		Namespace: Autocompleter
		All functions are part of the <Autocompleter> namespace.
	*/
var Autocompleter = {};
/*	Class: Autocompleter.Base
		Base class for the Autocompleter classes.
		
		Arguments:
		el - (DOM element or id) element to observe.
		options - (object) key/value set of options.
		
		Options:
		minLength - (integer, default 1) Minimum length to start auto compliter
		useSelection - (boolean, default true) Select completed text part (works only for appended strings)
		markQuery - (boolean, default true) Mark queried string with <span class="autocompleter-queried">*</span>
		inheritWidth - (boolean, default true) Inherit width for the autocompleter overlay from the input field
		maxChoices - (integer, default 10). Maximum of suggested fields.
		injectChoice - (function, optional). Callback for injecting the list element with the arguments (itemValue, itemIndex), take a look at updateChoices for default behaviour.
		onSelect - Event Function. Fires when when an item gets selected.
		onShow - Event Function. Fires when autocompleter list shows.
		onHide - Event Function. Fires when autocompleter list hides.
		customTarget - (element, optional). Allows to override the autocompleter list element with your own list element.
		className - (string, default 'autocompleter-choices'). Class name for the list element.
		zIndex - (integer, default 42). z-Index for the list element.
		observerOptions - optional Options Object. For the Observer class.
		fxOptions - optional Options Object. For the Fx.Style on the list element.
		allowMulti - (boolean, defaults to false) allow more than one value, seperated by a delimeter
		delimeter - (string) default delimter between multi values (defaults to ", ")
		autotrim - (boolean) trim the delimeter on blur
		allowDupes - (boolean, defaults to false) if multi, prevent duplicate entries
		baseHref - (string) the base url where the css and image assets are located (defaults to cnet's servers you should change)

		Note:
		If you're not cnet, you should download these assets to your own local:
		http://www.cnet.com/html/rb/assets/global/autocompleter/Autocompleter.css
		http://www.cnet.com/html/rb/assets/global/autocompleter/spinner.gif
		
		Then either change this script or pass in the local when you instantiate the class.
		
		Example:
		This base class is not used directly (but rather its inheritants are such as <Autocompleter.Ajax>)
		so there is no example here.
	*/
Autocompleter.Base = new Class({

	options: {
		minLength: 1,
		useSelection: true,
		markQuery: true,
		inheritWidth: true,
		dropDownWidth: 100,
		maxChoices: 10,
		injectChoice: null,
		onSelect: Class.empty,
		onShow: Class.empty,
		onHide: Class.empty,
		customTarget: null,
		className: 'autocompleter-choices',
		zIndex: 42,
		observerOptions: {},
		fxOptions: {},
		multi: false,
		delimeter: ', ',
		autotrim: true,
		allowDupes: false,
		/*	if you're not cnet, you should download these assets to your own local:
				http://www.cnet.com/html/rb/assets/global/autocompleter/Autocompleter.css
				http://www.cnet.com/html/rb/assets/global/autocompleter/spinner.gif
			*/
		baseHref: 'http://www.cnet.com/html/rb/assets/global/autocompleter/'
	},

	initialize: function(el, options) {
		this.setOptions(options);
		if(!$('AutocompleterCss')) window.addEvent('domready', function(){
			new Asset.css(this.options.baseHref+'Autocompleter.css', {id: 'AutocompleterCss'});
		}.bind(this));
		this.element = $(el);
		this.build();
		this.observer = new Observer(this.element, this.prefetch.bind(this), $merge({
			delay: 400
		}, this.options.observerOptions));
		this.value = this.observer.value;
		this.queryValue = null;
		this.element.addEvent('blur', function(e){
			this.autoTrim.delay(50, this, e);
		}.bind(this));
		this.addEvent('onSelect', function(){
			this.element.focus();
			this.userChose = true;
			(function(){
				this.userChose = false;
			}).delay(100, this);
		}.bind(this));
	},

/*	Property: build
		Builds the html structure for choices and appends the events to the element.
		Override this function to modify the html generation.	*/

	build: function() {
		if ($(this.options.customTarget)) this.choices = this.options.customTarget;
		else {
			this.choices = new Element('ul', {
				'class': this.options.className,
				'styles': {zIndex: this.options.zIndex}
				}).injectInside(document.body);
			this.fix = new OverlayFix(this.choices);
		}
		this.fx = this.choices.effect('opacity', $merge({wait: false, duration: 200}, this.options.fxOptions))
			.addEvent('onStart', function() {
				if (this.fx.now) return;
				this.choices.setStyle('display', '');
				this.fix.show();
			}.bind(this))
			.addEvent('onComplete', function() {
				if (this.fx.now) return;
				this.choices.setStyle('display', 'none');
				this.fix.hide();
			}.bind(this)).set(0);
		this.element.setProperty('autocomplete', 'off')
			.addEvent(window.ie ? 'keydown' : 'keypress', this.onCommand.bindWithEvent(this))
			.addEvent('mousedown', this.onCommand.bindWithEvent(this, [true]))
			.addEvent('focus', this.toggleFocus.bind(this, [true]))
			.addEvent('blur', this.toggleFocus.bind(this, [false]))
			.addEvent('trash', this.destroy.bind(this));
	},
	
	autoTrim: function(e){
		if(this.userChose) return this.userChose = false;
		var del = this.options.delimeter;
		var val = this.element.getValue();
		if(this.options.autotrim && val.test(del+"$")){
			e = new Event(e);
			this.observer.value = this.element.value = val.substring(0, val.length-del.length);
		}
	},
/*	Property: getQueryValue
		Returns the user's input to use to match against the full list. When options.multi == true, this value is the last entered string after the last index of the delimeter.
		
		Arguments:
		value - (string) optional, the value to clean; defaults to this.observer.value

		Examples:
(start code)
user input: blue
getQueryValue() returns "blue"

user input: blue, green, yellow
options.multi = true
options.delimter = ", "
getQueryValue() returns "yellow"

user input: blue, green, yellow, 
options.multi = true
options.delimter = ", "
getQueryValue() returns ""
(end)
	*/
	getQueryValue: function(value){
		value = $pick(value, this.observer.value);
		return (this.options.multi)?value.lastElement(this.options.delimeter):value||'';
	},
	
/*	Property: destroy
		Remove the autocomplete functionality from the input.
	*/
	destroy: function() {
		this.choices.remove();
	},

	toggleFocus: function(state) {
		this.focussed = state;
		if (!state) this.hideChoices();
	},

	onCommand: function(e, mouse) {
		var val = this.getQueryValue();
		if (mouse && this.focussed) this.prefetch();
		if (e.key) switch (e.key) {
			case 'enter':
				if (this.selected && this.visible) {
					this.choiceSelect(this.selected);
					e.stop();
				} return;
			case 'up': case 'down':
				if (this.getQueryValue() != (val || this.queryValue)) this.prefetch();
				else if (this.queryValue === null) break;
				else if (!this.visible) this.showChoices();
				else {
					this.choiceOver((e.key == 'up')
						? this.selected.getPrevious() || this.choices.getLast()
						: this.selected.getNext() || this.choices.getFirst() );
					this.setSelection();
				}
				e.stop(); return;
			case 'esc': case 'tab': 
				this.hideChoices(); 
				if (this.options.multi) this.element.value = this.element.getValue().trimLastElement();
				return;
		}
		this.value = false;
	},

	setSelection: function() {
		if (!this.options.useSelection) return;
		var del = this.options.delimeter;
		var qVal = this.getQueryValue(this.queryValue);
		var elVal = this.getQueryValue(this.element.getValue());
		var startLength;
		if(this.options.multi)	{
			var index = this.queryValue.lastIndexOf(del);
			var delLength = (index<0)?0:del.length;
			startLength = qVal.length+(index<0?0:index)+delLength;
		} else startLength = qVal.length;

		if (elVal.indexOf(qVal) != 0) return;
		var insert = this.selected.inputValue.substr(startLength);
		if (window.ie) {
			var sel = document.selection.createRange();
			sel.text = insert;
			sel.move("character", - insert.length);
			sel.findText(insert);
			sel.select();
		} else {
			var offset = (this.options.multi && this.element.value.test(del))?
				this.element.getValue().length-elVal.length+qVal.length
				:this.queryValue.length;
			this.element.value = this.element.value.substring(0, offset) + insert;
			this.element.selectionStart = offset;
			this.element.selectionEnd = this.element.value.length;
		}
		this.value = this.observer.value = this.element.value;
	},
/*	Property: hideChoices
		Hides the choices from the user.
	*/
	hideChoices: function() {
		if (!this.visible) return;
		this.visible = this.value = false;
		this.observer.clear();
		this.fx.start(0);
		this.fireEvent('onHide', [this.element, this.choices]);
	},

/*	Property: showChoices
		Shows the choices to the user.
	*/
	showChoices: function() {
		if (this.visible || !this.choices.getFirst()) return;
		this.visible = true;
		var pos = this.element.getCoordinates(this.options.overflown);
		this.choices.setStyles({'left': pos.left, 'top': pos.bottom});
		this.choices.setStyle('width', (this.options.inheritWidth)?pos.width:this.options.dropDownWidth);
		this.fx.start(1);
		this.choiceOver(this.choices.getFirst());
		this.fireEvent('onShow', [this.element, this.choices]);
	},

	prefetch: function() {
		var val = this.getQueryValue(this.element.getValue());
		if (val.length < this.options.minLength) this.hideChoices();
		else if (val == this.queryValue) this.showChoices();
		else this.query();
	},

	updateChoices: function(choices) {
		this.choices.empty();
		this.selected = null;
		if (!choices || !choices.length) return;
		if (this.options.maxChoices < choices.length) choices.length = this.options.maxChoices;
		choices.each(this.options.injectChoice || function(choice, i){
			var el = new Element('li').setHTML(this.markQueryValue(choice));
			el.inputValue = choice;
			this.addChoiceEvents(el).injectInside(this.choices);
		}, this);
		this.showChoices();
	},

	choiceOver: function(el) {
		if (this.selected) this.selected.removeClass('autocompleter-selected');
		this.selected = el.addClass('autocompleter-selected');
	},

	choiceSelect: function(el) {
		if(this.options.multi) {
			var del = this.options.delimeter;
			var value = (this.element.value.trimLastElement(del) + el.inputValue).split(del);
			var fin = [];
			if (!this.options.allowDupes) {
				value.each(function(item){
					if(fin.contains(item))fin.remove(item); //move it to the end
					fin.include(item);
				})
			} else fin = value;
			this.observer.value = this.element.value = fin.join(del)+del;
		} else this.observer.value = this.element.value = el.inputValue;
		
		
		this.hideChoices();
		this.fireEvent('onSelect', [this.element], 20);
	},

/*	Property: markQueryValue
		Marks the queried word in the given string with <span class="autocompleter-queried">*</span>
		Call this i.e. from your custom parseChoices, same for addChoiceEvents
		
		Arguments:
		txt - (string) the string to mark
	 */
	markQueryValue: function(txt) {
		var val = (this.options.mult)?this.lastQueryElementValue:this.queryValue;
		return (this.options.markQuery && val) ? txt.replace(new RegExp('^(' + val.escapeRegExp() + ')', 'i'), '<span class="autocompleter-queried">$1</span>') : txt;
	},

/*	Property: addChoiceEvents
		Appends the needed event handlers for a choice-entry to the given element.
		
		Arguments:
		el - (DOM element or id) the element to add
*/
	addChoiceEvents: function(el) {
		return el.addEvents({
			'mouseover': this.choiceOver.bind(this, [el]),
			'mousedown': this.choiceSelect.bind(this, [el])
		});
	},
	query: Class.empty
});

Autocompleter.Base.implement(new Events);
Autocompleter.Base.implement(new Options);

/*	Class: OverlayFix
		Private class used by <Autocompleter> - basically an <IframeShim>.
	*/
var OverlayFix = new Class({

	initialize: function(el) {
		this.element = $(el);
		if (window.ie){
			this.element.addEvent('trash', this.destroy.bind(this));
			this.fix = new Element('iframe', {
					'properties': {'frameborder': '0', 'scrolling': 'no', 'src': 'javascript:false;'},
					'styles': {'position': 'absolute', 'border': 'none', 'display': 'none', 'filter': 'progid:DXImageTransform.Microsoft.Alpha(opacity=0)'}})
				.injectAfter(this.element);
		}
	},

	show: function() {
		if (this.fix) this.fix.setStyles($extend(
			this.element.getCoordinates(), {'display': '', 'zIndex': (this.element.getStyle('zIndex') || 1) - 1}));
		return this;
	},

	hide: function() {
		if (this.fix) this.fix.setStyle('display', 'none');
		return this;
	},

	destroy: function() {
		this.fix.remove();
	}

});

String.extend({
	lastElement: function(separator){
		separator = separator || ' ';
		var txt = this; //(separator.test(' $'))?this:this.trim();
		var index = txt.lastIndexOf(separator);
		var result = (index == -1)? txt: txt.substr(index + separator.length, txt.length);
		return result;
	},
 
 
	trimLastElement: function(separator){
		separator = separator || ' ';
		var txt = this; //(separator.test(' $'))?this:this.trim();
		var index = this.lastIndexOf(separator);
		return (index == -1)? "": txt.substr(0, index + separator.length);
	}
});

/* do not edit below this line */   
/* Section: Change Log 

$Source: /cvs/main/flatfile/html/rb/js/global/cnet.global.framework/common/3rdParty/Autocomplete/Autocompleter.js,v $
$Log: Autocompleter.js,v $
Revision 1.3  2007/06/12 20:26:52  newtona
*** empty log message ***

Revision 1.2  2007/06/07 18:43:35  newtona
added CSS to autocompleter.js
removed string.cnet.js dependencies from template parser and stickyWin.default.layout.js

Revision 1.1  2007/06/02 01:35:17  newtona
*** empty log message ***


*/
/*	Script: Autocompleter.JsonP.js
		Implements <JsonP> support for the <Autocompleter> class.
		
		Dependencies:
		Mootools 1.1 - <Class.Extras>, <Element.Event>, <Element.Selectors>, <Element.Form>, <Element.Dimensions>, <Fx.Style>, <Ajax>, <Json>
		Autocompleter - <Autocompleter.js>, <Observer.js>
		CNET - <jsonp.js>
		
		Author:
		Aaron Newton (aaron [dot] newton [at] cnet [dot] com)
		
		Class: Autocompleter.JsonP
		Implements <JsonP> support for the <Autocompleter> class.
		
		Arguments:
		el - (DOM element or id) element to observe.
		url - (string) the url to query for values
		options - (object) key/value set of options.

		Options:
		postVar - (string) the key to which the user's entry is mapped - passed to the server as postVar=userEntry (see example below)
		jsonpOptions - (object) options passed along to the <JsonP> class.
		onRequest - (callback function) fired when the request is sent
		onComplete - (callback function) fired when the request is complete
		minLength - (integer) Overrides minLength (defaults to 1).
		filterResponse - Function, optional. Allows to override default filterResponse method
		
		Example:
		Let's say the user is typing into an input to search for ipods and you need to take what they've typed ("ipo" so far) and send it to a server to get back filtered results like so:

http://server.com/handler.jsp?query=ipo
		
		Then the postVar option would be "query" so that the user's input is mapped to this key in the query string.
		
(start code)
var myCompleter = new Autocomplete.JsonP($('myinput'), 'http://server.com/handler.jsp', {
	postVar: 'query'
	...
});
(end)
	
		You're not really done though, because you need to handle the results that come back using the functionality in the base <Autocompleter> class. Here's an example that will work with the cnet API:

(start code)
new Autocompleter.JsonP($('jsonp'), 'http://api.cnet.com/restApi/v1.0/techProductSearch',
{
	jsonpOptions: {
		//this data gets added to the query string using JsonP's options
		data: {
			viewType: 'json',
			partKey: '19926949750937665684988687810562', //this is my code, user your own!
			iod:'none',
			start:0,
			results:10
		}
	},
	//require at least a key stroke from the user
	minLength: 1,
	//this function filters the results based on the input
	filterResponse: function(resp) {
		//test it
		if(!choices || choices.length == 0) return [];
		//filter it and return it
		var regex = new RegExp('^' + (this.queryValue || '').escapeRegExp(), 'i');
		return choices.filter(function(choice){
			return (regex.test(choice.Name.$) || regex.test(choice['@id']));
		});
	},
	useSelection: false,
	//because the data returned has a unique structure, we must manage the parsing ourselves
	filterResponse: function(resp) {
		try {
			//this structure is unique to the CNET API
			choices = resp.CNETResponse.TechProducts.TechProduct;
			//test it
			if(!choices || choices.length == 0) return [];
			//filter it and return it
			return choices.filter(function(choice){
				return (choice.Name.$.test(this.getQueryValue(), 'i') || choice['@id'].test(this.getQueryValue()), 'i');
			}.bind(this));
		} catch(e){'filterResponse error: ', dbug.log(e)}
	},
	injectChoice: function(choice) {
		//again, the structure of these items is unique to the CNET API
		if(! choice.Name.$)return;
		var el = new Element('li')
			.setHTML(this.markQueryValue(choice.Name.$))
			.adopt(new Element('span', {'class': 'example-info'}).setHTML(this.markQueryValue(choice['@id'])));
		el.inputValue = choice.Name.$+' ('+choice['@id']+')';
		this.addChoiceEvents(el).injectInside(this.choices);
	}
});
(end)
	*/

Autocompleter.JsonP = Autocompleter.Base.extend({

	options: {
		postVar: 'query',
		jsonpOptions: {},
		onRequest: Class.empty,
		onComplete: Class.empty,
		minLength: 1, 
		filterResponse: null
	},

	initialize: function(el, url, options) {
		this.url = url;
		this.parent(el, options);
		if (this.options.filterResponse) this.filterResponse = this.options.filterResponse.bind(this);
	},

	query: function(){
		var multi = this.options.multi;
		var data = $extend({}, this.options.jsonpOptions.data);
		if(multi) this.lastQueryElementValue = this.element.value.lastElement(this.options.delimeter);
		data[this.options.postVar] = (multi)?this.lastQueryElementValue:this.element.value;

		this.jsonp = new JsonP(this.url, $merge(
			{
				data: data
			},
			this.options.jsonpOptions
		));
		this.jsonp.addEvent('onComplete', this.queryResponse.bind(this));

		this.fireEvent('onRequest', [this.element, this.jsonp]);
		this.jsonp.request();
	},
	
/*	Property: queryResponse
		Inherated classes have to extend this function and use this.parent(resp)
		
		Arguments:
		resp - (String) the response from the ajax query.
*/
	queryResponse: function(resp) {
		try {
			this.value = this.queryValue = this.element.value;
			var choices = this.filterResponse(resp);
			this.selected = false;
			this.hideChoices();
		} catch(e) {
			try { dbug.log('jsonp request error: ', e); } catch(e) {}
		}
		this.fireEvent(choices ? 'onComplete' : 'onFailure', [this.element, choices], 20);
		if (!choices || !choices.length) return;
		this.updateChoices(choices);
	},

	filterResponse: function(resp) {
		var regex = new RegExp('^' + this.queryValue.escapeRegExp(), 'i');
		return this.tokens.filter(function(token) {
			return regex.test(token);
		});
	}

});
/* do not edit below this line */   
/* Section: Change Log 

$Source: /cvs/main/flatfile/html/rb/js/global/cnet.global.framework/common/3rdParty/Autocomplete/Autocompleter.JsonP.js,v $
$Log: Autocompleter.JsonP.js,v $
Revision 1.1  2007/06/12 20:26:52  newtona
*** empty log message ***


*/
/*	Script: Autocompleter.Local.js
		Extends the <Autocompleter.Base> class to add support for a pre-defined object.
		
		Class: Autocompleter.Local
		Extends the <Autocompleter.Base> class to add support for a pre-defined object.
		
		Arguments:
		el - (DOM element or id) element to observe.
		tokens - (Array) an array of values
		options - (object) key/value set of options.
		
		Options:
		All values passed to <Autocompleter.Base>
		
		minLength - Overrides minLength to 0.
		filterTokens - Function, optional. Allows to override default filterTokens method

		Example:
(start code)
//this object's structure is arbitrary
var tokens = [
	['Apple', 'Red'],
	['Lemon', 'Yellow'],
	['Grape', 'Purple']	
];

new Autocompleter.Local($('myInput'), tokens, {
	delay: 100,
	//this is a custom filter because our object has a unique structure
	filterTokens: function() {
		var regex = new RegExp('^' + (this.queryValue || '').escapeRegExp(), 'i');
		var filtered = this.tokens.filter(function(token){
			return (regex.test(token[0]) || regex.test(token[1]));
		});
		return filtered;
	},
	//again, because our data structure is unique, we must handle the results ourselves
	injectChoice: function(choice) {
		var el = new Element('li')
			.setHTML(this.markQueryValue(choice[0]))
			.adopt(new Element('span', {'class': 'example-info'}).setHTML(this.markQueryValue(choice[1])));
		el.inputValue = choice[0];
		this.addChoiceEvents(el).injectInside(this.choices);
	}
});
(end)
	*/

Autocompleter.Local = Autocompleter.Base.extend({
	options: {
		minLength: 0,
		filterTokens : null
	},
	initialize: function(el, tokens, options) {
		this.parent(el, options);
		this.tokens = tokens;
		if (this.options.filterTokens) this.filterTokens = this.options.filterTokens.bind(this);
	},
	query: function() {
		this.hideChoices();
		this.queryValue = (this.options.multi)?
				this.element.value.lastElement(this.options.delimeter).trim()
				:this.element.value;
		this.updateChoices(this.filterTokens());
	},
	filterTokens: function(token) {
		var regex = new RegExp('^' + this.queryValue.escapeRegExp(), 'i');
		return this.tokens.filter(function(token) {
			return regex.test(token);
		});
	}
});
/* do not edit below this line */   
/* Section: Change Log 

$Source: /cvs/main/flatfile/html/rb/js/global/cnet.global.framework/common/3rdParty/Autocomplete/Autocompleter.Local.js,v $
$Log: Autocompleter.Local.js,v $
Revision 1.1  2007/06/12 20:26:52  newtona
*** empty log message ***


*/
/*	Script: Autocompleter.Remote.js
		Contains the classes for the Remote methods for <Autocompleter>.

		Namespace: Autocompleter.Ajax
		Contains the classes for the Remote methods for <Autocompleter>
		
		Details:
		Author - Harald Kirschner <mail [at] digitarald.de>
		Refactored by - Aaron Newton <aaron [dot] newton [at] cnet [dot] com>
		License - MIT-style license
		Version - 1.0rc3
		Source - http://digitarald.de/project/autocompleter/
		
		Dependencies:
		Mootools 1.1 - <Class.Extras>, <Element.Event>, <Element.Selectors>, <Element.Form>, <Element.Dimensions>, <Fx.Style>, <Ajax>, <Json>
		Autocompleter - <Autocompleter.js>, <Observer.js>
	*/
Autocompleter.Ajax = {};
/*	Class: Autocompleter.Ajax.Base
		The base functionality for all <Autocompleter.Ajax> functionality.
		
		Arguments:
		el - (DOM element or id) element to observe.
		url - (string) the url to query for values
		options - (object) key/value set of options.
		
		Options:
		postVar - String, default ‘value’. Post variable name for the query string
		postData - Object, optional. Additional request data
		ajaxOptions - optional Options Object. For the Ajax instance
		onRequest - Event Function.
		onComplete - Event Function.
		
		Example:
		The <Autocompleter.Ajax.Base> class is not used directly but rather its inhertants are (see 
		<Autocompleter.Ajax.Json> and <Autocompleter.Ajax.Xhtml>) so there is no example here.
	*/
Autocompleter.Ajax.Base = Autocompleter.Base.extend({

	options: {
		postVar: 'value',
		postData: {},
		ajaxOptions: {},
		onRequest: Class.empty,
		onComplete: Class.empty
	},

	initialize: function(el, url, options) {
		this.parent(el, options);
		this.ajax = new Ajax(url, $merge({
			autoCancel: true
		}, this.options.ajaxOptions));
		this.ajax.addEvent('onComplete', this.queryResponse.bind(this));
		this.ajax.addEvent('onFailure', this.queryResponse.bind(this, [false]));
	},

	query: function(){
		var multi = this.options.multi;
		var data = $extend({}, this.options.postData);
		if(multi) this.lastQueryElementValue = this.element.value.lastElement(this.options.delimeter);
		data[this.options.postVar] = (multi)?this.lastQueryElementValue:this.element.value;
		this.fireEvent('onRequest', [this.element, this.ajax]);
		this.ajax.request(data);
	},
	
/*	Property: queryResponse
		Inherated classes have to extend this function and use this.parent(resp)
		
		Arguments:
		resp - (String) the response from the ajax query.
*/
	queryResponse: function(resp) {
		this.value = this.queryValue = this.element.value;
		this.selected = false;
		this.hideChoices();
		this.fireEvent(resp ? 'onComplete' : 'onFailure', [this.element, this.ajax], 20);
	}

});

/*	Class: Autocompleter.Ajax.Json
		Extends <Autocompleter.Ajax.Base> to include Json support.
		
		Arguments:
		All those specified in <Autocompleter.Ajax.Base> and <Autocompleter.Base>.
		
		Example:
new Autocompleter.Ajax.Json($('ajaxJson'), 'server/auto.php' {
	postVar: 'query'
});
	*/
Autocompleter.Ajax.Json = Autocompleter.Ajax.Base.extend({

	queryResponse: function(resp) {
		this.parent(resp);
		var choices = Json.evaluate(resp || false);
		if (!choices || !choices.length) return;
		this.updateChoices(choices);
	}

});

/*	Class: Autocompleter.Ajax.Xhtml
		Extends <Autocompleter.Ajax.Base> to include Xhtml support.

		Arguments:
		All those specified in <Autocompleter.Ajax.Base> and <Autocompleter.Base>.

		Example:		
(start code)
new Autocompleter.Ajax.Xhtml($('ajaxXhtml'), 'server/auto.php', {
	postData: {html: 1}, //some data to go along with the request
	//handle the data returned
	parseChoices: function(el) {
		var value = el.getFirst().innerHTML;
		el.inputValue = value;
		this.addChoiceEvents(el).getFirst().setHTML(this.markQueryValue(value));
	}
});
(end)
	*/
Autocompleter.Ajax.Xhtml = Autocompleter.Ajax.Base.extend({

	options: {
		parseChoices: null
	},

	queryResponse: function(resp) {
		this.parent(resp);
		if (!resp) return;
		this.choices.setHTML(resp).getChildren().each(this.options.parseChoices || this.parseChoices, this);
		this.showChoices();
	},

	parseChoices: function(el) {
		var value = el.innerHTML;
		el.inputValue = value;
		el.setHTML(this.markQueryValue(value));
	}

});

/* do not edit below this line */   
/* Section: Change Log 

$Source: /cvs/main/flatfile/html/rb/js/global/cnet.global.framework/common/3rdParty/Autocomplete/Autocompleter.Remote.js,v $
$Log: Autocompleter.Remote.js,v $
Revision 1.1  2007/06/12 20:26:52  newtona
*** empty log message ***


*/
/*	Script: Observer.js
		Observes formelements for changes; part of the <Autocomplete> 3rd party package.
		
		Details:
		Author - Harald Kirschner <mail [at] digitarald.de>
		License - MIT-style license
		Version - 1.0rc1
		Source - http://digitarald.de/project/autocompleter/
		
		Dependencies:
		Mootools 1.1 - <Class>, <Class.Extras>, <Element.Event>
		
		Class: Observer
		Observes form elements for changes; part of the <Autocomplete> 3rd party package.
		
		Arguments:
		el - (DOM element or id) element to observe
		onFired - (function) event to execute periodically and/or on keyup
		options - (object) a set of key/value options
		
		Options:
		periodical - (boolean) update and fire the onFired event regularly; defaults to false
		delay - (integer) how often to update using periodical if (periodical is true); defaults to 1000
	*/
var Observer = new Class({

	options: {
		periodical: false,
		delay: 1000
	},

	initialize: function(el, onFired, options){
		this.setOptions(options);
		this.addEvent('onFired', onFired);
		this.element = $(el);
		this.listener = this.fired.bind(this);
		this.value = this.element.getValue();
		if (this.options.periodical) this.timer = this.listener.periodical(this.options.periodical);
		else this.element.addEvent('keyup', this.listener);
	},

	fired: function() {
		var value = this.element.getValue();
		if (this.value == value) return;
		this.clear();
		this.value = value;
		this.timeout = this.fireEvent.delay(this.options.delay, this, ['onFired', [value]]);
	},

	clear: function() {
		$clear(this.timeout);
		return this;
	}
});

Observer.implement(new Options);
Observer.implement(new Events);

/* do not edit below this line */   
/* Section: Change Log 

$Source: /cvs/main/flatfile/html/rb/js/global/cnet.global.framework/common/3rdParty/Autocomplete/Observer.js,v $
$Log: Observer.js,v $
Revision 1.2  2007/06/12 20:26:52  newtona
*** empty log message ***

Revision 1.1  2007/06/02 01:35:17  newtona
*** empty log message ***


*/
	/*	Script: slimbox.js
		A lightbox clone for Mootools.
		
		Authors:
		Christophe Beyls (http://www.digitalia.be); MIT-style license.
		Inspired by the original Lightbox v2 by Lokesh Dhakar.
		Refactored by Aaron Newton <aaron [dot] newton [at] cnet [dot] com>
		
		Class: Lightbox
		A lightbox for displaying images in an overlay.
		
		Arguments:
		options - (object) a set of key/value options
		anchors - (array; optional) a group of anchors to which to add lightbox functionality.
		
		Options:
		resizeDuration - (integer) duration in milliseconds for the resize effect (defaults to 400)
		resizeTransition - optional <Fx.Transitions> transition reference
		initialWidth - (integer) the initial width of the box - defaults to 250
		initialHeight - (integer) the height width of the box - defaults to 250
		zIndex - (integer) zindex for the overlay (defaults to 10);
		overlayStyles - (object) styles object to pass to <Element.setStyle> for the modal layer (so you can set it to be whatever color or opacity you like). Note that the default styles are located in the (external) css file.
		animateCaption - (boolean) slide the caption in smoothly (defaults to true)
		showCounter - (boolean) shows the number of images in the set (defaults to true)
		autoScanLinks - (boolean) scan the document for anchor tags with rel tags == the relString option
		relString - (string) the string value for the "rel" tag that will make the link use the lightbox 
		                     (defaults to "lightbox"; unused if the anchors argument is specified)
	  useDefaultCss - (boolean) injects the default css for the lightbox; defauls to true
		assetBaseUrl - (string) the url where the css and image assets are (a directory); defaults to
		                     "http://www.cnet.com/html/rb/assets/global/slimbox/"
		onImageShow - (function) optional callback fired when an image is displayed
		onDisplay - (function) optional callback fired when the lightbox first shows up (onImageShow 
													 is fired just after this for the first image displayed)
		onHide - (function) optional callback fired when the lightbox is closed.
		
Examples:
(start code)
new Lightbox(); //defaults; scans the document for rel="lightbox...
new Lightbox({
	autoScanLinks: false
}, $$('a.lightbox')); //use all anchor tags with class "lightbox" instead
(end)

		Note:
		A new Lightbox is created on domReady, so it is not required that you write any javascript at all.
		All you must do is add rel="lightbox" to your images (and rel="lightbox[setName]" for sets). If you want
		to create a Lightbox on the fly or with some other set of images, you can do that whenever you like.
*/
var Lightbox = new Class({
	options: {
		resizeDuration: 400,
		resizeTransition: false,	// default transition
		initialWidth: 250,
		initialHeight: 250,
		zIndex: 10,
		animateCaption: true,
		showCounter: true,
		autoScanLinks: true,
		relString: 'lightbox',
		useDefaultCss: true,
		assetBaseUrl: 'http://www.cnet.com/html/rb/assets/global/slimbox/',
		onImageShow: Class.empty,
		onDisplay: Class.empty,
		onHide: Class.empty,
		overlayStyles: {}
	},

	initialize: function(options, anchors){
		this.setOptions(options);
		if(this.options.useDefaultCss) this.addCss();
		this.anchors = anchors || [];
		if (this.options.autoScanLinks) {
			$$('a').each(function(el){
				if (el.getProperty('rel') && el.getProperty('rel').test("^"+this.options.relString,'i')){
					if(!el.getProperty('lightboxed')) this.anchors.push(el);
				}
			}, this);
		}
		if(!$$(this.anchors).length) return; //no links!
		$$(this.anchors).each(function(el){
			if(!el.getProperty('lightboxed')) {
				el.setProperty('lightboxed', true);
				el.addEvent('click', function(e){
					new Event(e).stop();
					this.click(el);
				}.bind(this));
			}
		}.bind(this));
		this.eventKeyDown = this.keyboardListener.bindAsEventListener(this);
		this.eventPosition = this.position.bind(this);
		window.addEvent('domready', this.addHtmlElements.bind(this));
	},

	addHtmlElements: function(){
		this.overlay = new Element('div', {
			'class': 'lbOverlay',
			styles: {
				'z-index':this.options.zIndex
			}
		}).injectInside(document.body).setStyles(this.options.overlayStyles);
		this.center = new Element('div', {
			styles: {	
				width: this.options.initialWidth+'px', 
				height: this.options.initialHeight+'px', 
				'margin-left': (-(this.options.initialWidth/2))+'px', 'display': 'none',
				'z-index':this.options.zIndex+1
			}
		}).injectInside(document.body).addClass('lbCenter');
		this.image = new Element('div', {
			'class': 'lbImage'
		}).injectInside(this.center);
		
		this.prevLink = new Element('a', {'class': 'lbPrevLink', 'href': 'javascript:void(0);', 'styles': {'display': 'none'}}).injectInside(this.image);
		this.nextLink = this.prevLink.clone().removeClass('lbPrevLink').addClass('lbNextLink').injectInside(this.image);
		this.prevLink.onclick = this.previous.bind(this);
		this.nextLink.onclick = this.next.bind(this);

		this.bottomContainer = new Element('div', {'class': 'lbBottomContainer', 'styles': {'display': 'none', 'z-index':this.options.zIndex+1}}).injectInside(document.body);
		this.bottom = new Element('div', {'class': 'lbBottom'}).injectInside(this.bottomContainer);
		new Element('a', {'class': 'lbCloseLink', 'href': '#'}).injectInside(this.bottom).onclick = this.overlay.onclick = this.close.bind(this);
		this.caption = new Element('div', {'class': 'lbCaption'}).injectInside(this.bottom);
		this.number = new Element('div', {'class': 'lbNumber'}).injectInside(this.bottom);
		new Element('div', {'styles': {'clear': 'both'}}).injectInside(this.bottom);

		var nextEffect = this.nextEffect.bind(this);
		this.fx = {
			overlay: this.overlay.effect('opacity', {duration: 500}).hide(),
			resize: this.center.effects($extend({duration: this.options.resizeDuration, onComplete: nextEffect}, this.options.resizeTransition ? {transition: this.options.resizeTransition} : {})),
			image: this.image.effect('opacity', {duration: 500, onComplete: nextEffect}),
			bottom: this.bottom.effect('margin-top', {duration: 400, onComplete: nextEffect})
		};

		this.preloadPrev = new Element('img');
		this.preloadNext = new Element('img');
	},
	
	addCss: function(){
		window.addEvent('domready', function(){
			if(!$('SlimboxCss')) new Asset.css(this.options.assetBaseUrl + 'slimbox.css', {id: 'SlimboxCss'});
		}.bind(this));
	},

	click: function(link){
		link = $(link);
		var rel = link.getProperty('rel')||this.options.relString;
		if (rel == this.options.relString) return this.show(link.href, link.title);

		var j, imageNum, images = [];
		this.anchors.each(function(el){
			if (el.getProperty('rel') == link.getProperty('rel')){
				for (j = 0; j < images.length; j++) if(images[j][0] == el.href) break;
				if (j == images.length){
					images.push([el.href, el.title]);
					if (el.href == link.href) imageNum = j;
				}
			}
		}, this);
		return this.open(images, imageNum);
	},

	show: function(url, title){
		return this.open([[url, title]], 0);
	},

	open: function(images, imageNum){
		this.fireEvent('onDisplay');
		this.images = images;
		this.position();
		this.setup(true);
		this.top = (window.getScrollTop() + (window.getHeight() / 15)).toInt();
		this.center.setStyles({
			top: this.top+'px', 
			display: ''
		});
		this.fx.overlay.start(0.8);
		return this.changeImage(imageNum);
	},

	position: function(){
		this.overlay.setStyles({
			'top': window.getScrollTop()+'px', 
			'height': window.getHeight()+'px'
		});
	},

	setup: function(open){
		var elements = $$('object, iframe');
		elements.extend($$(window.ie ? 'select' : 'embed'));
		elements.each(function(el){
			if (open) el.lbBackupStyle = el.getStyle('visibility');
			var vis = (open ? 'hidden' : el.lbBackupStyle);
			el.setStyle('visibility', vis);
		});
		var fn = open ? 'addEvent' : 'removeEvent';
		window[fn]('scroll', this.eventPosition)[fn]('resize', this.eventPosition);
		document[fn]('keydown', this.eventKeyDown);
		this.step = 0;
	},

	keyboardListener: function(event){
		switch (event.keyCode){
			case 27: case 88: case 67: this.close(); break;
			case 37: case 80: this.previous(); break;	
			case 39: case 78: this.next();
		}
	},

	previous: function(){
		return this.changeImage(this.activeImage-1);
	},

	next: function(){
		return this.changeImage(this.activeImage+1);
	},

	changeImage: function(imageNum){
		this.fireEvent('onImageShow', imageNum);
		if (this.step || (imageNum < 0) || (imageNum >= this.images.length)) return false;
		this.step = 1;
		this.activeImage = imageNum;

		this.center.setStyle('backgroundColor', '');
		this.bottomContainer.setStyle('display', 'none');
		this.prevLink.setStyle('display', 'none');
		this.nextLink.setStyle('display', 'none');
		this.fx.image.hide();
		this.center.addClass('lbLoading');

		this.preload = new Element('img').addEvent('load', this.nextEffect.bind(this)).setProperty('src', this.images[imageNum][0]);
		return false;
	},

	nextEffect: function(){
		switch (this.step++){
		case 1:
			this.image.setStyle('backgroundImage', 'url('+this.images[this.activeImage][0]+')');
			this.image.setStyle('width', this.preload.width+'px');
			this.bottom.setStyle('width',this.preload.width+'px');
			this.image.setStyle('height', this.preload.height+'px');
			this.prevLink.setStyle('height', this.preload.height+'px');
			this.nextLink.setStyle('height', this.preload.height+'px');

			this.caption.setHTML(this.images[this.activeImage][1] || '');
			this.number.setHTML((!this.options.showCounter || (this.images.length == 1)) ? '' : 'Image '+(this.activeImage+1)+' of '+this.images.length);

			if (this.activeImage) $(this.preloadPrev).setProperty('src', this.images[this.activeImage-1][0]);
			if (this.activeImage != (this.images.length - 1)) 
				$(this.preloadNext).setProperty('src',  this.images[this.activeImage+1][0]);
			if (this.center.clientHeight != this.image.offsetHeight){
				this.fx.resize.start({height: this.image.offsetHeight});
				break;
			}
			this.step++;
		case 2:
			if (this.center.clientWidth != this.image.offsetWidth){
				this.fx.resize.start({width: this.image.offsetWidth, marginLeft: -this.image.offsetWidth/2});
				break;
			}
			this.step++;
		case 3:
			this.bottomContainer.setStyles({
				top: (this.top + this.center.getSize().size.y)+'px', 
				height: '0px', 
				marginLeft: this.center.getStyle('margin-left'), 
				display: ''
			});
			this.fx.image.start(1);
			break;
		case 4:
			this.center.style.backgroundColor = '#000';
			if (this.options.animateCaption){
				this.fx.bottom.set(-this.bottom.offsetHeight);
				this.bottomContainer.setStyle('height', '');
				this.fx.bottom.start(0);
				break;
			}
			this.bottomContainer.style.height = '';
		case 5:
			if (this.activeImage) this.prevLink.setStyle('display', '');
			if (this.activeImage != (this.images.length - 1)) this.nextLink.setStyle('display', '');
			this.step = 0;
		}
	},

	close: function(){
		this.fireEvent('onHide');
		if (this.step < 0) return;
		this.step = -1;
		if (this.preload){
			this.preload.onload = Class.empty;
			this.preload = null;
		}
		for (var f in this.fx) this.fx[f].stop();
		this.center.setStyle('display', 'none');
		this.bottomContainer.setStyle('display', 'none');
		this.fx.overlay.chain(this.setup.pass(false, this)).start(0);
		return false;
	}
});
Lightbox.implement(new Options);
Lightbox.implement(new Events);
window.addEvent('domready', function(){new Lightbox()});
/* do not edit below this line */   
/* Section: Change Log 

$Source: /cvs/main/flatfile/html/rb/js/global/cnet.global.framework/common/3rdParty/Slimbox/slimbox.js,v $
$Log: slimbox.js,v $
Revision 1.10  2007/06/29 21:58:23  newtona
dangit. missing semi-colon.

Revision 1.9  2007/06/29 21:33:58  newtona
adding some docs about that style property

Revision 1.8  2007/06/29 21:32:25  newtona
more re-writes of this 3rd party script. I've tested this more thoroughly and it's stable now.
added an option to style the modal layer (overlayStyles)

Revision 1.7  2007/06/29 19:28:09  newtona
numerous fixes; mostly adding mootools standard conventions like .setStyle instead of .style.<name> =  and addEvent
changed id namespaces for dom elements to classes

Revision 1.6  2007/06/29 00:22:47  newtona
refactoring to work with Mootools 1.0 for backwards support

Revision 1.5  2007/06/18 18:42:36  newtona
the overlay div is not added to the DOM if there are no lightbox links

Revision 1.4  2007/06/17 19:57:35  newtona
fixed an issue with using the document.links collection; now uses $$('a')

Revision 1.3  2007/06/15 15:52:41  newtona
docs update

Revision 1.2  2007/06/14 01:09:33  newtona
added zindex option; fixed a bug with sets.

Revision 1.1  2007/06/07 20:22:24  newtona
*** empty log message ***


*//*	Script: simple.editor.js
		A simple html editor for wrapping text with links and whatnot.
		
		Author:
		Aaron Newton <aaron [dot] newton [at] cnet [dot] com>
		
		Dependencies:
		Mootools - <Moo.js>, <String.js>, <Array.js>, <Function.js>, <Element.js>, <Dom.js>
		CNET - <Element.forms.js>, <Element.shortcuts.js>
		Optional - <Trinket.Base.js>, <Trinket.contexts.js>, <Trinket.LinkBuilder.js>, <StickyWinModal>, <stickyWinHTML>

		Class: SimpleEditor
		A simple html editor for wrapping text with links and whatnot.
		
		Arguments:
		input - (DOM element or id) the input this editor modifies
		buttons - (css selector or <Elements> collection) all the links 
							and/or buttons/images that make changes to the text when clicked.
		commands - (optional, object) a commands object (see below) for this editor.
		
		Commands:
		<SimpleEditor> comes with a handful of common commands to wrap text with bold tags or italics, etc. You can define your own and add them to all SimpleEditors or to a specific instance.

		A command id made up of a shortcut key and a function that is passed the input.
		
		Example:
(start code)
bold: {
	shortcut: 'b',
	command: function(input){
		input.insertAroundCursor({before:'<b>',after:'</b>'});
	}
}
(end)

		When the user clicks the button or hits ctrl+b, the tag will be inserted around the selected text.
		
		See <SimpleEditor.addCommand> and <SimpleEditor.addCommands> on how to add your own.
		
		Buttons/Links:
		The buttons passed in must have a property "rel" equal to the key of the command they execute.
		
		Example:
(start code)
<img src="bold.gif" alt="Bold (ctrl+b)" title="Bold (ctrl+b)" rel="bold">
(end)
		
		In the example above, the rel="bold" will map this image to the bold command.
	*/
var SimpleEditor = new Class({
	initialize: function(input, buttons, commands){
		this.commands = new Hash($merge(SimpleEditor.commands, commands||{}));
		this.input = $(input);
		this.buttons = $$(buttons);
		this.buttons.each(function(button){
			button.addEvent('click', function() {
				this.exec(button.getProperty('rel'));
			}.bind(this));
		}.bind(this));
		this.input.addEvent('keydown', function(e){
			e = new Event(e);
			if (e.control) {
				var key = this.shortCutToKey(e.key);
				if(key) {
					e.stop();
					this.exec(key);
				}
			}
		}.bind(this));
	},
	shortCutToKey: function(shortcut){
		var returnKey = false;
		this.commands.each(function(value, key){
			if(value.shortcut == shortcut) returnKey = key;
		});
		return returnKey;
	},
/*	Property: addCommand
		Inserts a single command to the SimpleEditor.
		
		*Note*: You can use this method on your instance of this class to add the command to that instance, or you can execute it on the class namespace and all <SimpleEditor> instances created after this will get these commands.

		Arguments:
		key - (string) the unique identifier for this command ("bold", "italics", etc.)
		command - (function) funciton to execute on the input; the function is passed the input as an argument
		shortcut - (character, optional) a shortcut key that, when pressed in conjunction with ctrl, will execute
								the function

		Example:
(start code)
//all instances will get bold tags as <strong></strong>
SimpleEditor.addCommand('bold', function(input) {
	input.insertAroundCursor({before:'<strong>',after:'</strong>'});
}, 'b')

//but this instance will get bold tags as <b></b>
var myEditor = new SimpleEditor(input, $$('img.editbuttons'));
myEditor.addCommand('bold', function(input){
	input.insertAroundCursor({before:'<b>',after:'</b>'});
}, 'b');
(end)
	*/
	addCommand: function(key, command, shortcut){
		this.commands.set(key, {
			command: command,
			shortcut: shortcut
		});
	},

/*	Property: addCommand
		Inserts a collection of commands to the SimpleEditor.
		
		*Note*: You can use this method on your instance of this class to add the command to that instance, or you can execute it on the class namespace and all <SimpleEditor> instances created after this will get these commands.

		Arguments:
		commands - (object) a key/value set of commands (see below)
		
		Commands:
		This is an object whose key is the command key. Its members are key/values for the shortcut value and the command function. The example below should illustrate this more clearly.

		Example:
(start code)
//all instances will get bold tags as <strong></strong> and italics as <em></em>
SimpleEditor.addCommands(SimpleEditor.addCommands({
	bold: {
		shortcut: 'b',
		command: function(input){
			input.insertAroundCursor({before:'<strong>',after:'</strong>'});
		}
	},
	italics: {
		shortcut: 'i',
		command: function(input){
			input.insertAroundCursor({before:'<em>',after:'</em>'});
		}
	}
));

//but this instance will get bold tags as <b></b> and italics as <i></i>
var myEditor = new SimpleEditor(input, $$('img.editbuttons'));
myEditor.addCommands(SimpleEditor.addCommands({
	bold: {
		shortcut: 'b',
		command: function(input){
			input.insertAroundCursor({before:'<b>',after:'</b>'});
		}
	},
	italics: {
		shortcut: 'i',
		command: function(input){
			input.insertAroundCursor({before:'<i>',after:'</i>'});
		}
	}
});
(end)
	*/
	addCommands: function(commands){
		this.commands.extend(commands);
	},
	exec: function(key){
		if(this.commands.hasKey(key)) this.commands.get(key).command(this.input);
	}
});
$extend(SimpleEditor, {
	commands: {},
	addCommand: function(key, command, shortcut){
		SimpleEditor.commands[key] = {
			command: command,
			shortcut: shortcut
		}
	},
	addCommands: function(commands){
		$extend(SimpleEditor.commands, commands);
	}
});
/*	Default commands:	*/
SimpleEditor.addCommands({
	/*	bold - <b></b>	*/
	bold: {
		shortcut: 'b',
		command: function(input){
			input.insertAroundCursor({before:'<b>',after:'</b>'});
		}
	},
	/*	underline - <u></u>	*/
	underline: {
		shortcut: 'u',
		command: function(input){
			input.insertAroundCursor({before:'<u>',after:'</u>'});
		}
	},
	/*	anchor - uses <Trinket.LinkBuilder> if present	*/
	anchor: {
		shortcut: 'l',
		command: function(input){
			function simpleLinker(){
				var href = window.prompt('The URL for the link');
				var opts = {before: '<a href="'+href+'">', after:'</a>'};
				if (!input.getSelectedText()) opts.defaultMiddle = window.prompt('The link text');
				input.insertAroundCursor(opts);
			}
			try {
				if(Trinket) {
					if(!this.linkBulder){
						var lb = Trinket.available.filter(function(trinket){
							return trinket.name == 'Link Builder';
						});
						this.linkBuilder = (lb.length)?lb[0]:new Trinket.LinkBuilder({
							context: 'default'
						});
						this.linkBuilder.clickPrompt(input);
					}
				} else simpleLinker();
			} catch(e){ simpleLinker(); }
		}
	},
	/*	copy - if <Clipboard.js> is present	*/
	copy: {
		shortcut: false,
		command: function(input){
			if(Clipboard) Clipboard.copyFromElement(input);
			else simpleErrorPopup('Woops', 'Sorry, this function doesn\'t work here; use ctrl+c.');
			input.focus();
		}
	},
	/*	cut - if <Clipboard.js> is present	*/
	cut: {
		shortcut: false,
		command: function(input){
			if(Clipboard) {
				Clipboard.copyFromElement(input);
				input.insertAtCursor('');
			} else simpleErrorPopup('Woops', 'Sorry, this function doesn\'t work here; use ctrl+x.');
		}
	},
	/*	hr - <hr/>	*/
	hr: {
		shortcut: '-',
		command: function(input){
			input.insertAtCursor('\n<hr/>\n');
		}
	},
	/*	img - <img src="">	*/
	img: {
		shortcut: 'g',
		command: function(input){
			input.insertAtCursor('<img src="'+window.prompt('The url to the image')+'" />');
		}
	},
	/*	stripTags - removes all tags from the selection	*/
	stripTags: {
		shortcut: '\\',
		command: function(input){
			input.insertAtCursor(input.getSelectedText().stripTags());
		}
	},
	/*	supertext - <sup></sup>	*/
	sup: {
		shortcut: false,
		command: function(input){
			input.insertAroundCursor({before:'<sup>', after: '</sup>'});
		}
	},
	/*	subtext - <sub></sub>	*/
	sub: {
		shortcut: false,
		command: function(input){
			input.insertAroundCursor({before:'<sub>', after: '</sub>'});
		}
	},
	/*	paragraph - <p></p>	*/
	paragraph: {
		shortcut: 'enter',
		command: function(input){
			input.insertAroundCursor({before:'\n<p>\n', after: '\n</p>\n'});
		}
	},
	/*	strike - <strike></strike>	*/
	strike: {
		shortcut: 'k',
		command: function(input){
			input.insertAroundCursor({before:'<strike>',after:'</strike>'});
		}
	},
	/*	italics - <i></i>	*/
	italics: {
		shortcut: 'i',
		command: function(input){
			input.insertAroundCursor({before:'<i>',after:'</i>'});
		}
	},
	/*	bullets - <ul><li></li></ul>	*/
	bullets: {
		shortcut: '8',
		command: function(input){
			input.insertAroundCursor({before:'<ul>\n  <li>',after:'</li>\n</ul>'});
		}
	},
	/*	numberList - <ol><li></li></ol>	*/
	numberList: {
		shortcut: '=',
		command: function(input){
			input.insertAroundCursor({before:'<ol>\n  <li>',after:'</li>\n</ol>'});
		}
	},
	/*	clean - removes non-asci MSword style characters with <Element.tidy>	*/
	clean: {
		shortcut: false,
		command: function(input){
			input.tidy();
		}
	},
	/*	preview - uses <StickyWinModal>	to display a preview */
	preview: {
		shortcut: false,
		command: function(input){
			try {
				if(!this.container){
					this.container = new Element('div', {
						styles: {
							border: '1px solid black',
							padding: 8,
							height: 300,
							overflow: 'auto'
						}
					});
					this.preview = new StickyWinModal({
						content: stickyWinHTML("preview", this.container, {
							width: 600,
							buttons: [{
								text: 'close',
								onClick: function(){
									this.container.empty();
								}.bind(this)
							}]
						}),
						showNow: false
					});
				}
				this.container.setHTML(input.getValue());
				this.preview.show();
			} catch(e){dbug.log('you need StickyWinModal and stickyWinHTML')}
		}
	}
});
/* do not edit below this line */   
/* Section: Change Log 

$Source: /cvs/main/flatfile/html/rb/js/global/cnet.global.framework/common/js.widgets/simple.editor.js,v $
$Log: simple.editor.js,v $
Revision 1.1  2007/06/02 01:35:46  newtona
*** empty log message ***


*/