/*
	Created by Tim Schottler - 04-15-2006.  Modified a lot since then.
	A lot of the stuff in here is modelled after other libraries, mostly prototype, or taken directly from it.  *Gives them some credit*
	Most of the form input validation regular expressions were taken from Macromedia's cfform.js file in /cfide/scripts/cfform.js, some were modified, some weren't.
	Do whatever you want with this.  Just remember that Tim Schottler is really cool.
*/

//////////////////// *LIBRARY ////////////////////
var Library = {
	ScriptFragment: '(?:<script.*?>)((\n|\r|.)*?)(?:<\/script>)'
}


//////////////////// *JS_EXTENSION ////////////////////
String.prototype.stripScripts = function(){
	return this.replace(new RegExp(Library.ScriptFragment, 'img'), '');
}
String.prototype.evalScripts = function(){
	var matchAll = new RegExp(Library.ScriptFragment, 'img');
	var matchOne = new RegExp(Library.ScriptFragment, 'im');
	var matches = this.match(matchAll);
	var results = [];
	try {
		for(var i=0; i<matches.length; i++){
			eval(matches[i].match(matchOne)[1]);
		}
	} catch(e){}
}
String.prototype.replaceAll = function(str1,str2){
	var str = this;
	while(str.indexOf(str1) != -1){
		str.replace(str1,str2);
	}
	return str;
}
Array.prototype.indexOf = function(obj){
	for(var i=0; i<this.length; i++)
		if(this[i] == obj) return i;
	return -1;
}


//////////////////// *CLASS ////////////////////
var Class = {
	create: function(){
		return function(){
			this.init.apply(this, arguments);
		}
	},
	extend: function(parent,child){
		for(var i in parent){
			if(!child[i]) child[i] = parent[i];
			else child[i+'_base'] = parent[i];
		}
		return child;
	}
}


//////////////////// *TRY ////////////////////
var Try = {
	these: function(){
		var r = false;
		for(var i=0; i<arguments.length; i++){
			try {
				r = arguments[i]();
				break;
			}catch(e){}
		}
		return r;
	}
}


//////////////////// *EVENT ////////////////////
// Mozilla has an Event object, ie is gay and does not.
if(!Event){
	var Event = {};
}
Event = Class.extend(Event,{
	observe: function(obj,e,func){
		if(obj.attachEvent){
			e = 'on'+e;
			obj.attachEvent(e,func);
		} else if(obj.addEventListener){
			obj.addEventListener(e,func,false);
		}
	},
	stop: function(e,dontPreventDefault){
		if(e.preventDefault){
			e.stopPropagation();
			if(!dontPreventDefault)
				e.preventDefault();
		} else {
			if(!dontPreventDefault)
				e.returnValue = false;
			e.cancelBubble = true;
		}
	}
});


//////////////////// *ELEM ////////////////////
var Elem = {
	create: function(tag,init){
		var elem = document.createElement(tag);
		if(init){
			for(var i in init){
				if(i == 'attributes'){
					for(var j in init[i]) elem.setAttribute(j,init[i][j]);
				} else {
					elem[i] = init[i];
				}
			}
		}
		return elem;
	},
	remove: function(elem){
		elem = $(elem);
		elem.parentNode.removeChild(elem);
	},
	get: function(tag,parent,matchObj){
		if(!tag) tag = '*';
		if(!parent) parent = document;
		else parent = $(parent);
		var elems = parent.getElementsByTagName(tag);
		var r = [];
		if(!matchObj) r = elems;
		for(var i=0; i<elems.length; i++){
			var elem = elems[i];
			for(var j in matchObj){
				if(j == 'attributes'){
					for(var k in matchObj[j]){
						if(elem.getAttribute(k) == matchObj[j][k]){
							r.push(elem);
							break;
						}
					}
				} else {
					if(j == 'className'){
						if(Elem.hasClassName(elem,matchObj[j])){
							r.push(elem);
							break;
						}
					} else if(elem[j] == matchObj[j]){
						r.push(elem);
						break;
					}
				}
			}
		}
		return r;
	},
	addClassName: function(elem,c){
		elem = $(elem);
		if(elem.className == undefined) return;
		if(!Elem.hasClassName(elem,c)) elem.className = elem.className + ' ' + c;
	},
	removeClassName: function(elem,c){
		elem = $(elem);
		if(elem.className == undefined) return;
		var cArr = elem.className.split(' ');
		for(var i=0; i<cArr.length; i++){
			if(cArr[i] == c){
				cArr.splice(i,1);
				break;
			}
		}
		elem.className = cArr.join(' ');
	},
	hasClassName: function(elem,c){
		elem = $(elem);
		if(!elem.className) return;
		var cArr = elem.className.split(' ');
		var has = false;
		for(var i=0; i<cArr.length; i++){
			if(cArr[i] == c){
				has = true;
				break;
			}
		}
		return has;
	},
	toggleClassName: function(elem,c){
		if($(elem).className == undefined) return;
		Elem[Elem.hasClassName(elem,c) ? 'removeClassName' : 'addClassName'](elem,c);
	},
	update: function(elem,html,dontStripScripts){
		elem = $(elem);
		if(!dontStripScripts){
			elem.innerHTML = html.stripScripts();
		    setTimeout(function(){html.evalScripts()}, 10);
		} else {
			elem.innerHTML = html;
		}
	},
	empty: function(elem){
		elem = $(elem);
		try {
			elem.innerHTML = '';
		} catch(e) {
			var elems = elem.getElementsByTagName('*');
			for(var i=0; i<elems.length; i++){
				elem.removeChild(elems[i]);
			}
		}
	},
	prepend: function(elem,node){
		elem = $(elem);
		elem.insertBefore(node,elem.firstChild);
	},
	append: function(elem,node){
		elem = $(elem);
		elem.appendChild(node);
	},
	setOpacity: function(elem,opacity){
		elem.style.opacity = opacity/100;
		elem.style.filter = 'alpha(opacity='+opacity+')';
	},
	visible: function(elem){
		return $(elem).style.display != 'none';
	},
	toggle: function(){
		for(var i=0; i<arguments.length; i++){
			var elem = $(arguments[i]);
			Elem[Elem.visible(elem) ? 'hide' : 'show'](elem);
		}
	},
	hide: function(){
		for(var i=0; i<arguments.length; i++)
			$(arguments[i]).style.display = 'none';
	},
	show: function(){
		for(var i=0; i<arguments.length; i++)
			$(arguments[i]).style.display = '';
	},
	getChildren: function(elem){
		elem = $(elem);
		if(elem.children) return elem.children;
		var elems = [];
		var c = elem.childNodes;
		for(var i=0; i<c.length; i++){
			var n = c[i];
			if(n.nodeName != '#text') elems.push(n);
		}
		return elems;
	},
	getLastChild: function(elem){
		elem = $(elem);
		var lastChild = elem.lastChild;
		while(lastChild.nodeName == '#text')
			lastChild = lastChild.previousSibling;
		return lastChild;
	}
}


//////////////////// *FORM ////////////////////
var Form = {
	getForm: function(frm){
		if(frm) frm = $(frm);
		else if(document.forms[0]) frm = document.forms[0];
		return frm;
	},
	getParentForm: function(input){
		return $(input).form;
	},
	addSubmitEvent: function(frm,func){
		Event.observe(frm,'submit',func);
	},
	focusFirstElement: function(frm){
		frm = Form.getForm(frm);
		if(frm && frm.elements && frm.elements.length){
			for(var i=0; i<frm.elements.length; i++){
				var input = frm.elements[i];
				if(input.type != 'hidden' && input.getAttribute('disallowfocus') == null){
					try {
						input.focus();
						return;
					}catch(e){
					}
				}
			}
		}
	},
	serialize: function(frm){
		frm = Form.getForm(frm);
		var str = '';
		if(frm && frm.length){
			for(var i=0; i<frm.length; i++){
				var input = frm[i];
				str += input.name+'='+encodeURIComponent($F(input));
				if(i<frm.length) str += '&';
			}
			return str;
		}
	},
	validate: function(frm){
		for(var i=0; i<frm.elements.length; i++){
			if(Form.validateInput(frm.elements[i],true) == false) return false;
		}
		return true;
	},
	validateInput: function(input,applyfocus){
		input = $(input);
		var required = input.getAttribute('required');
		required = required == 1 ? true : false;
		var validation = input.getAttribute('validation');
		if(validation == '' || validation == null) validation = 'required';
		validation = validation.toLowerCase();
		var value = "";
		var valid = false;
		if(input.type == 'radio' || input.type == 'checkbox'){
			if(input.checked) value = input.value;
			for(var i=0; i<input.form.elements.length; i++){
				if(input.form.elements[i].name == input.name){
					var cinput = input.form.elements[i];
					if(cinput.checked){
						valid = Valid[validation](cinput.value,required);
						break;
					}
				}
			}
		} else {
			value = input.value;
			valid = Valid[validation](value,required);
		}
		if(!input.form.errormsgs) input.form.errormsgs = {};
		if(!valid){
			// slap up an error message
			if(!input.form.errormsgs[input.name]){
				var msg = Form.errorMessage(input.getAttribute('message'),validation);
				input.form.errormsgs[input.name] = Elem.create('span',{className:'inputerror',innerHTML:msg})
				Elem.append(input.parentNode,input.form.errormsgs[input.name]);
			}
			if(applyfocus) input.focus();
			return false;
		}
		if(input.form.errormsgs[input.name]){
			// remove error message if it exists - the user fixed the input
			Elem.remove(input.form.errormsgs[input.name]);
			input.form.errormsgs[input.name] = false;
		}
		return true;
	},
	errorMessage: function(msg,validation,input){
		if(msg == '' || msg == null){
			switch(validation){
				case 'email':
					msg = 'Please enter a valid email address.';
					break;
				case 'phone':
					msg = 'Please enter a valid US phone number.';
					break;
				case 'zip':
					msg = 'Please enter a valid US Zip Code.';
					break;
				case 'ssn':
					msg = 'Please enter a valid social security number.';
					break;
				case 'guid':
					msg = 'Please enter a valid GUID.';
					break;
				case 'uuid':
					msg = 'Please enter a valid UUID.';
					break;
				default:
					msg = 'Please fill all required fields.';
					break;
			}
		}
		return msg;
	},
	hideSelects: function(){
		if(!document.getElementById || !document.all) return false;
		for(var i=0; i<document.forms.length; i++){
			var frm = document.forms[i];
			for(var j=0; j<frm.elements.length; j++){
				var input = frm.elements[j];
				if(input.type.indexOf('select') != -1) Elem.hide(input);
			}
		}
	},
	showSelects: function(){
		if(!document.getElementById || !document.all) return false;
		for(var i=0; i<document.forms.length; i++){
			var frm = document.forms[i];
			for(var j=0; j<frm.elements.length; j++){
				var input = frm.elements[j];
				if(input.type.indexOf('select') != -1) Elem.show(input);
			}
		}
	}
}


//////////////////// *AJAX ////////////////////
var Ajax = Class.create();
Ajax.getTransport = function(){
	return Try.these(
		function(){return new ActiveXObject('Msxml2.XMLHTTP')},
		function(){return new ActiveXObject('Microsoft.XMLHTTP')},
		function(){return new XMLHttpRequest()}
	) || false;
}
Ajax.prototype = {
	init: function(url,params){
		this.method = params.method || "get";
		this.method = this.method.toLowerCase();
		this.args = params.args || {};
		this.success = params.success || function(){};
		this.error = params.error || function(){};
		this.dontStripScripts = params.dontStripScripts || false;
		if(typeof this.args == 'object'){
			var paramstring = "";
			for(var i in this.args){
				if(typeof this.args[i] != 'object' && typeof this.args[i] != 'function')
					paramstring += i+"="+encodeURIComponent(this.args[i])+"&";
			}
		}
		this.container = params.container || false;
		this.XHR = Ajax.getTransport();
		var me = this;
		this.XHR.onreadystatechange = function(){me.checkState()};
		this.XHR.open(this.method, url, true);
		if(this.method == 'post') this.XHR.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
		this.XHR.send(paramstring);
	},
	checkState: function(){
		if(this.XHR.readyState == 4){
			if(this.XHR.status == 200){
				if(this.container) Elem.update(this.container,this.XHR.responseText,this.dontStripScripts);
				this.success(this.XHR);
				return;
			}
			this.error(this.XHR);
		}
	}
}


//////////////////// *UI ////////////////////
var UI = {
	init: function(){
		var tabs = $G('div',document,{className:'UITab'});
		for(var i=0; i<tabs.length; i++)
			new UI.Tab(tabs[i]);
			
		var trees = $G('ul',document,{className:'UITree'});
		for(var i=0; i<trees.length; i++){
			if(trees[i].getAttribute('id') != 'filebrowser')
				new UI.Tree(trees[i]);
		}
	}
}


//////////////////// *UI_MENU ////////////////////
UI.Menu = Class.create();
UI.Menu.instances = [];
UI.Menu.prototype = {
	params: {icons:true},
	iconFolder: '/global/images/icons/',
	init: function(elem,params){
		UI.Menu.instances.push(this);
		
		elem = $(elem);
		if(document.getElementById && document.all){
			elem.onmouseover = Form.hideSelects;
			elem.onmouseout = Form.showSelects;
		}
		
		this.current = [];
		if(params) this.params = params;
		var lis = $G('li',elem);
		for(var i=0; i<lis.length; i++){
			var li = lis[i];
			var level = 0;
			while(li.parentNode.nodeName.toLowerCase() == 'li' || li.parentNode.nodeName.toLowerCase() == 'ul'){
				level++;
				li = li.parentNode.parentNode;
			}
			this.addChild(lis[i],level);
		}
	},
	addChild: function(elem,level){
		new UI.Menu.Child(this,elem,level);
	},
	setCurrentPage: function(li_id){
		var menu = $(li_id);
		while(menu.parentNode.nodeName.toLowerCase() == 'li' || menu.parentNode.nodeName.toLowerCase() == 'ul') {
			Elem.addClassName(menu,'on');
			menu = menu.parentNode.parentNode;
		}
	}
}
UI.Menu.Child = Class.create();
UI.Menu.Child.prototype = {
	init: function(parent,elem,level){
		var me = this;
		this.parent = parent;
		this.level = level;
		this.timeout = null;
		this.container = elem;
		this.a = $G('a',elem)[0];
		this.ul = $G('ul',elem);
		this.ul.length ? this.ul = this.ul[0] : this.ul = {};
		this.a.onmouseover = function(){me.show()};
		this.ul.onmouseover = function(){me.reset()};
		this.a.onmouseout = this.ul.onmouseout = function(){me.hide()};
		this.icon = this.container.getAttribute('icon');
		if(this.parent.params.icons && this.icon){
			var span = Elem.create('span');
			span.className = 'navicon';
			span.style.backgroundImage = 'url("/global/images/icons/'+this.icon+'")';
			Elem.prepend(this.a,span);
		}
		if(this.ul.style){
			Elem.addClassName(this.a,'expandable');
		}
	},
	show: function(e){
		try {
			this.reset();
			var parent = this.parent;
			var lvl = this.level;
			if(parent.current[lvl]) parent.current[lvl].kill();
			parent.current[lvl] = this;
			Elem.addClassName(this.container,'over');
			Elem.addClassName(this.a,'over');
			if(this.ul.style) Elem.addClassName(this.ul,'over');
		}catch(e){}
	},
	hide: function(e){
		try {
			var me = this;
			if(this.ul.style){
				this.timeout = setTimeout(function(){me.kill()},700);
			} else {
				this.kill();
			}
		}catch(e){}
	},
	kill: function(e){
		Elem.removeClassName(this.container,'over');
		Elem.removeClassName(this.a,'over');
		if(this.ul.style) Elem.removeClassName(this.ul,'over');
	},
	reset: function(e){
		if(this.timeout) clearTimeout(this.timeout);
	}
}


//////////////////// *UI_TAB ////////////////////
UI.Tab = Class.create();
UI.Tab.instances = [];
UI.Tab.prototype = {
	init: function(elem){
		UI.Tab.instances.push(this);
		
		this.elem = $(elem);
		Elem.removeClassName(elem,'UITab');
		Elem.addClassName(elem,'UITab_init');
		this.tabs = $G('div',this.elem,{parentNode:this.elem});
		
		this.labelcontainer = Elem.create('div',{className:'labels'});
		Elem.prepend(this.elem,this.labelcontainer);
		
		for(var i=0; i<this.tabs.length; i++)
			this.initTab(this.tabs[i],i);
		this.labels = $G('div',this.elem,{parentNode:this.labelcontainer,
						 				  className:'label'});
		if(this.tabs.length) this.openTab(0);
	},
	initTab: function(tab,i){
		Elem.hide(tab);
		Elem.addClassName(tab,'tab');
		var label = Elem.create('div',{className:'label',
									   innerHTML:'<span class="left"><span class="right">'+tab.getAttribute('label')+'</span></span>'});
		label.index = tab.index = i;
		label.ref = tab.ref = this;
		label.onmouseover = label.onmouseout =  this.labelOverOut;
		label.onmousedown =  this.labelClick;
		Elem.append(this.labelcontainer,label);
	},
	labelOverOut: function(){
		if(!Elem.hasClassName(this,'on'))
			Elem.toggleClassName(this,'over');
	},
	labelClick: function(){
		var me = this.ref;
		for(var i=0; i<me.tabs.length; i++)
			i != this.index ? me.closeTab(i) : me.openTab(i);
	},
	closeTab: function(index){
		Elem.hide(this.tabs[index]);
		Elem.removeClassName(this.labels[index],'on');
	},
	openTab: function(index){
		Elem.show(this.tabs[index]);
		Elem.removeClassName(this.labels[index],'over');
		Elem.addClassName(this.labels[index],'on');
	}
}


//////////////////// *UI_TREE ////////////////////
UI.Tree = Class.create();
UI.Tree.instances = [];
UI.Tree.prototype = {
	init: function(elem,listeners){
		UI.Tree.instances.push(this);
		this.listeners = listeners || {main:{},child:{}};
		elem = $(elem);
		this.current = {head:{}};
		this.rootNode = elem;
		this.branches = [];
		this.initChildren(elem);
	},
	initChildren: function(parentnode){
		parentnode.isBranch = true;
		var children = Elem.getChildren(parentnode);
		for(var i=0; i<children.length; i++){
			var node = children[i];
			if(node.nodeName.toLowerCase() == 'li'){
				var isLastChild = i == children.length-1 ? true : false;
				this.initChild(node,isLastChild);
				if(node.childNodes.length > 1){
					var ul = $G('ul',node,{parentNode:node});
					if(ul.length) this.initChildren(ul[0]);
				}
			}
		}
	},
	initChild: function(elem,isLastChild){
		new UI.Tree.Child(this,elem,isLastChild,this.listeners.child);
	},
	expandAll: function(){
		for(var i=0; i<this.branches.length; i++)
			this.branches[i].expand();
	},
	collapseAll: function(){
		for(var i=0; i<this.branches.length; i++)
			this.branches[i].collapse();
	},
	// crud for branches to avoid reinitializing control
	createChild: function(parentnode,node){
		parentnode = $(parentnode);
		if(!parentnode.isBranch) alert('not a branch parent');
		if(node.nodeName.toLowerCase() != 'li') alert('not an li element');
		
		Elem.removeClassName(Elem.getLastChild(parentnode),'lastchild');
		
		parentnode.appendChild(node);
		this.initChild(node,true);
	},
	
	setCurrent: function(child){
		if(this.current) Elem.removeClassName(this.current.head,'current');
		this.current = child;
		Elem.addClassName(this.current.head,'current');
	},
	getCurrent: function(){
		return this.current;
	}
}
UI.Tree.Child = Class.create();
UI.Tree.Child.prototype = {
	callListener: function(method){
		var L = this.listeners[method];
		if(L) L(this);
	},
	init: function(tree,elem,isLastChild,listeners){
		var me = this;
		this.tree = tree;
		this.container = elem;
		
		if(this.container.initialized) return;
		this.container.initialized = true;
		
		this.listeners = listeners || {};
		
		this.head = $G('div',elem)[0];
		this.ul = $G('ul',elem);
		this.ul.length ? this.ul = this.ul[0] : this.ul = {};
		this.head.onmouseover = function(e){
			e = e || window.event;
			me.mouseover(e);
		}
		this.head.onmouseout = function(e){
			e = e || window.event;
			me.mouseout(e);
		}
		if(isLastChild)
			Elem.addClassName(this.container,'lastchild');
		if(this.ul.style){
			Elem.addClassName(this.head,'branch');
			this.tree.branches.push(this);
			this.head.onclick = function(e){
				e = e || window.event;
				me.click(e);
			}
			this.collapse();
		}
		
		//this.callListener('init');
	},
	click: function(e){
		this.tree.setCurrent(this);
		if(this.expanded){
			this.collapse();
		} else {
			this.expand();
		}
		
		this.callListener('branch_dblclick');
		Event.stop(e);
	},
	collapse: function(){
		this.expanded = false;
		Elem.hide(this.ul);
		Elem.removeClassName(this.head,'expanded');
		
		//this.callListener('collapse');
	},
	expand: function(){
		this.expanded = true;
		Elem.show(this.ul);
		Elem.addClassName(this.head,'expanded');
		
		//this.callListener('expand');
	},
	mouseover: function(e){
		try {
			//Elem.addClassName(this.head,'over');
			
			this.callListener('mouseover');
			Event.stop(e);
		}catch(er){}
	},
	mouseout: function(e){
		try {
			//Elem.removeClassName(this.head,'over');
			
			this.callListener('mouseout');
			Event.stop(e);
		}catch(er){}
	}
}


//////////////////// *SHORTCUTS ////////////////////
var $ = function(elem){
	if(typeof elem == 'string') elem = document.getElementById(elem);
	return elem;
}
var $G = Elem.get;
var $F = function(input){
	input = $(input);
	if(input.value != undefined) return input.value;;
}
var $L = function(func){
	Event.observe(window,'load',func);
}


//////////////////// *VALIDATION ////////////////////
var Valid = {
	// verifies a required field is filled
	required: function(value,required){
		if((value == null || !value.length) && required) return false;
		return true;
	},
	// matches a@a.aa
	email: function(value,required){
		return Valid.regex(value, /^[a-zA-Z_0-9-'\+~]+(\.[a-zA-Z_0-9-'\+~]+)*@([a-zA-Z_0-9-]+\.)+[a-zA-Z]{2,7}$/, required);
	},
	/*  matches:
		617.219.2000 
		219-2000
		(617)283-3599 x234
		1(222)333-4444
		1 (222) 333-4444
		222-333-4444
		1-222-333-4444
		
		non-matches:
		44-1344-458606
		+44-1344-458606
		+34-91-397-6611
		7-095-940-2000
		+7-095-940-2000
		+49-(0)-889-748-5516		*/
	phone: function(value,required){
		return Valid.regex(value, /^(((1))?[ ,\-,\.]?([\\(]?([1-9][0-9]{2})[\\)]?))?[ ,\-,\.]?([^0-1]){1}([0-9]){2}[ ,\-,\.]?([0-9]){4}(( )((x){0,1}([0-9]){1,5}){0,1})?$/, required);
	},
	/*  matches:
		22222
		22222-2222		*/
	zip: function(value,required){
		return Valid.regex(value, /^([0-9]){5,5}$|(([0-9]){5,5}(-| ){1}([0-9]){4,4}$)/, required);
	},
	/*  matches:
		aaa-aa-aaaa
		aaa aa aaaa
		aaaaaaaaa		*/
	ssn: function(value,required){
		return Valid.regex(value, /^[0-9]{3}(-| )?[0-9]{2}(-| )?[0-9]{4}$/, required);
	},
	/*  matches:
		string of length 36 hexadecimal digits (0-9 or A-F) formatted as
		XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX		*/
	guid: function(value,required){
		return Valid.regex(value, /[A-Fa-f0-9]{8,8}-[A-Fa-f0-9]{4,4}-[A-Fa-f0-9]{4,4}-[A-Fa-f0-9]{4,4}-[A-Fa-f0-9]{12,12}/, required);
	},
	/*  matches:
		string of length 35 hexadecimal digits (0-9 or A-F) formatted as:
		XXXXXXXX-XXXX-XXXX-XXXXXXXXXXXXXXXX			*/
	uuid: function(value,required){
		return Valid.regex(value, /[A-Fa-f0-9]{8,8}-[A-Fa-f0-9]{4,4}-[A-Fa-f0-9]{4,4}-[A-Fa-f0-9]{16,16}/, required);
	},
	/*  matches:
		http://www.mm.com/index.cfm
		HTTP://WWW.MM.COM
		http://www.mm.com/index.cfm?userid=1&name=mike+nimer
		http://www.mm.com/index.cfm/userid/1/name/mike+nimer - trick used by cf developers so search engines can parse their sites (search engines ignore query strings)
		ftp://www.mm.com/
		ftp://uname:pass@www.mm.com/
		mailto:email@address.com
		news:rec.gardening
		news:rec.gardening
		http://a/
		file://ftp.yoyodyne.com/pub/files/foobar.txt		*/
	url: function(value,required){
		return Valid.regex(value, /^((http|https|ftp|file)\:\/\/([a-zA-Z0-0]*:[a-zA-Z0-0]*(@))?[a-zA-Z0-9-\.]+(\.[a-zA-Z]{2,3})?(:[a-zA-Z0-9]*)?\/?([a-zA-Z0-9-\._\?\,\'\/\+&amp;%\$#\=~])*)|((mailto)\:[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)*@([a-zA-Z0-9-]+\.)+[a-zA-Z0-9]{2,7})|((news)\:[a-zA-Z0-9\.]*)$/, required);
	}, // '
	// takes a string, regex, and boolean indicating whether it has to match or can be empty
	regex: function(value, pattern, required){
		value = value.replace(/^\s+/,'').replace(/\s+$/,'');
		
		if(!value.length && !required) return true;
		return pattern.test(value);
	}
}
