
/*
Script: dbug.js
Wrapper for the firebug console.log() function.

Dependancies:
	 no dependencies
	
Author:
	Aaron Newton, <aaron [dot] newton [at] cnet [dot] com>

Class: dbug
		dbug is a wrapper for the firebug console plugin for
		firefox. The syntax for logging is the same as documented
		at http://getfirebug.com, though only the .log() command
		is supported.
		
		You can leave dbug.log() statements in your code and 
		they will not be echoed out to the screen in any way. 

		To display the dbug statements, you have two options:
		include *"jsdebug=true"* in the query string of the page
		and all your dbug statements will be printed as they
		occur OR *type into the firebug console dbug.enable()*
		and the debug statements that have occurred up until
		that point will be echoed, and all others from that
		point will be printed as they occur. You can also
		*put dbug.enable() in your javascript* to turn it on.
		
		dbug.disable() will turn it back off.

Arguments:
	args - collection of things to log to the console.
	
Examples:
	(start code)
	dbug.log("message");
	> message
	dbug.log("my var is %s", myVar)
	> my var is x
	dbug.log($('myelement'));
	> <div id="myelement"></div>
	dbug.log("myelement: %s, some value: %s", $('myelement'), somevalue);
	> myelement: <div id="myelement"></div>, some value: blah
	(end)
	
	more at <http://getfirebug.com>
	*/
var dbug = {
/*	Property: logged

		Array with any messages logged that have not been sent to the console; 
		happens when dbug is not enabled. when you enable it again,
		these messages will be dumped to the console.
	*/
	logged: [],	
	timers: {},
/*	property: debug
		boolean; whether or not the debugger is enabled.
	*/	
	firebug: false, 
	debug: false, 

/*	property: log

		sends a message to the console if dbug is enabled, otherwise
		it stores this info until dbug is enabled.
		
		Parameters:
			message - the message to log, includes various substition options, see <http://www.getFirebug.com>

		Syntax: 
		> dbug.log("message");
		> > message
		> dbug.log("my var is %s", myVar)
		> > my var is x

		for more examples, see <http://www.getFirebug.com>
	*/
	log: function() {
		dbug.logged.push(arguments);
	},
	nolog: function(msg) {
		dbug.logged.push(arguments);
	},
/*	Property: time
		Starts a console timer with the given name if dbug is enabled.
		See <http://www.getFirebug.com> for details.
	*/
	time: function(name){
		dbug.timers[name] = new Date().getTime();
	},
/*	Property: timeEnd
		Ends a console timer with the given name if dbug is enabled.
		See <http://www.getFirebug.com> for details.
	*/
	timeEnd: function(name){
		if (dbug.timers[name]) {
			var end = new Date().getTime() - dbug.timers[name];
			dbug.timers[name] = false;
			dbug.log('%s: %s', name, end);
		} else dbug.log('no such timer: %s', name);
	},
/*	Property: enable

		turns on the dbug functionality so that messages will show up
		in the firebug console. any messages sent to dbug.log() 
		previously will be displayed in the console immediately and
		all future logging statements will echo to the console.

		See also: 
		<dbug.log>, <dbug.disable>
		
		Example:
		>dbug.enable()
		> > enabling dbug
		
	*/	
	enable: function() { 
		if(dbug.firebug) {
			try {
				dbug.debug = true;
				dbug.log = console.debug || console.log;
				dbug.time = console.time;
				dbug.timeEnd = console.timeEnd;
				dbug.log('enabling dbug');
				for(var i=0;i<dbug.logged.length;i++){ dbug.log.apply(console, dbug.logged[i]); }
				dbug.logged=[];
			} catch(e) {
				dbug.enable.delay(400);
			}
		}
	},
/*	Property: disable

		turns the dbug functionality off. all future logging calls
		will be stored in the logged array until dbug is enabled again.
		
		See also: 
		<dbug.log>, <dbug.enable>, <dbug.logged>
		
		Example:
		>dbug.disable()
	*/
	disable: function(){ 
		if(dbug.firebug) dbug.debug = false;
		dbug.log = dbug.nolog;
		dbug.time = function(){};
		dbug.timeEnd = function(){};
	},
/*	Property: cookie
		dbug.cookie turns debugging on for the rest of the day for that domain. This lets you click around and use the debugging version of libraries without having to add jsdebug=true to each new page's url and reload the page.
	*/
	cookie: function(){
		dbug.enable();
		dbug.log('setting debugging cookie');
		var date = new Date();
		date.setTime(date.getTime()+(24*60*60*1000));
		document.cookie = 'jsdebug=true;expires='+date.toGMTString();
	},
/*	Property: disableCookie
		This removes the cookie set by <dbug.cookie> and turns off debugging for subsequent page loads.
	*/
	disableCookie: function(){
		dbug.log('disabling debugging cookie');
		document.cookie = 'jsdebug=false';
	}
};

if (typeof console != "undefined" && console.warn){
	dbug.firebug = true; 
	var value = document.cookie.match('(?:^|;)\\s*jsdebug=([^;]*)');
	var debugCookie = value ? unescape(value[1]) : false;
	if(window.location.href.indexOf("jsdebug=true")>0 || debugCookie=='true') dbug.enable();
	if(debugCookie=='true')dbug.log('debugging cookie enabled');
	if(window.location.href.indexOf("jsdebugCookie=true")>0){
		dbug.cookie();
		if(!dbug.debug)dbug.enable();
	}
	if(window.location.href.indexOf("jsdebugCookie=false")>0)dbug.disableCookie();
}
/* do not edit below this line */   
/* Section: Change Log 

$Source: /cvs/main/flatfile/html/rb/js/global/cnet.global.framework/common/utilities/dbug.js,v $
$Log: dbug.js,v $
Revision 1.7  2007/03/09 23:32:03  newtona
docs update

Revision 1.6  2007/03/08 23:31:22  newtona
strict javascript warnings cleaned up
removed deprecated dbug loadtimers
dbug enables on debug.cookie()

Revision 1.5  2007/02/21 00:30:08  newtona
added loadTime & loadTimeEnd empty functions for legacy support; these should be removed after the next release.

Revision 1.4  2007/02/08 19:18:34  newtona
dbug now uses cookies

Revision 1.3  2007/02/03 01:39:38  newtona
fixed an IE bug

Revision 1.2  2007/01/23 00:12:23  newtona
tweaks to work with Debugger.js

Revision 1.1  2007/01/09 02:39:35  newtona
renamed addons directory to "common" directory

Revision 1.6  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.5  2006/12/06 17:52:50  newtona
making this file have no dependencies

Revision 1.4  2006/11/22 00:21:01  newtona
docs update

Revision 1.3  2006/11/21 23:56:08  newtona
added dbug.time and debug.timeEnd

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: function.cnet.js
		Extends functionality in Mootools <Function.js>
		
		Dependencies:
		mootools - <Moo.js>,  <Utility.js>
		
		Author:
		Aaron Newton - aaron [dot] newton [at] cnet [dot] com

		Function: $type
		Extends the <$type> function in <Function.js>
		
		Property: isNumber
		Determines if a value is a number. If the value is a string, if the string will parse
		to a number, returns true.
		
		Arguments:
		val - the object to asses.
		
		Example:
		>$type.isNumber(myValue) //if it's a number, returns true
	*/
		$type.isNumber = function(val) {
			if(isNaN(val))return false;
			if((typeof val != "undefined" && typeof val == "number") ||
			(typeof val != "boolean" && (typeof val != "string" || val.length >0) && isFinite(new Number(val)))) return true;
			return false;
		};
		
/* do not edit below this line */   
/* Section: Change Log 

$Source: /cvs/main/flatfile/html/rb/js/global/cnet.global.framework/mootools.extended/function.cnet.js,v $
$Log: function.cnet.js,v $
Revision 1.12  2007/03/01 00:50:35  newtona
type.isNumber now returns false for NaN
element.smoothshow/hide now works (in IE specifically) when there are no values for border

Revision 1.11  2007/02/07 20:52:46  newtona
removed $copy ( it's in mootools now)

Revision 1.10  2007/02/03 01:40:36  newtona
fixed typo (Object.copy should be $copy)

Revision 1.9  2007/01/29 23:51:57  newtona
added $copy until it gets into the mootools release

Revision 1.8  2007/01/26 06:06:47  newtona
removed everything except $type.isNumber; everything else is now in mootools

Revision 1.7  2007/01/22 22:06:40  newtona
removed $set/$pick, it's in mootools version 1.0 now

Revision 1.6  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.5  2006/12/04 18:36:52  newtona
syntax error in the docs

Revision 1.4  2006/11/15 01:18:45  newtona
updated docs

Revision 1.3  2006/11/13 22:56:01  newtona
added function $set

Revision 1.2  2006/11/02 21:34:00  newtona
Added cvs footer


*/
/*	Script: string.cnet.js
		These are mootools authored extensions designed to allow prototype.lite libraries run in this environment.

Dependancies:
	 mootools - <Moo.js>, <String.js>, <Array.js>

Author:
	Aaron Newton, <aaron [dot] newton [at] cnet [dot] com>
	

		Class: String
		This extends the <String> prototype.
	*/
String.extend({
/*	Property: stripTags
		Remove all html tags from a string.	*/
	stripTags: function() {
		return this.replace(/<\/?[^>]+>/gi, '');
  },
/*	Property: stripScripts
		Removes all script tags from an HTML string.
	*/
	stripScripts: function() {
		return this.replace(/<script[^>]*?>.*?<\/script>/img, '');
	},
/*	Property: evalScripts
		Executes scripts included in an HTML string.
	*/
	evalScripts: function() {
		var scripts = this.match(/<script[^>]*?>.*?<\/script>/g);
		if(scripts) scripts.each(function(script){
				eval(script.replace(/^<script[^>]*?>/, '').replace(/<\/script>$/, ''));
			});
	},
/*	Property: replaceAll
		Replaces all instances of a string with the specified value.
		
		Arguments:
		searchValue - the string you want to replace
		replaceValue - the string you want to insert in the searchValue's place
		regExOptions - defaults to "ig" but you can pass in your preference
		
		Example:
		>"I like cheese".replaceAll("cheese", "cookies");
		> > I like cookies
	*/
	replaceAll: function(searchValue, replaceValue, regExOptions) {
		return this.replace(new RegExp(searchValue, $pick(regExOptions,'gi')), replaceValue);
	},
/*	Property: urlEncode
		urlEncodes a string (if it is not already).
		
		Example:
		> "Mondays aren't that fun".urlEncode()
		> > Mondays%20aren%27t%20that%20fun
	*/
	urlEncode: function() {
		if (this.indexOf('%') > -1) return this;
		else return escape(this);
	},
/*	Property: parseQuery
		Turns a query string into an associative array of key/value pairs.
		
		Example:
(start code)
"this=that&what=something".parseQuery()
> { this: "that", what: "something" }

var values = "this=that&what=something".parseQuery();
> values.this > "that"
(end)
	*/
	parseQuery: function() {
		var pairs = this.match(/^\??(.*)$/)[1].split('&');
		var params = {};
		pairs.each(function(pair) {
		  pair = pair.split('=');
		  params[pair[0]] = pair[1];
		});
		return params;
	},
/*	Property: tidy
		Replaces common special characters with their ASCII counterparts (smart quotes, elipse characters, stuff from MS Word, etc.).
	*/
	tidy: function() {
		var txt = this.toString();
		$each({
			"[\xa0\u2002\u2003\u2009]": " ",
			"\xb7": "*",
			"[\u2018\u2019]": "'",
			"[\u201c\u201d]": '"',
			"\u2026": "...",
			"\u2013": "-",
			"\u2014": "--"
		}, function(value, key){
			txt = txt.replace(new RegExp(key, 'g'), value);
		});
		return txt;
	}
});
/* do not edit below this line */   
/* Section: Change Log 

$Source: /cvs/main/flatfile/html/rb/js/global/cnet.global.framework/mootools.extended/string.cnet.js,v $
$Log: string.cnet.js,v $
Revision 1.8  2007/03/16 00:23:24  newtona
added string.tidy and element.tidy

Revision 1.7  2007/03/09 20:14:47  newtona
strict javascript warnings cleaned up

Revision 1.6  2007/03/08 23:32:14  newtona
strict javascript warnings cleaned up

Revision 1.5  2007/02/06 18:14:01  newtona
re-implemented replaceAll because String.replace(new, old, "ig") doesn't work in IE. Ungh. IE.

Revision 1.4  2007/01/26 06:08:27  newtona
updated docs
refactored .replaceAll
removed dependency on Prototype.compatibility.js

Revision 1.3  2006/11/15 01:19:19  newtona
added String.parseQuery

Revision 1.2  2006/11/02 21:34:00  newtona
Added cvs footer


*/
/*	Script: window.cnet.js
Extends the Mootools <Window> class.

Class: window
		This extends the <window> class from the <http://mootools.net> library.
		
Dependencies:
 mootools = <Moo.js>, <Utility.js>, <Common.js>, <Window.Base.js>
 cnet - <string.cnet.js>
	*/
window.extend({
/*	property: isLoaded (deprecated)
		Use <window.loaded>; true if the dom is ready.
	*/
	isLoaded: window.loaded,
/*	Property: getHost	
		Returns the domain of the window or the passed in url.
		
		Arguments:
		url (optional) - the url you wish to get the host for (otherwise window.getHost
							returns the host of the current window location).
*/
	getHost:function(url){
		url = $pick(url, window.location.href);
		var host = url;
		if(url.test('http://')){
			url = url.substring(url.indexOf('http://')+7,url.length);
			if(url.test(':')) url = url.substring(0, url.indexOf(":"));
			if(url.test('/')) return url.substring(0,url.indexOf('/'));
			return url;
		}
		return false;
	},
/*	Property: getQueryStringValue
		Returns a specific query string value from the window location.
		
		Arguments:
		key - the key to search for in the query string
		url - (optional) url with a query string to parse (defaults to window.location)
		
		Example:
(start code)
//window.location is http://www.example.com/?red=apple&yellow=lemon
var something = window.getQueryStringValue("red");
> something = "apple"
(end)
	*/
	getQueryStringValue: function(key, url) {
		try { 
			return window.getQueryStringValues(url)[key];
		}catch(e){return null;}
	},

/* Property: getQueryStringValues 
		An object with name/value pairs of the values in the query string of the window.
		
		Arguments:
		url - (optional) url with a query string to parse (defaults to window.location)
		
		Example:
		If you were on the page http://www.example.com?red=apple&yellow=lemon
		
		then window.getQueryStringValues() would return:
(start code)
{
	red: 'apple',
	yellow: 'lemon'
}
(end)
*/
		getQueryStringValues: function(url){
			var qs = $pick(url, $pick(window.location.search, '')); //get the query string
			if(qs == "") return []; //if there isn't one, return null
			if(qs.indexOf("?") >= 0)qs = qs.substring(qs.indexOf("?")+1, qs.length); //remove the question mark
			return qs.parseQuery();
		},

	
/*	Property: getPort
		Returns the port number of the window location.
		
		Arguments
		url - (optional) the url to test for a port; defaults to the window location.
		
		Example:
		(start code)
//window.location.href is http://www.example.com:8001/blah.html
window.getPort()
> 8001
		(end)
	*/
	getPort: function(url) {
		url = $pick(url, window.location.href);
		var re = new RegExp(':([0-9]{4})');
		var m = re.exec(url);
	  if (m == null) return false;
	  else {
			var port = false;
			m.each(function(val){
				if($type.isNumber(val)) port = val;
			});
	  }
		return port;
	},
/*	Property:	qs
		An object with name/value pairs of the values in the query string of the window.
		
		Example:
		If you were on the page http://www.example.com?red=apple&yellow=lemon
		
		then window.qs would be:
(start code)
{
	red: 'apple',
	yellow: 'lemon'
}
(end)
	*/
	qs: {}
});
window.qs = window.getQueryStringValues();

/*	Class: window.popup
This class opens a popup window with the passed in values.
		
Arguments
	url - the destination for the popup
	options - an object containing key/value options
	
Options:
	width - (integer) the width of the window; defaults to 500
	height - (integer) the height of the window; defaults to 300
	x - (integer) the offest from the left of the screen; defaults to 50
	y - (integer) the offset from the top of the screen; defaults to 50
	toolbar - (integer) show the browser toolbar in the window; 
			0 (zero) does not show it, 1 (one) does; defaults to 0 (zero)
	location - (integer) show the location in the browser;
			0 does not show it; defautls to 0
	directories - (integer) show the directories in the browser;
			0 does not show it; defautls to 0
	status - (integer) show the status bar in teh browser;
			0 does not show it; defautls to 0
	scrollbars - (string) 'auto' shows the scroll bars if they are required,
			'no' shows none, 'yes' shows them all the time
	resizeable - (integer) lets the user resize the window;
			1 allows resizeing; defaults to 1
	name - (string) the name of the popup; defaults to "popup"
	
	Examples:
	(start code)
var myPopup = new window.popup('http://www.example.com'); //opens with default parameters

var myPopup = new window.popup('http://www.example.com', {
	width: 300,
	height: 800,
	x: 500,
	toolbar: 1
}); //launch a window with custom properties
	(end)

	Property: popupWindow
	The window object itself (the popup). The class window.popup opens a new browser window. The pointer to this
	window can be reached like so:
	(start code)
	var myPopup = new window.popup('http://www.example.com');
	myPopup.popupWindow // this is the reference to the popup itself.
	(end)
	
	Note that if you call this class with the same name (the default name is 'popup') as an already open window
	you won't open a new popup window, but instead will send your url to the existing window. You should probably
	give it something unique so you can have more than one if you need. 

	Example:
	(start code)
	var myPopup = new window.popup('http://www.example.com'); //default name for the popup is "popup"
	var anotherPopup = new window.popup('http://www.example2.com'); //you just refreshed the "popup" window with this new url
	(end)
	
	This actually represents a way to keep refering to the same window that's already open. So long as the window
	calling it is the same window that opened the popup to begin with (even if the user goes to another page), the
	above code will always re-acquire the already open popup.
	
	Example:
	(start code)
	//page loads
	var myPopup = new window.popup('http://www.example.com'); //default name for the popup is "popup"
	
	//user goes to another page, and, when that page loads, this happens again
	var myPopup = new window.popup('http://www.example.com'); //default name for the popup is "popup"
	(end)
	
	The result is you just refreshed the already open window with the same url. There are ways to do this
	without refreshing, but not with this class (yet).
	*/
window.popup = new Class({
	options: {
			width: 500,
			height: 300,
			x: 50,
			y: 50,
			toolbar: 0,
			location: 0,
			directories: 0,
			status: 0,
			scrollbars: 'auto',
			resizeable: 1,
			name: 'popup',
			onBlock: Class.empty
	},
	initialize: function(url, options){
		this.url = url || false;
		this.setOptions(options);
		if(this.url) this.openWin();
		return this;
	},
	openWin: function(url){
		url = url || this.url;
		this.popupWindow = window.open(url,
			this.options.name,
			'toolbar="'+this.options.toolbar+
			'",location="'+this.options.location+
			'",directories="'+this.options.directories+
			'",status="'+this.options.status+
			'",scrollbars="'+this.options.scrollbars+
			'",resizable="'+this.options.resizeable+
			',width='+this.options.width+
			',height='+this.options.height+
			',top='+this.options.y+
			',left='+this.options.x);
		this.focus.delay(100, this);
		return this.popupWindow;
	},
/*	Property: focus
		Focus the window related to the window.popup object.
		
		Example:
		(start code)
var myPopup = new window.popup('http://www.example.com'); //opens with default parameters
myPopup.focus(); //bring it to the front
		(end)
		
		Note:
		When you create a new popup it calls .focus() on itself immediately by default.
	*/
	focus: function(){
		if (this.popupWindow) this.popupWindow.focus();
		else if (this.focusTries<10) this.focus.delay(100, this); //try again
		else {
			this.blocked = true;
			this.fireEvent('onBlock');
		}
		return this;
	},
	focusTries: 0,
	blocked: null,
/*	Property: close
		Closes the popup window related to the window.popup object.

		Example:
		(start code)
var myPopup = new window.popup('http://www.example.com'); //opens with default parameters
myPopup.close(); //close the window
		(end)

	*/
	close: function(){
		this.popupWindow.close();
	}
});
window.popup.implement(new Options);
window.popup.implement(new Events);

/*	Class: legacyPopup
		A legacy instance of <window.popup> that defaults to a specific width and height; not intended for use.	*/
var legacyPopup = window.popup.extend({
	setOptions: function(){
		this.parent();
		this.options = Object.extend({
			width: 516, 
			height: 350
		}, this.options);
	}
});

/*	Function: openPop
		An instance of <legacyPopup>; not intended for actual use.
	*/
function openPop(url){
	return new legacyPopup(url);
}

/*	Function: GetValue
		Legacy syntax for window.getQueryStringValue; deprecated.
	*/
var GetValue = window.getQueryStringValue;
	
/* do not edit below this line */   
/* Section: Change Log 

$Source: /cvs/main/flatfile/html/rb/js/global/cnet.global.framework/mootools.extended/window.cnet.js,v $
$Log: window.cnet.js,v $
Revision 1.17  2007/03/10 01:24:47  newtona
getQueryString returns an empty array instead of null

Revision 1.16  2007/03/09 20:14:47  newtona
strict javascript warnings cleaned up

Revision 1.15  2007/02/21 00:22:21  newtona
fixed some syntax problems
implemented Events

Revision 1.14  2007/02/08 01:30:37  newtona
renamed the popup window in the class window.popup "popupWindow" instead of "window" - a reserved name in IE

Revision 1.13  2007/01/26 06:13:34  newtona
now .isLoaded = .loaded
syntax update for mootools 1.0
getHost takes a url now (defaults to window.location)
added getQueryStringValues
added .qs - an object of the window query string values

Revision 1.12  2007/01/22 22:02:02  newtona
removed ie background cache fixed; it's in mootools 1.0

Revision 1.11  2007/01/19 01:23:09  newtona
fixed a bug in window.getHost

Revision 1.10  2007/01/09 01:29:24  newtona
added returns of the Window.popup class when calling functions on Window.popups

Revision 1.9  2006/11/27 19:34:32  newtona
changed the line about firefox bugs; this comment was misleading. no functional changes.

Revision 1.8  2006/11/26 00:28:19  newtona
forgot about ports in getHost... fixed

Revision 1.7  2006/11/26 00:26:35  newtona
ok. actually *fixed* the bug with getHost

Revision 1.6  2006/11/26 00:16:48  newtona
fixed conditional bug in Window.getHost

Revision 1.5  2006/11/16 18:50:40  newtona
fixed a syntax error in getQueryStringValue

Revision 1.4  2006/11/15 01:19:49  newtona
added Window.getQueryStringValue

Revision 1.3  2006/11/04 00:54:35  newtona
added Widnow.getPort()

Revision 1.2  2006/11/02 21:34:00  newtona
Added cvs footer


*/
/*	Script: element.cnet.js
Extends the <Element> object.

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: Element
		This extends the <Element> prototype.
	*/
Element.extend({
/*	Property: getDimensions
		Returns width and height for element; if element is not visible the element is
		cloned off screen, shown, measured, and then removed.
		
		Arguments:
		options - a key/value set of options
		
		Options:
		computeSize - (boolean; optional) use <Element.getComputedSize> or not; defaults to false
		styles - (array; optional) see <Element.getComputedSize>
		plains - (array; optional) see <Element.getComputedSize>
		
		Returns:
		An object with .width and .height defined as integers. If options.computeSize is true, returns
		all the values that <Element.getComputedSize> returns.
		
		Example:
		>$(id).getDimensions()
		> > {width: #, height: #}
	*/
	getDimensions: function(options) {
		options = $merge({computeSize: false},options);
		var dim = {};
		function getSize(el, options){
			if(options.computeSize) dim = el.getComputedSize(options);
			else {
				dim.width = el.getSize().size.x;
				dim.height = el.getSize().size.y;
			}
			return dim;
		}
		try { //safari sometimes crashes here, so catch it
			dim = getSize(this, options);
		}catch(e){}
		if((dim.x == 0 || $type(dim.x) != 'number')||(dim.y == 0 || $type(dim.y) != 'number')){
			var holder = new Element('div').setStyles({
				'position':'absolute',
				'top':'-1000px',
				'left':'-1000px',
				'display':'block'
			}).injectAfter(this);
			var clone = this.clone().injectInside(holder).show();
			dim = getSize(clone, options);
			holder.remove();
		}
		return $merge(dim, {x: dim.width, y: dim.height});
	},
/*	Property: getComputedSize
		Calculates the size of an element including the width, border, padding, etc.
		
		Arguments:
		options - an object with key/value options
		
		Options:
		styles - (array) the styles to include in the calculation; defaults to ['padding','border']	
		plains - (object) an object with height and width properties, each of which is an 
							array including the edges to include in that plain. 
							defaults to {height: ['top','bottom'], width: ['left','right']}
		mode - (string; optional) limit the plain to 'vertical' or 'horizontal'; defaults to 'both'
		
		Returns:
		size - an object that contans dimension values (integers); see list below
		
		
		Dimension Values Returned:
		width - the actual width of the object (not including borders or padding)
		height - the actual height of the object (not including borders or padding)
		border-*-width - (where * is top, right, bottom, and left) the width of the border on that edge
		padding-* - (where * is top, right, bottom, and left) the width of the padding on that edge
		computed* - (where * is Top, Right, Bottom, and Left; e.g. computedRight) the width of all the 
			styles on that edge computed (so if options.styles is left to the default padding and border,
			computedRight is the sum of border-right-width and padding-right)
		totalHeight - the total sum of the height plus all the computed styles on the top or bottom. by
			default this is just padding and border, but if you were to specify in the styles option
			margin, for instance, the totalHeight calculated would include the margin.
		totalWidth - same as totalHeight, only using width, left, and right
	*/
	getComputedSize: function(options){
		options = $merge({
			styles: ['padding','border'],
			plains: {height: ['top','bottom'], width: ['left','right']},
			mode: 'both'
		}, options);
		var size = {width: 0,height: 0};
		switch (options.mode){
			case 'vertical':
				delete size.width;
				delete options.plains.width;
				break;
			case 'horizontal':
				delete size.height;
				delete options.plains.height;
				break;
		}
		var getStyles = [];
		//this function might be useful in other places; perhaps it should be outside this function?
		$each(options.plains, function(plain, key){
			plain.each(function(edge){
				options.styles.each(function(style){
					getStyles.push((style=="border")?style+'-'+edge+'-'+'width':style+'-'+edge);
				});
			});
		});
		var styles = this.getStyles.apply(this, getStyles);
		var subtracted = [];
		$each(options.plains, function(plain, key){ //keys: width, height, plains: ['left','right'], ['top','bottom']
			size['total'+key.capitalize()] = 0;
			size['computed'+key.capitalize()] = 0;
			plain.each(function(edge){ //top, left, right, bottom
				size['computed'+edge.capitalize()] = 0;
				getStyles.each(function(style,i){ //padding, border, etc.
					//'padding-left'.test('left') size['totalWidth'] = size['width']+[padding-left]
					if(style.test(edge)) {
						styles[style] = styles[style].toInt(); //styles['padding-left'] = 5;
						if(isNaN(styles[style]))styles[style]=0;
						size['total'+key.capitalize()] = size['total'+key.capitalize()]+styles[style];
						size['computed'+edge.capitalize()] = size['computed'+edge.capitalize()]+styles[style];
					}
					//if width != width (so, padding-left, for instance), then subtract that from the total
					if(style.test(edge) && key!=style && 
						(style.test('border') || style.test('padding')) && !subtracted.test(style)) {
						subtracted.push(style);
						size['computed'+key.capitalize()] = size['computed'+key.capitalize()]-styles[style];
					}
				});
			});
		});
		if($chk(size.width)) {
			size.width = size.width+this.offsetWidth+size.computedWidth;
			size.totalWidth = size.width + size.totalWidth;
			delete size.computedWidth;
		}
		if($chk(size.height)) {
			size.height = size.height+this.offsetHeight+size.computedHeight;
			size.totalHeight = size.height + size.totalHeight;
			delete size.computedHeight;
		}
		return $merge(styles, size);
	},
/*	Property: setPosition
		Sets the location of an element relative to another (defaults to the document body).
		
		Note:
		The element must be absolutely positioned (if it isn't, this method will set it to be);
		
		Arguments:
		options - a key/value object with options
		
		Options:
		relativeTo - (element) the element relative to which to position this one; defaults to document.body.
		position - (string) the aspect of the relativeTo element that this element should be positioned. Options are 'upperRight', 'upperLeft', 'bottomLeft', 'bottomRight', and 'center' (the default). With the exception of center, all other options will make the upper right corner of the positioned element = the specified corner of the relativeTo element. 'center' will make the center point of the positioned element = the center point of the relativeTo element.
		egde - (string; optional) the edge of the element to set relative to the relative elements corner; this way you can specify to position this element's upper right corner to the bottom left corner of the relative element. this is optional; the default behavior positions the element's upper left corner to the relative element unless position == center, in which case it positions the center of the element to the center of the relative element.
		offset - (object) x/y coordinates for the offset (i.e. {x: 10, y:100} will move it down 100 and to the right 10). Negative values are allowed.
		smoothMove - (boolean) move the element to the new position using <Fx.Styles>; defaults to false.
		effectOptions - (object) options object for <Fx.Styles>, optional
		returnPos - (boolean) don't move the element, but instead just return the position object ({top: '#', left: '#'}); defaults to false
		
		Example:
(start code)
$(el).getComputedSize();
returns:
{
	padding-top:0,
	border-top-width:1,
	padding-bottom:0,
	border-bottom-width:1,
	padding-left:0,
	border-left-width:1,
	padding-right:0,
	border-right-width:1,
	width:100,
	height:100,
	totalHeight:102,
	computedTop:1,
	computedBottom:1,
	totalWidth:102,
	computedLeft:1,
	computedRight:1
}
(end)		
	*/
	setPosition: function(options){
		options = $merge({
			relativeTo: document.body,
			position: 'center',
			edge: false,
			offset: {x:0,y:0},
			smoothMove: false,
			effectOptions: {},
			returnPos: false
		}, options);
		this.setStyle('position', 'absolute');
		var rel = $(options.relativeTo) || document.body;
		var top = (rel == document.body)?window.getScrollTop():rel.getTop();
		if (top < 0) top = 0;
		var left = (rel == document.body)?window.getScrollLeft():rel.getLeft();
		if (left < 0) left = 0;
		var dim = this.getDimensions({computeSize: true});
		var pos;
		var prefY = options.offset.y.toInt();
		var prefX = options.offset.x.toInt();
		switch(options.position) {
			case 'upperLeft':
				pos = {
					x:(left + prefX),
					y:(top + prefY)
				};
				break;
			case 'upperRight':
				pos = {
					x:(left + prefX + rel.offsetWidth),
					y:(top + prefY)
				};
				break;
			case 'bottomLeft':
				pos = {
					x:(left + prefX),
					y:(top + prefY + rel.offsetHeight)
				};
				break;
			case 'bottomRight':
				pos = {
					y:(left + prefX + rel.offsetWidth),
					x:(top + prefY + rel.offsetHeight)
				};
				break;
			default: //center
				pos = {
					x: left + (((rel == document.body)?window.getWidth():rel.offsetWidth)/2) + prefX,
					y: top + (((rel == document.body)?window.getHeight():rel.offsetHeight)/2) + prefY
				};
				options.edge = "center";
				break;
		}
		if(options.edge){
			var edgeOffset;
			switch(options.edge){
				case 'upperLeft':
					edgeOffset = {
						x: 0,
						y: 0
					};
					break;
				case 'upperRight':
					edgeOffset = {
						x: -dim.x-dim.computedRight-dim.computedLeft,
						y: 0
					};
					break;
				case 'bottomLeft':
					edgeOffset = {
						x: 0,
						y: -dim.y-dim.computedTop-dim.computedBottom
					};
					break;
				case 'bottomRight':
					edgeOffset = {
						x: -dim.x-dim.computedRight-dim.computedLeft,
						y: -dim.y-dim.computedTop-dim.computedBottom
					};
					break;
				default: //center
					edgeOffset = {
						x: -(dim.x/2),
						y: -(dim.y/2)
					};
					break;
			}
			pos.x = pos.x+edgeOffset.x;
			pos.y = pos.y+edgeOffset.y;
		}
		pos = {
			left: ((pos.x >= 0)?pos.x:0).toInt()+'px',
			top: ((pos.y >= 0)?pos.y:0).toInt()+'px'
		};
		if(options.returnPos) return pos;
		if(options.smoothMove && this.effects) this.effects(options.effectOptions).start(pos);
		else this.setStyles(pos);
		return this;
	},

/*	Property: visible
		Returns a boolean; true = visible, false = not visible.
		
		Example:
		>$(id).visible()
		> > true | false	*/
	visible: function() {
		return this.getStyle('display') != 'none';
	},
/*	Property: toggle
		Toggles the state of an element from hidden (display = none) to 
		visible (display = what it was previously or else display = block)
		
		Example:
		> $(id).toggle()
	*/
	toggle: function() {
		return this[this.visible() ? 'hide' : 'show']();
	},
/*	Property: hide
		Hides an element (display = none)
		
		Example:
		> $(id).hide()
		*/
	hide: function() {
		this.originalDisplay = this.getStyle('display'); 
		this.setStyle('display','none');
		return this;
	},
/*	Property: smoothHide
		Transitions the height, opacity, padding, and margin (but not border) from their current height to zero, then set's display to none and resets the height, opacity, etc. back to their original values.

		Arguments:
		options - a key/value object of options
		
		Options:
		all the options passed along to <Fx.Base> (transition, duration, etc.); (optional); PLUS
		styles - (array; optional) css properties to transition in addition to width/height; 
							defaults to ['padding','border','margin']
		mode - (string; optional) 'vertical','horizontal', or 'both' to describe how the element should slide in.
							defaults to 'vertical'
	*/
	smoothHide: function(options){
		options = $merge({
			styles: ['padding','border','margin'],
			mode:'vertical'
		},options);
		if(this.getStyle('display') != 'none'){
			var startStyles = this.getComputedSize({
				styles: options.styles,
				mode: options.mode
			});
			startStyles.opacity = 1;
			var zero = {};
			$each(startStyles, function(style, name){
				zero[name] = (name.test('width')||name.test('height'))?'0px':0; 
				startStyles[name] = (name.test('width')||name.test('height'))?style+'px':style; 
			});
			this.effects(options).start(zero).chain(function(){
				this.setStyles(startStyles).setStyle('display','none');
			}.bind(this));
		}
		return this;
	},
/*	Property: smoothShow
		Sets the display of the element to opacity: 0 and display: block, then transitions the height, opacity, padding, and margin (but not border) from zero to their proper height.
		
		Arguments:
		all the options passed along to <Fx.Base> (transition, duration, etc.); (optional); PLUS
		mode - (string; optional) 'vertical','horizontal', or 'both' to describe how the element should slide in.
		heightOverride - (integer; optional) height to open to; overrides the default offsetheight
		widthOverride -  (integer; optional) width to open to; overrides the default offsetwidth
	*/
	smoothShow: function(options){
		if(arguments[1]) options.heightOverride = arguments[1];
		options = $merge({
			styles: ['padding','border','margin'],
			mode: 'vertical'
		}, options);
		if(this.getStyle('display') == "none" || 
			 this.getStyle('visiblity') == "hidden" || 
			 this.getStyle('opacity')==0){
			//toggle display, but hide it
			this.setStyles({ 
				display:'block',
				opacity:0 
			});
			var startStyles = this.getComputedSize({
				styles: options.styles,
				mode: options.mode
			});
			startStyles.opacity = 1;
			var zero = { height: '0px', opacity: 0 };
			$each(startStyles, function(style, name){ zero[name] = 0; });
			this.setStyles(zero).effects(options).start(startStyles);
		}
		return this;
	},
/*	Property: show
		Shows an element (display = what it was previously or else display = block)
		
		Example:
		>$(id).show() */
	show: function(display) {
		this.originalDisplay = (this.originalDisplay=="none")?'block':this.originalDisplay;
		this.setStyle('display',(display || this.originalDisplay || 'block'));
		return this;
	},
/*	Property: cleanWhitespace
		Removes all empty text nodes from an element and its children
		
		Example:
		> $(id).cleanWhitespace()	*/
	cleanWhitespace: function() {
		$A(this.childNodes).each(function(node){
			if (node.nodeType == 3 && !/\S/.test(node.nodeValue)) node.parentNode.removeChild(node);
		});
		return this;
	},
/*	Property: find
		Returns an element from the node's array (such as parentNode), deprecated (left over from Prototype.lite).
		
		Arguments:
		what - the value you wish to find (such as 'parentNode')

		Example:
		> $(id).find(parentNode)
	*/
	find: function(what) {
		var element = this[what];
		while (element.nodeType != 1) element = element[what];
		return element;
	},
/*	Property: replace
		Replaces an html element with the html passed in.
		
		Arguments:
		html - the html with which to replace the node.
		evalScripts - (boolean; optional) evaluate javascript in the new node. defaults to true.
		
		Example:
		>$(id).replace(myHTML) */
	replace: function(html, evalScripts) {
		if (this.outerHTML) {
			this.outerHTML = html.stripScripts();
		} else {
			var range = this.ownerDocument.createRange();
			range.selectNodeContents(this);
			this.parentNode.replaceChild(
				range.createContextualFragment(html.stripScripts()), this);
		}
		if($pick(evalScripts, true)) html.evalScripts.delay(10, html);
	},
/*	Property: empty
		Returns a boolean: true = the Node is empty, false, it isn't.
		
		Example:
		> $(id).empty
		> true (the node is empty) | false (the node is not empty)
	*/
	empty: function() {
		return !!this.innerHTML.match(/^\s*$/);
	},
	/*	Property: getOffsetHeight
			Returns the offset height of an element, deprecated.
			You should instead use <Element.getStyle>('height')
			or just Element.offsetHeight.
			
			Example:
			> $(id).getOffsetHeight()
		*/
	getOffsetHeight: function(){ return this.offsetWidth; },
	/*	Property: getOffsetWidth
			Returns the offset width of an element, deprecated.
			You should instead use <Element.getStyle>('width')
			or just Element.offsetWidth.
			
			Example:
			> $(id).getOffsetWidth()
		*/
	getOffsetWidth: function(){ return this.offsetWidth; },
/*	Property: tidy
		Uses <String.tidy> to clean up common special characters with their ASCII counterparts (smart quotes, elipse characters, stuff from MS Word, etc.).
	*/
	tidy: function(){
		try {	
			if(this.getValue().tidy())this.value = this.getValue().tidy();
		}catch(e){dbug.log('element.tidy error: %o', e);}
	}
});
/*	legacy support for $S	*/
var $S = $$;
/* do not edit below this line */   
/* Section: Change Log 

$Source: /cvs/main/flatfile/html/rb/js/global/cnet.global.framework/mootools.extended/element.cnet.js,v $
$Log: element.cnet.js,v $
Revision 1.18  2007/03/23 20:13:38  newtona
getDimensions: added support for getComputedSize
getComputedSize: added
setPosition: added edge option, uses getComputedSize
smoothHide: uses getComputedSize
smoothShow: uses getComputedSize
sumObj: removed function; no longer needed

Revision 1.17  2007/03/16 00:23:24  newtona
added string.tidy and element.tidy

Revision 1.16  2007/03/08 23:32:14  newtona
strict javascript warnings cleaned up

Revision 1.15  2007/03/01 00:50:35  newtona
type.isNumber now returns false for NaN
element.smoothshow/hide now works (in IE specifically) when there are no values for border

Revision 1.14  2007/02/27 19:37:56  newtona
element.show now enforces that the original display was not 'none'

Revision 1.13  2007/02/22 21:05:35  newtona
smoothHide now checks that the element is not already hidden

Revision 1.12  2007/02/21 00:21:22  newtona
added legacy support for $S

Revision 1.11  2007/02/08 22:14:04  newtona
added border widths to smoothshow/hide

Revision 1.10  2007/02/08 01:30:58  newtona
tweaking element.setPosition, now can use effects

Revision 1.9  2007/02/07 20:52:34  newtona
added Element.position

Revision 1.8  2007/02/06 18:13:13  newtona
added element.smoothShow and smoothHide; depends on latest svn of mootools

Revision 1.7  2007/02/03 01:40:05  newtona
fixed a typo bug

Revision 1.6  2007/01/26 06:06:13  newtona
element.replace now takes a 2nd argument to eval scripts or not
element.getDimensions now returns w & h for hidden elements

Revision 1.5  2007/01/05 19:45:48  newtona
made getDimensions capable of discovering dimensions of hidden elements

Revision 1.4  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.3  2006/11/27 17:59:32  newtona
small change to replace and the way it uses timeouts

Revision 1.2  2006/11/02 21:34:00  newtona
Added cvs footer


*/
/*	
	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>
			
	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
		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.
	*/
var FormValidator = new Class({
	initialize: function(form, options){
		this.setOptions({
			fieldSelectors:"input, select, textarea",
			useTitles:false,
			evaluateOnSubmit:true,
			evaluateFieldsOnBlur: true,
			onFormValidate: function(isValid, form){},
			onElementValidate: function(isValid, field){}
		}, 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);
		}
	},
	watchFields: function(){
		try{
			this.form.getElementsBySelector(this.options.fieldSelectors).each(function(el){
				el.addEvent('blur', this.validateField.pass(el, this));
			}, this);
		}catch(e){//console.log('error: %s', e);
		}
	},
	onSubmit: function(event){
		if(!this.validate()) new Event(event).stop();
	},
/*	Property: reset
		Removes all the error messages from the form.
	*/
	reset: function() {
		this.form.getElementsBySelector(this.options.fieldSelectors).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.form.getElementsBySelector(this.options.fieldSelectors).map(function(field) { return this.validateField(field); }, 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
	*/
	validateField: function(field){
		field = $(field);
		var result = true;
		if(field){
			var validators = field.className.split(" ").some(function(cn){
				return FormValidator.getValidator(cn);
			});
			result = field.className.split(" ").map(function(className){
				var test = this.test(className,field);
				return test;
			}, this);
			result = result.every(function(val){
				return val;
			});
			if(validators){
				if(result) field.addClass('validation-passed').removeClass('validation-failed');
				else field.addClass('validation-failed').removeClass('validation-passed');
			}
		}
		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
	*/
	test: function(className, field){
		field = $(field);
		var isValid = true;
		if(field) {
			var validator = FormValidator.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)){
					var advice = this.makeAdvice(className, field, validator.getError(field));
					this.insertAdvice(advice, field);
					this.showAdvice(className, field);
				} else this.hideAdvice(className, field);
				this.fireEvent('onElementValidate', [isValid, field]);
			}
		}
		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','block');
		}
	},
	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){
		var errorMsg = this.options.useTitles ? $pick(field.title, error):error;
		var advice = this.getAdvice(className, field);
		if(!advice){
			advice = new Element('div').addClass('validation-advice').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) {
				var prop = this.getPropName(className);
				if(field[prop]) this.hideAdvice(className, field);
				field.removeClass('validation-failed');
				field.removeClass('validation-passed');
			}, this);
		}
	}
});
FormValidator.implement(new Options);
FormValidator.implement(new Events);

/*	Section: FormValidator global functions
		These functions are available to the <FormValidator> object itself, not instances of it.
		Use these functions to add validators to the FormValidator object, which will be available
		to all instances of the FormValidator class.
	*/
Object.extend(FormValidator, {
/*	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)
		Example:
(start code)
FormValidator.add('isEmpty', {
	errorMsg: 'This field is required',
	test: function(element){
		if(element.value.length ==0) return false;
		else return true;
	}
});
	*/
	add : function(className, options) {
		this.validators[className] = new InputValidator(className, options);
	},
/*	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: ...}],
]);
	*/
	addAllThese : function(validators) {
		$A(validators).each(function(validator) {
			this.add(validator[0], validator[1]);
		}, this);
	},
	getValidator: function(className){
		return FormValidator.validators[className] = $pick(FormValidator.validators[className], false);
	}
});


/*	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.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 ***


*/
