
function load_content(args)
{
	// var loader_id = '#lc_loader_' + timestamp();
	
	// TODO: workout how to do json as a type properly
	
	var load = {
		loader: '<img id="lc_loader" src="codebase/img/loader/spin.gif" style="background-color: transparent; position:absolute; left: 35%; z-index:9999;" />',
		loader_id: '#lc_loader',
		trans: 'fast',
		sync: false,
		into: false,
		url: false,
		load_type: 'html',
		send_type: "POST",
		args: false,
		timeout: 30000,
		on_error: "report(mess,{inject: load.into, type:'sticky'})",
		on_complete: false
	}
	
	// merge the incoming args with the default values in the load object
	if(args) $.extend(load, args);
	
	// done merging args ... see if anything is missing
	if(load.into && load.url && load.load_type && load.send_type) // clear the current content then load the new content
	{
		// transition out the old content
		$(load.into).fadeOut(load.trans, function(){

			$(load.into).html(''); // blank out the old content
			$(load.into).fadeIn(); // instant reappear ... BOO!
			$(load.into).prepend(load.loader);	// add the loader into the area
		
			// do the ajax
			$.ajax({
				async: load.sync,
				type: load.send_type,
				url: load.url,
				data: load.args,
				dataType: load.load_type,
				timeout: load.timeout,
				error: function(request,estring,exception){ 
					var mess = ajax_error(request,estring,exception); // get the formatted error message
	 				$(load.loader_id).fadeOut('fast', function(){ $(load.loader_id).remove(); }); // remove the spinner
					eval(load.on_error); // run the on_error callback
				},
				success: function(data){
				
					// remove the spinner
	 			 	$(load.loader_id).fadeOut('fast', function(){ $(load.loader_id).remove(); }); // remove the spinner

					switch(load.load_type)
					{
						case 'html':
							$(load.into).append(data); //append(data).fadeIn('fast'); //load the content into the load.into area
							
							
							
						break;
				
						// case 'json':
						// 	// extract json vars
						// 	var ret = data.op[0].ret; 
						// 					 		var msg = data.op[0].msg; 
						// 	var scpt = data.op[0].scpt;
						// 	var data = data.op[0].data;
						// 	
						// 	if(scpt) eval(scpt);
						// 
						// 	if(!ret) // finish up as an error 
						// 	{ 
						// 		// report(msg,{inject:'#board_sort_messages',type:'sticky'});
						// 	} 
						// 	else if(ret == -1) // finish with a warning
						// 	{
						// 		// report(msg,{inject:'#board_sort_messages',cls:'appwarn'});		
						// 	}
						// 	else // finish sucessfully
						// 	{ 
						// 		// report(msg,{inject:'#board_sort_messages',cls:'appmsg'});
						// 	}	
						// break;			
					}
				},
				complete: function(request,status){
					// report(msg,{inject:'#board_sort_messages',cls:'appmsg'});
					// run the callback if there is one
					if(load.on_complete) eval(load.on_complete);

					if(typeof(callstack)=="object")
					{
						for(c=0;c<callstack.length;c++)
						{
							eval(callstack[c]);
						}

						callstack = new Array(); // all done with it so blank it out in case another function has a callstack!
					}
				}
			});	
		});
	}	
	else
	{
		alert("load_content faild to initalize beacuse of missing variables in the load object. current makeup of the load object is: ")
	}
}




/**
 * collect and handle ajax errors thrown by the error: handler for jQuery::$.ajax(). Return a description of what went wrong
 *
 * @return void
 * @author Dan Bryant <db@leadingtone.org>
 * @copyright LearningChange LLC and Dan Bryant (Shared Source Initiave/Dual Ownership),  8 August, 2007
 * @package core.js
 * @version: 1.0
 * @perams:
 *	request: the request object
 *	estring: a decriptive string explaining the error
 **/
function ajax_error(request,estring,exception)
{
	var message = '';
	if(request) message += 'Request object: ' + request.status + ' :: ' + request.statusText + '; ';
	if(estring) message += 'Error string: ' + estring + '; ';
	if(exception) 
	{	message += 'Error object:';
		if(exception.name) message += 'Name: ' + exception.name + ', ';
		if(exception.description) message += 'Description: ' + exception.description + ', ';
		if(exception.message) message += 'Message: ' + exception.message + ', ';		
		message = erase_last(', ',message) + '; ';
	}
	return 'AjaxError: ' + erase_last('; ',message) + '.';
}

// analog of erase_last in format.php
function erase_last(kill,string)
{
	return string.substr(0,(string.length - kill.length));
}

/* Author: Dan Bryant <db@leadingtone.org>
 * description: a general message reporting function for jquery bassed web apps. 
 *   adds a error message to a <span id="app_msg">. The error message fades out after 5 seconds or on click if it is an error *
 * params: 
 * 		msg: the error message
 * 			name: 			:: 		type:default value				:: 		description
 * 		cfgin: a function configuration object                                                                                    
 * 	 		inject: 		:: string (as a jQuery ID):'#app_msg' 	:: where do you want to put the output?                                                
 * 	 		append: 		:: bool:true 							:: append the output inside the inject or prepend it if false                               
 * 	 		cls: 			:: string: 'apperr' (you can use appmsg and appwarn see css/ui_styles.css) :: set the class of the div ...                                                           
 * 	 		fade_for: 		:: int:10000							:: a time to fade out in miliseconds; 1000 = 1 second; defaults to 10 seconds                  
 * 			type: 			:: string ( sticky || fade ):fade 		:: sticky *cough* sticks until you click on it, fade automagicly fades out. defaults to fade 
 * 			url_trans: 		:: bool:true 							:: translate email addresses and urls in messages into clickable links?
 */
//function report(msg,cls,type,fade_for) 
function report(msg,cfgin) 
{	
	// define default values needed by this function	
	var cfg = {
		width: false,
		inject: '#app_msg',
		append: true,
		cls:'apperr',
		fade_for: 10000,
		type: 'fade',
		url_trans: false
	}
	
	if(cfgin) $.extend(cfg, cfgin);

	// if(cfg.url_trans)
	// {
	// 	// is there a url in the message? extract it
	// 	if(msg.match(/((ht|f)tp(s?)\:\/\/|~\/|\/)?([\w]+:\w+@)?([a-zA-Z]{1}([\w\-]+\.)+([\w]{2,5}))(:[\d]{1,5})?((\/?\w+\/)+|\/?)(\w+\.[\w]{3,4})?((\?\w+=\w+)?(&\w+=\w+)*)/))
	// 	{
	// 		// alert('found url');
	// 	}
	// }

	// create the empty element and get the handle
	var dv = document.createElement("div");

	// add the div into the target
	if(cfg.append) { $(cfg.inject).append(dv); }
	else $(cfg.inject).prepend(dv);

	// set the error type and inject the content in to the new div
	switch(cfg.type)
	{
		case 'sticky':
		
			// add the requested class to the new div ... but make it sticky!
			if(document.all) { var ie = 'is_really_fucking_retarded'; dv.className = cfg.cls + '_sticky'; } // ie specific code
			else { dv.setAttribute("class", cfg.cls + '_sticky'); } // real browsers
			
			if(cfg.width)
			{
				$(dv).css({width:cfg.width});
			}
					
			// show the message 
			var close_btn = document.createElement('span');

			// class the close button
			// $(close_btn).attr({class:'sticky_message_button'}); // ie chokes on this ... fucking retarded! ie is choking on the word class for chrisake!
			if(document.all) { var _ie_ = 'seriously___ie_is_retarded'; close_btn.className = "sticky_message_button"; } // ie specific code
			else { close_btn.setAttribute("class", "sticky_message_button"); } // real browsers

			$(close_btn).html('close');
			$(close_btn).bind( 'click', function() { $(dv).fadeOut('slow', function(){$(this).remove();}); }); 
			$(dv).html(msg);
			
			$(dv).prepend(close_btn);
		break;
		
		case 'fade':

			// add the requested class to the new div 
			if(document.all) { var ie = 'is_retarded'; dv.className = cfg.cls; } // ie specific code
			else { dv.setAttribute("class", cfg.cls); } // real browsers


			$(dv).html(msg);
			$(dv).fadeOut(cfg.fade_for, function(){$(this).remove();});
		break;	
	}
}

// from a given decimal amount lookup the fractional representation
function frac_lookup(decimal)
{                         
	// a lookup table keyed by decimal inches	
	var frac_inch = new Array();
	frac_inch['0.015625']='1/64';
	// frac_inch['0.031250']='1/32';
	frac_inch['0.03125']='1/32';
	frac_inch['0.046875']='3/64';
	// frac_inch['0.062500']='1/16';
	frac_inch['0.0625']='1/16';
	frac_inch['0.078125']='5/64';
	// frac_inch['0.093750']='3/32';
	frac_inch['0.09375']='3/32';
	frac_inch['0.109375']='7/64';
	// frac_inch['0.125000']='1/8';
	frac_inch['0.125']='1/8';
	frac_inch['0.140625']='9/64';
	// frac_inch['0.156250']='5/32';
	frac_inch['0.15625']='5/32';
	frac_inch['0.171875']='11/64';
	// frac_inch['0.187500']='3/16';
	frac_inch['0.1875']='3/16';
	frac_inch['0.203125']='13/64';
	// frac_inch['0.218750']='7/32';
	frac_inch['0.21875']='7/32';
	frac_inch['0.234375']='15/64';
	// frac_inch['0.250000']='1/4';
	frac_inch['0.25']='1/4';
	frac_inch['0.265625']='17/64';
	// frac_inch['0.281250']='9/32';
	frac_inch['0.28125']='9/32';
	frac_inch['0.296875']='19/64';
	// frac_inch['0.312500']='5/16';
	frac_inch['0.3125']='5/16';
	frac_inch['0.328125']='21/64';
	frac_inch['0.343750']='11/32';
	frac_inch['0.34375']='11/32';
	frac_inch['0.359375']='23/64';
	// frac_inch['0.375000']='3/8';
	frac_inch['0.375']='3/8';
	frac_inch['0.390625']='25/64';
	// frac_inch['0.406250']='13/32';
	frac_inch['0.40625']='13/32';
	frac_inch['0.421875']='27/64';
	// frac_inch['0.437500']='7/16';
	frac_inch['0.4375']='7/16';
	frac_inch['0.453125']='29/64';
	// frac_inch['0.468750']='15/32';
	frac_inch['0.46875']='15/32';
	frac_inch['0.484375']='31/64';
	// frac_inch['0.500000']='1/2';
	frac_inch['0.5']='1/2';
	frac_inch['0.515625']='33/64';
	// frac_inch['0.531250']='17/32';
	frac_inch['0.53125']='17/32';
	frac_inch['0.546875']='35/64';
	// frac_inch['0.562500']='9/16';
	frac_inch['0.5625']='9/16';
	frac_inch['0.578125']='37/64';
	// frac_inch['0.593750']='19/32';
	frac_inch['0.59375']='19/32';
	frac_inch['0.609375']='39/64';
	// frac_inch['0.625000']='5/8';
	frac_inch['0.625']='5/8';
	frac_inch['0.640625']='41/64';
	// frac_inch['0.656250']='21/32';
	frac_inch['0.65625']='21/32';
	// frac_inch['0.671850']='43/64';
	frac_inch['0.67185']='43/64';
	// frac_inch['0.687500']='11/16';
	frac_inch['0.6875']='11/16';
	frac_inch['0.703125']='45/64';
	// frac_inch['0.718750']='23/32';
	frac_inch['0.71875']='23/32';
	frac_inch['0.734375']='47/64';
	// frac_inch['0.750000']='3/4';
	frac_inch['0.75']='3/4';
	frac_inch['0.765625']='49/64';
	// frac_inch['0.781250']='25/32';
	frac_inch['0.78125']='25/32';
	frac_inch['0.796875']='51/64';
	// frac_inch['0.812500']='13/16';
	frac_inch['0.8125']='13/16';
	frac_inch['0.828125']='53/64';
	// frac_inch['0.843750']='27/32';
	frac_inch['0.84375']='27/32';
	frac_inch['0.859375']='55/64';
	// frac_inch['0.875000']='7/8';
	frac_inch['0.875']='7/8';
	frac_inch['0.890625']='57/64';
	// frac_inch['0.906250']='29/32';
	frac_inch['0.90625']='29/32';
	frac_inch['0.921875']='59/64';
	// frac_inch['0.937500']='15/16';
	frac_inch['0.9375']='15/16';
	frac_inch['0.953125']='61/64';
	// frac_inch['0.968750']='31/32';
	frac_inch['0.96875']='31/32';
	frac_inch['0.984375']='63/64';  

	if(frac_inch[decimal]) return frac_inch[decimal];
	else return false;
}

// parse a human readable inches string into a total amount of inches
function parse_human_inches(string)
{
	// string = "2'"; // test case: just feet
	// string = "2'" +  ' 1"'; // test case: feet w/whole inches
	// string = "2'" +  ' 1 1/2"'; // test case: feet w/whole inches and fractional inches
	// string = "2'" +  '1/2"'; // test case: fet and fractional inches
	// string = '1 1/2"'; // test case: whole inches and fractional inches
	// string = '1/2"'; // test case: just fractional inches

	// TODO: start by cleaning the string
	// string = string.replace(/[^\d \/'"/,''); // 
	
	var total_inches = 0;
	if(string.indexOf("'") != string.lastIndexOf("'")) return false; // does the string have only one ' 
	if(string.indexOf('"') != string.lastIndexOf('"')) return false; // does the string have only one " 
	if(string.indexOf("'") == -1 && string.indexOf('"') == -1 ) return false; // string is missing both foot and inch markers 
	if((string.indexOf("'") != -1 && string.indexOf('"') != -1 ) && (string.indexOf("'") > string.indexOf('"')) ) return false; // foot and inch markers are backwards
	
	// TODO: match the incoming string against a regex of human inches to validate the placement of the parts (correct order)
	
	var t = new Array();
	if(string.indexOf("'") != -1) // there is a foot part of the string ... split the string on it
	{
		t = string.split("'",2);
	}
	
	if(t[0]) // figure out how many feet there are
	{
		feet = parseInt(trim(t[0])); // try to get a usable foot amount out of the string
		if(feet == NaN) return false; // the feet part of the string is not an integer; number cannot calculate feet!
		total_inches += (feet*12);
	}
	
	if(t[1]) // there was an inches part in the string
	{
		inches = trim(t[1]); // remove spaces on outer edges
	}
	else if(!t[0]) // there was no feet part of the string ... grab the inches directly fom the string
	{
		inches = trim(string);
	}
	else
	{
		inches = '0';
	}

	// clean up the inches string
	inches = inches.replace(/[^\d \/]/,''); // remove any junk in the inches string
	inches = inches.replace(/[\s]+/,' '); // remove any multiple spaces between the inches and the fraction of an inch

	var i = new Array();
	if(inches.indexOf(' ') != -1) // there are fractional inches 
	{
		i = inches.split(' ',2);
	}
	
	// add any middle inch part you can find
	if(i[0]) // there are fractional inches
	{
		inch = parseInt(i[0]); // make sure that the middle part is a number
		if(inch == NaN) return false;
		total_inches += inch; // add the inch component to the total inches
	}
	else if(inches.indexOf('/') == -1) // there are no fractional inches
	{
		inch = parseInt(inches); // make sure that the middle part is a number
		if(inch == NaN) return false;
		total_inches += inch; // add the inch component to the total inches		
	}
	
	if(i[1]) // there are fractional inches
	{
		// parse a decimal amount from the fractional amount
		dec = parseFloat(eval(i[1])); // make sure were dealing with a number

		// check the validity of the dec
		if(isNaN(dec)) return false; // did not find a number
		if(!frac_lookup(dec)) return false; // fractional part was not a valid fractional amount

		// valid decimal amount ... add it!
		 total_inches += dec;
	}
	else if(inches.indexOf('/') != -1) // you have only a fractional inch left
	{
		dec = parseFloat(eval(inches)); // make sure were dealing with a number

		// check the validity of the dec
		if(isNaN(dec)) return false; // did not find a number
		if(!frac_lookup(dec)) return false; // fractional part was not a valid fractional amount

		// valid decimal amount ... add it!
		 total_inches += dec;
	}

	return total_inches;
}

/**
 * validate a field against arguments passed in through args
 *
 * @return bool
 * @author Dan Bryant <db@leadingtone.org>
 * @copyright LearningChange LLC and Dan Bryant (Shared Source Initiave/Dual Ownership),  1 May, 2007
 * @package core.js
 * @version: 1.0
 * @params:
 *	field = a html input element
 *  no_error = has an error already occured (take from previous return to validate) ... track previous errors
 *	args = a hash 
 *  	possible values:
 *			message: '',
 *  		preg: '',
 *  		match: null,
 *  		special: '',
 *  		remote: '',
 *  		rargs: '',
 *  		show_valid: false
example:

	var args = ''; // passsed to $.ajax()
	var no_error = true;

	var bname = encode(form.bname.value);
	no_error = validate(form.bname,no_error,{special:'!blank',show_valid:true,vmsg:'Please input your Business Name.'});
	args += (bname) ? 'bname=' + bname : '';
 **/
function validate(field,no_error,args) 
{
	var arg = {
		append: true,
		embed: true,
		style: false,
		imagepath:'codebase/img/',
		image: 'error.gif',
		msgclass: 'verror',
		vmsg: '',
		preg: '',
		match: null,
		special: '',
		remote: '',
		rargs: '',
		input_elm: false,
		show_valid: false
	}

	if(args) $.extend(arg, args);

	// get the value of the field to check
	var check_val = (arg.input_elm) ? field : field.value;

	// Assume true
	retval = true;

	// Do we have a remote page to check?
	if(arg.remote)
	{
		// Do the ajax call using the remote for the page and the rargs. pass the json.return and json.message back to through this
	}
	else // do all the other checks
	{
		// If we have a preg see if value matches
		if(arg.preg)
		{
			if(!check_val.match(arg.preg)) retval = false; 
		}

		// If we have have a value match field check the two fields values
		if(arg.match)
		{
			// match against a single value if check_val is not an array ... if it is then loob through the array breaking and true when found
			if(check_val != arg.match.value) retval = false; 
		}

		// Do we have a special check to do?
		if(arg.special)
		{
			switch(arg.special)
			{
				case '!blank':
					if(check_val.replace(/\s+/,'').length == 0){ retval = false; }
				break;

				case '!url':
				 	if(!check_val.match(/^((ht|f)tp(s?)\:\/\/|~\/|\/)?([\w]+:\w+@)?([a-zA-Z]{1}([\w\-]+\.)+([\w]{2,5}))(:[\d]{1,5})?((\/?\w+\/)+|\/?)(\w+\.[\w]{3,4})?((\?\w+=\w+)?(&\w+=\w+)*)?/)){ retval = false; }
				break;

				case '!email':
					if(!echeck(check_val)){ retval = false; }
				break;

				case '!money':
					if(!check_val.match(/(^\$)?((\d{1,3}(,\d{3})*|\d{1,9999})(\.\d{2})?$)|((\d{1,3}(,\d{3})*|\d{1,9999})?(\.\d{2})$)/)){ retval = false; }
				break;

				default: alert("Cannot handle special validation: " + arg.specal);
			}
		}		
	}

	// does a span class="dynmsg" exist? remove it clearing the way for a new one for this field
	affect = (!arg.input_elm) ? field : arg.input_elm;
	$(affect).siblings(".dynmsg").remove();
	
	var style = (arg.style) ? ' style="'+arg.style+'"' : '';
	
	// show the proper message for the set return value
	if(retval) 
	{
		if(arg.show_valid)
		{	
			var valid_msg = '<span class="jvmsg dynmsg"'+style+'><img src="'+arg.imagepath+'accept.gif" title="valid data format" alt="valid data format"/></span>';			
			// figure out the parent of this field and append the validation message to it
			if(arg.append) 
			{
				$(affect).parent().append(valid_msg);
			}
			else
			{
				$(affect).parent().prepend(valid_msg);	
			}

		}
		rval = (no_error) ? true : false; // dont return true if there was a previous error
		return rval;		
	}
	else
	{			
		if(arg.embed)
		{
			var formatted_message = (arg.vmsg) ? '<span class="jvmsg dynmsg"'+style+'><img  src="'+arg.imagepath+''+arg.image+'" alt="error in data" title="'+arg.vmsg+'" /></span>' : '<span class="jvmsg dynmsg"><img src="'+arg.imagepath+''+arg.image+'" alt="error in data" /></span>'
		}	
		else
		{
			var formatted_message = (arg.vmsg) ? '<span class="'+arg.msgclass+'"'+style+'>' + arg.vmsg + '</span>' : '';
			formatted_message = '<span class="jvmsg dynmsg"><img src="'+arg.imagepath+''+arg.image+'" alt="error in data" />' + formatted_message + '</span>';
		}	
		
		// prepend or apend to the parent?
		if(arg.append)
		{
			$(affect).parent().append(formatted_message);
		}
		else
		{
			$(affect).parent().prepend(formatted_message);
		}
		return false;
	}
}

/**
 * show a fake validataion message when your using external validataion methods but still want to show a core.js::validate() style message
 * note this is temporary until I can get time to finish validate() </joke> ... hahahhah!
 *
 * @return markup
 * @author Dan Bryant <db@leadingtone.org>
 * @copyright LearningChange LLC and Dan Bryant (Shared Source Initiave/Dual Ownership), 20 November, 2007
 * @package core.js
 * @version 1.0
 * @perams:
 *	type: fail || valid || <future> warn,stop,other...?
 * example:
 * 		vmesg({jqid:'#dimensions',message:'Please set the product Dimensions properly.',style:'float: left;',append:false}); // a failure with style override set, type specified, and appending to the jqid
 * 		vmesg({ jqid:'#dimensions', type:'valid', message:'Dimensions are valid.', style:'float: left;', append:false }); // a sucess with style override set, type specified, and appending to the jqid
 **/
function vmesg(args)
{
	var arg = {
		jqid: false,
		append: true,
		type: 'error', 
		style: false,
		imagepath:'codebase/img/',
		message: false
	}

	if(args) $.extend(arg, args);	
	
	var markup = '';
	
	if(!arg.jqid || !arg.message) 
	{
		markup += 'Misconfigured vmesg() call. Missing arguments.';
	}
	else // configure the output
	{	
		$(arg.jqid + ' span.jvmsg').remove() // pull the previous jvmsg'es
		
		var style = (arg.style) ? ' style="'+arg.style+'"' : '';
		markup += '<span'+style+' class="jvmsg dynmsg">';
	
		switch(arg.type)
		{
			case 'error':
				image = 'error.gif';
				alt_text = 'data error';
			break;

			case 'valid':
				image = 'accept.gif';
				alt_text = 'valid data';
			break;
						
			default:
				markup += 'Misconfigured vmesg() call. Unknown type:' + arg.type + '.';
		}
	
		markup += '<img title="'+arg.message+'" alt="'+alt_text+'" src="'+arg.imagepath+image+'"/>';
		markup += '</span>';

	}
	
	// do the output
	if(arg.append) { $(arg.jqid).append(markup); }
	else { $(arg.jqid).prepend(markup); }
}



// clean any lingering validation messages based on their type
function vclean(type)
{
	switch(type)
	{
		case 'vmesg': // a message set by validate.js
			$(".jvmsg").remove();
		break;
		
		case 'umesg': // a message set by mod_used
			$(".mod_used").remove();
		break;
		
		default:
			$(".jvmsg").remove();
			$(".mod_used").remove();
	}
}


// visit a "page" with the "args"
function go(url)
{
	window.location = url;
}

// analog of php function
/**
 * check if a varialble 'v' is empty null or undefined
 *
 * @return void
 * @author Dan Bryant <db@leadingtone.org>
 * @copyright LearningChange LLC and Dan Bryant (Shared Source Initiave/Dual Ownership),  1 May, 2007
 * @package core.js
 * @version: 1.0
 * @params:
 *	v = a variable
 **/
function empty(v) // I want my, I want my empty v!
{
	switch(v)
	{
		case "": return true; break;
		case null: return true; break;
		case undefined: return true; break;
	}
	return false;
}


/**
 * see if a string is made entirely of spaces or blank
 *
 * @return void
 * @author Dan Bryant <db@leadingtone.org>
 * @copyright LearningChange LLC and Dan Bryant (Shared Source Initiave/Dual Ownership),  7 May, 2007
 * @package core.js
 * @version: 1.0
 * @params:
 *	look = sring to look at
 **/
function blank(look)
{
	// make damn sure that youe checking against a string
	look += '';	
	// if(look.match(/^\s?$/)) return true;
	if(look.replace(/\s+/,'').length == 0) return true;
	return false; // else 
}



/**
 * scrub the value of an input field
 *
 * @return void
 * @author Dan Bryant <db@leadingtone.org>
 * @copyright LearningChange LLC and Dan Bryant (Shared Source Initiave/Dual Ownership), 13 June, 2007
 * @package core.js
 * @version: 1.0
 * @perams:
 *	
 **/
function scrub(el,type,limit)
{
	limit = limit || false; 
	
	switch(type)
	{
		case 'inch': 
			el.value = el.value.replace(/[^\d \/"']+/,''); // kill any non digit
		break;

		case 'nint': // allow negative int
			el.value = el.value.replace(/[^\d\-]+/,''); // kill any non digit, allow '-'
		break;

		case 'int': // positive integer
			el.value = el.value.replace(/[^\d]+/,''); // kill any non digit
		break;

		case 'float': // positive float
			el.value = el.value.replace(/[^\d.]+/,''); // kill any non digit, allow '.'
		break;

		case 'nfloat': // allow negative flaot
			el.value = el.value.replace(/[^\d.\-]+/,''); // kill any non digit, allow  '.' & '-'
		break;

		case 'uname':
			el.value = el.value.replace(/[^\w.,_\- ]/,''); // kill any non word characters except for - _ " " and .
		break;	
		
		case 'limit': // a positive limit number
			if(!limit){ alert('programming error: you must conficure a limit when scrubbing for "limit"'); }
			el.value = el.value.replace(/[^\d.]+/,'');
			if(el.value > limit) { el.value = limit; } // if the limit is excesdded then set it to the max
		break;
		
		case 'nlimit': // a limit number allowing negative numbers
			if(!limit){ alert('programming error: you must conficure a limit when scrubbing for "nlimit"'); }
			el.value = el.value.replace(/[^\d.\-]+/,'');
			if(el.value > limit) { el.value = limit; }
		break;
		
	}
}


/**
 * create the markup for a select box and return the markup
 *
 * @return markup
 * @author Dan Bryant <db@leadingtone.org>
 * @copyright LearningChange LLC - Dan Bryant (Shared Source Initiave/Dual Ownership), 16 November, 2007
 * @package core.js
 * @version: 1.0
 * @perams:
 *	first = first select value
 *	last = last select value
 *  selected = selected value
 *  attr = attribute string for the select box
 **/
function numeric_range_select(first,last,selected,attr) 
{
	// default values
	first = first + '' || -20;
	last = last + ''|| 20;
	selected = selected || 0;
	attr = attr || '';

	
	attr = (attr) ? ' ' + attr : attr;

	output = "<select"+attr+">";

	for(c = first; c <= last; c++)
	{
		checked = (c == selected) ? ' selected="selected"' : '';
		output += '<option value="'+c+'"' + checked + ' label="'+c+'">'+c+'</option>';
	}

	return output += '</select>';

}


/**
 * get the value in a radio button group 
 *
 * @return selected index value or null
 * @author Dan Bryant <db@leadingtone.org>
 * @copyright LearningChange LLC and Dan Bryant (Shared Source Initiave/Dual Ownership), 12 April, 2007
 * @package core.js
 * @version: 1.0
 * @params: 
 * 	radio = the actual radio element (!!NOT!! its id/name)
 **/
function get_radio_value(radio)
{
	for (var c = 0; c < radio.length; c++)
	{
		// alert(radio[c].value);		
		// alert(radio[c].checked);
		
		if (radio[c].checked)
		{
		 	return radio[c].value; // a checked value found ... return it
		}	
	}
	return ''; // no checked value found ... returning '' instead of null so that the output of this function can more easily be used in validate()
	// return null; // no checked value found ... 
}

/**
 * get the value of the select box passed in
 *
 * @return a return object with the following properties:
 *	ro.value = the selected index's value 
 *	ro.label = the text label for this select box (you should always use this instead of depending on the html in the option)
 *	ro.html = the text that is between the end of the opening <option> tag and the start of the </option> tag (UNRELIABLE TRY NOT TO DEPEND ON THIS!)
 *
 *
 * @author Dan Bryant <db@leadingtone.org>
 * @copyright LearningChange LLC and Dan Bryant (Shared Source Initiave/Dual Ownership), 10 September, 2007
 * @package core.js
 * @version: 1.0
 * @perams:
 *	
 **/
function get_select_value(select)
{
	ro = {};
	ro.value = select[select.selectedIndex].value;
	ro.label = select[select.selectedIndex].label;
	ro.html = select[select.selectedIndex].text;
	return ro;
}

/**
 * // this is a wrapper to allow iframes to add elemts back into the parent window
 *
 * @return void
 * @author Dan Bryant <db@leadingtone.org>
 * @copyright LearningChange LLC and Dan Bryant (Shared Source Initiave/Dual Ownership), 24 May, 2007
 * @package core.js
 * @version: 1.0
 * @params:
 *	content = the contet to add into the target
 *	target = the target to add the content into
 * 	after = set to false if you want to prepend into the target
 **/
function add_html(content,target,after)
{
	after = after || true;
	
	if(after)
	{
		$(target).append(content);		
	}
	else
	{
		$(target).prepend(content);				
	}
}
/**
 * take an input object and serialize it onto a string 
 *
 * @return the object as a serialized string
 * @author Dan Bryant <db@leadingtone.org>
 * @copyright LearningChange LLC and Dan Bryant (Shared Source Initiave/Dual Ownership),  4 July, 2007
 * @package core.js
 * @version: 1.0
 * @perams:
 *	obj = the object to serialize
 **/
function serialize(obj)
{
	s = '{'; // open the serialization

	for( key in obj)
	{
		s += key + ':"' + obj[key] + '",';
	}

	// erase the last "," from the serialized string
	s = s.substring(0,(s.length-1));
	
	s += '}'; // close the serialization

	// return the serialization
	return s;
}

/**
 * unserialize a string created by the serialize function
 *
 * @return an unserialized object
 * @author Dan Bryant <db@leadingtone.org>
 * @copyright LearningChange LLC and Dan Bryant (Shared Source Initiave/Dual Ownership),  4 July, 2007
 * @package core.js
 * @version: 1.0
 * @perams:
 *	ser = the serialized object
 **/
function unserialize(ser)
{
	return eval(ser);
}

// after 1 ms set the cursor focus back to the element named in id
function fback(id)
{
	// this does not work! => $('#product_units_' + cfg.item)[0].focus(); 
	// this does not work! => var el = $('#product_units_' + cfg.item).get(); el.focus();
	// this does not work! => document.getElementById("product_units_" + cfg.item).focus();
	// this does not work! => var el = document.getElementById('product_units_' + cfg.item); el.focus();
	
	// window.setTimeout('document.getElementById("product_units_'+cfg.item+'").focus();',1); // => this works! ... holy Cow!			
	window.setTimeout('document.getElementById("'+id+'").select();',1);
}

// show|hide the .input_info element contained in "el"
function show_info(el)
{
	// hide any present validation messages set by various validation routines
	$('.dynmsg').remove();
	
	// show the info message
	$(el).parent().children('.input_info').toggle('medium');
}

// if el has value of val then clear it ... use this on text input elements
function clearme(el,val) { if(el.value == val) el.value = ""; }

// ez timstamp!
function timestamp() { return new Date().getTime(); }


function date_time()
{
	var date = new Date();
	// return date.getDate() + '/' + date.getDay() + '/' + date.getFullYear() + ' == ' + date.getHours() + ':' + date.getMinutes() + ':' + date.getSeconds();   
	// return date.toGMTString();
	// return date.toLocaleString();
	return date.toString();
}


// ez microtime!
function microtime() { return new Date().getUTCMilliseconds(); }

// look in a needle property in a object (obj) haystack
function has(property,obj) { if(obj.hasOwnProperty(property)) return true; return false; }

// javascript *cough* javascript ticks! see util.inc for an analogus function ( this ones ancestor ... don't ask )
function jjsticks(string) { return string.replace("'",'&#39;');	}

// fix any characters that could choke html or javascript
// see util.inc for an analogus function ( this ones ancestor ... don't ask )
function jjstring(string)
{
	string = string.replace('/"/g','&rdquo;'); // encode any '"'
	string = string.replace('/\n/g',''); // encode any '\n'	 
	string = string.replace('/\\/g',"&#92;"); // encode any "\"
	string = string.replace("/'/g",'&rsquo;'); // encode any "'"
	return string;
}

// get a real id from a fake id ... cause html is stupid! not allowing an id to start with a number ... <valleyGirl>I mean C'mon, like get real!</valleyGirl>
function real(id) { return id.substr(1,id.length) }

// cause javascript counterpart for bool2chk() -> see: util.php
function jbool2chk(val) { if(val) return 'checked="checked"'; }

// force the whole app to refresh itself .. after a child window updates a detail in the parent
function reboot_view()
{
	// window.location.reload(); // this is a problem if there is data on the post...
	// dont include post data when rebooting the view!
	window.location = window.location; 
}

// easy random number generation!
function rand()
{
	return Math.round(100*Math.random());
}

// easy to use round function
function round(args)
{
	var arg = {
		val: 0,
		decimals: 0
	}

	if(args) $.extend(arg, args);
	
	switch(arg.decimals)
	{		
		case 0:
			rounded = Math.round(arg.val);		
		break;

		case 1:
			rounded = Math.round(arg.val*10)/10;		
		break;
		
		case 2:
			rounded = Math.round(arg.val*100)/100;
		break;

		case 3:
			rounded = Math.round(arg.val*1000)/1000;
		break;
		
		default:
			rounded = Math.round(arg.val);
	}

	return rounded;
}

function fakesave()
{
	alert('Your work has been saved.');
	reboot_view();
}

// wrapper!
function encode(str)
{
	var str = encodeURIComponent(str);
	if(!str) return false;
	return str;
}

// wrapper!
function decode(str)
{
	var str = decodeURIComponent(str);
	if(!str) return false;
	return str;
}

/**
 * Prints a formatted string (like PHP printf())
 *
 * @return string|false
 * @author Matt Bower <matt@webbower.com>
 * @copyright 
 * @package 
 * @version: 1.0
 * @params:
 *	
 **/
function printf(formattedString) {
    var s = formattedString;
    if(s.match(/%s/g).length != (arguments.length-1)) {
        alert('Warning. Number of placeholders does not match number of arguments');
        return false;
    }
    for (var ctr = 1 ; ctr < arguments.length ; ctr++) {
        if(s.indexOf('%s') == -1) break;
        s = s.replace(/%s/, arguments[ctr]);
    }
    return s;
}

// Several programming languages implement a sprintf function, to output a formatted string. It originated from the C programming language, 
// printf function. Its a string manipulation function.
// 
// This is limited sprintf Javascript implementation. Function returns a string formatted by the usual printf conventions. 
// See below for more details. You must specify the string and how to format the variables in it. Possible format values:
// 
//     * %% - Returns a percent sign
//     * %b - Binary number
//     * %c - The character according to the ASCII value
//     * %d - Signed decimal number
//     * %f - Floating-point number
//     * %o - Octal number
//     * %s - String
//     * %x - Hexadecimal number (lowercase letters)
//     * %X - Hexadecimal number (uppercase letters)
// 
// Additional format values. These are placed between the % and the letter (example %.2f):
// 
//     * + (Forces both + and - in front of numbers. By default, only negative numbers are marked)
//     * - (Left-justifies the variable value)
//     * 0 zero will be used for padding the results to the right string size
//     * [0-9] (Specifies the minimum width held of to the variable value)
//     * .[0-9] (Specifies the number of decimal digits or maximum string length)
// 
// If you plan using UTF-8 encoding in your project don't forget to set the page encoding to UTF-8 (Content-Type meta tag), and use 
// Javascript UTF-8 utility found on this website.

// Source code for webtoolkit.sprintf.js
// Javascript sprintf
// http://www.webtoolkit.info/

// sprintfWrapper = {
// 
//     init : function () {
// 
//         if (typeof arguments == "undefined") { return null; }
//         if (arguments.length < 1) { return null; }
//         if (typeof arguments[0] != "string") { return null; }
//         if (typeof RegExp == "undefined") { return null; }
// 
//         var string = arguments[0];
//         var exp = new RegExp(/(%([%]|(\-)?(\+|\x20)?(0)?(\d+)?(\.(\d)?)?([bcdfosxX])))/g);
//         var matches = new Array();
//         var strings = new Array();
//         var convCount = 0;
//         var stringPosStart = 0;
//         var stringPosEnd = 0;
//         var matchPosEnd = 0;
//         var newString = '';
//         var match = null;
// 
//         while (match = exp.exec(string)) {
//             if (match[9]) { convCount += 1; }
// 
//             stringPosStart = matchPosEnd;
//             stringPosEnd = exp.lastIndex - match[0].length;
//             strings[strings.length] = string.substring(stringPosStart, stringPosEnd);
// 
//             matchPosEnd = exp.lastIndex;
//             matches[matches.length] = {
//                 match: match[0],
//                 left: match[3] ? true : false,
//                 sign: match[4] || '',
//                 pad: match[5] || ' ',
//                 min: match[6] || 0,
//                 precision: match[8],
//                 code: match[9] || '%',
//                 negative: parseInt(arguments[convCount]) < 0 ? true : false,
//                 argument: String(arguments[convCount])
//             };
//         }
//         strings[strings.length] = string.substring(matchPosEnd);
// 
//         if (matches.length == 0) { return string; }
//         if ((arguments.length - 1) < convCount) { return null; }
// 
//         var code = null;
//         var match = null;
//         var i = null;
// 
//         for (i=0; i<matches.length; i++) {
// 
//             if (matches[i].code == '%') { substitution = '%' }
//             else if (matches[i].code == 'b') {
//                 matches[i].argument = String(Math.abs(parseInt(matches[i].argument)).toString(2));
//                 substitution = sprintfWrapper.convert(matches[i], true);
//             }
//             else if (matches[i].code == 'c') {
//                 matches[i].argument = String(String.fromCharCode(parseInt(Math.abs(parseInt(matches[i].argument)))));
//                 substitution = sprintfWrapper.convert(matches[i], true);
//             }
//             else if (matches[i].code == 'd') {
//                 matches[i].argument = String(Math.abs(parseInt(matches[i].argument)));
//                 substitution = sprintfWrapper.convert(matches[i]);
//             }
//             else if (matches[i].code == 'f') {
//                 matches[i].argument = String(Math.abs(parseFloat(matches[i].argument)).toFixed(matches[i].precision ? matches[i].precision : 6));
//                 substitution = sprintfWrapper.convert(matches[i]);
//             }
//             else if (matches[i].code == 'o') {
//                 matches[i].argument = String(Math.abs(parseInt(matches[i].argument)).toString(8));
//                 substitution = sprintfWrapper.convert(matches[i]);
//             }
//             else if (matches[i].code == 's') {
//                 matches[i].argument = matches[i].argument.substring(0, matches[i].precision ? matches[i].precision : matches[i].argument.length)
//                 substitution = sprintfWrapper.convert(matches[i], true);
//             }
//             else if (matches[i].code == 'x') {
//                 matches[i].argument = String(Math.abs(parseInt(matches[i].argument)).toString(16));
//                 substitution = sprintfWrapper.convert(matches[i]);
//             }
//             else if (matches[i].code == 'X') {
//                 matches[i].argument = String(Math.abs(parseInt(matches[i].argument)).toString(16));
//                 substitution = sprintfWrapper.convert(matches[i]).toUpperCase();
//             }
//             else {
//                 substitution = matches[i].match;
//             }
// 
//             newString += strings[i];
//             newString += substitution;
// 
//         }
//         newString += strings[i];
// 
//         return newString;
// 
//     },
// 
//     convert : function(match, nosign){
//         if (nosign) {
//             match.sign = '';
//         } else {
//             match.sign = match.negative ? '-' : match.sign;
//         }
//         var l = match.min - match.argument.length + 1 - match.sign.length;
//         var pad = new Array(l < 0 ? 0 : l).join(match.pad);
//         if (!match.left) {
//             if (match.pad == "0" || nosign) {
//                 return match.sign + pad + match.argument;
//             } else {
//                 return pad + match.sign + match.argument;
//             }
//         } else {
//             if (match.pad == "0" || nosign) {
//                 return match.sign + match.argument + pad.replace(/0/g, ' ');
//             } else {
//                 return match.sign + match.argument + pad;
//             }
//         }
//     }
// }
// 
// sprintf = sprintfWrapper.init;

// by http://mathiasbynens.be/archive/2006/01/js-number-format
// usability alterations by Dan Bryant
// example number_format(number, decimals, comma, formatSeparator)
function number_format(a, b, c, d) {
	
	// set some sensible defaults <db>
	b = b || 2;
	c = c || '.';
	d = d || ',';	
	
	a = Math.round(a * Math.pow(10, b)) / Math.pow(10, b);
	e = a + '';
	f = e.split('.');
	if(!f[0]) f[0] = '0';
	if(!f[1]) f[1] = '';
	if(f[1].length < b){
		g = f[1];
		for(i = f[1].length + 1; i <= b; i++) {
			g += '0';
		}
		f[1] = g;
	}
	if(d != '' && f[0].length > 3) {
		h = f[0];
		f[0] = '';
		for(j = 3; j < h.length; j += 3) {
			i = h.slice(h.length - j, h.length - j + 3);
			f[0] = d + i +  f[0] + '';
		}
		j = h.substr(0, (h.length % 3 == 0) ? 3 : (h.length % 3));
		f[0] = j + f[0];
	}
	c = (b <= 0) ? '': c;
	return f[0] + c + f[1];
}

// turn on logging here. TODO: create a global configuration area to hold all of the globals that I am peppering in thoughlessly!
logging = true;
// logging = false;

// wrapper function for console.log() which i HATE to type ... again and again and agiain!
function clog(value)
{ if(logging) { // logging is a global that controls log() and clog() 

	if(console)
	{
		if(clog.caller) console.log(clog.caller); // caller is DEPRICATED!
		console.log(value);	
	}
	else // you dont have a debugging console. you should go get firebug!
	{
		if(clog.caller) log(clog.caller);
		// alert('no console found. you need to install firefug.');
		log(value);
	}

}}



// neat function found here: http://ajaxcookbook.org/javascript-debug-log/
/*
for (var i = 0; i < 10; i++) {
    log("This is log message #" + i);
}
*/
function log(message) 
{ if(logging) {
    if (!log.window_ || log.window_.closed) {
        var win = window.open("", null, "width=400,height=200," +
                              "scrollbars=yes,resizable=yes,status=no," +
                              "location=no,menubar=no,toolbar=no");
        if (!win) return;
        var doc = win.document;
        doc.write("<html><head><title>Debug Log</title></head>" +
                  "<body></body></html>");
        doc.close();
        log.window_ = win;
    }
    var logLine = log.window_.document.createElement("pre");
    logLine.appendChild(log.window_.document.createTextNode(message));
    log.window_.document.body.appendChild(logLine);
}}


/**
* Function : dump()
* Arguments: The data - array,hash(associative array),object
*    The level - OPTIONAL
* Returns  : The textual representation of the array.
* This function was inspired by the print_r function of PHP.
* This will accept some data as the argument and return a
* text that will be a more readable version of the
* array/hash/object that is given.
*/
function jdump(arr,level) 
{
	var dumped_text = "";
	if(!level) level = 0;

	//The padding given at the beginning of the line.
	var level_padding = "";
	for(var j=0;j<level+1;j++) level_padding += "    ";

	if(typeof(arr) == 'object') 
	{ //Array/Hashes/Objects
 		for(var item in arr) 
		{
  			var value = arr[item];

			if(typeof(value) == 'object') 
			{ //If it is an array,
				dumped_text += level_padding + "'" + item + "' ...\n";
	   			dumped_text += dump(value,level+1);
	  		} 
			else 
			{
	   			dumped_text += level_padding + "'" + item + "' => \"" + value + "\"\n";
	  		}
		}
	} 
	else 
	{ //Stings/Chars/Numbers etc.
	 	dumped_text = "===>"+arr+"<===("+typeof(arr)+")";
	}
	return dumped_text;
}

//http://www.bigbold.com/snippets/posts/show/759
function vdump(obj) 
{
   	if(typeof obj == "object") 
	{
      	return "Type: "+typeof(obj)+((obj.constructor) ? "\nConstructor: "+obj.constructor : "")+"\nValue: " + obj;
   	}
 	else 
	{
      	return "Type: "+typeof(obj)+"\nValue: "+obj;
   	}
}

// http://blog.firetree.net/2005/07/04/javascript-find-position/
function findPosX(obj)
{
  var curleft = 0;
  if(obj.offsetParent)
      while(1) 
      {
        curleft += obj.offsetLeft;
        if(!obj.offsetParent)
          break;
        obj = obj.offsetParent;
      }
  else if(obj.x)
      curleft += obj.x;
  return curleft;
}

// http://blog.firetree.net/2005/07/04/javascript-find-position/
function findPosY(obj)
{
  var curtop = 0;
  if(obj.offsetParent)
      while(1)
      {
        curtop += obj.offsetTop;
        if(!obj.offsetParent)
          break;
        obj = obj.offsetParent;
      }
  else if(obj.y)
      curtop += obj.y;
  return curtop;
}


// email validation script. Courtesy of SmartWebby.com (http://www.smartwebby.com/dhtml/)
function echeck(str) {
	var at="@";
	var dot=".";
	var lat=str.indexOf(at);
	var lstr=str.length;
	var ldot=str.indexOf(dot);
	if (str.indexOf(at)==-1){ return false; }
	if (str.indexOf(at)==-1 || str.indexOf(at)==0 || str.indexOf(at)==lstr){ return false; }
	if (str.indexOf(dot)==-1 || str.indexOf(dot)==0 || str.indexOf(dot)==lstr) { return false; }
	if (str.indexOf(at,(lat+1))!=-1) { return false; }
	if (str.substring(lat-1,lat)==dot || str.substring(lat+1,lat+2)==dot) { return false; }
	if (str.indexOf(dot,(lat+2))==-1) { return false; }
	if (str.indexOf(" ")!=-1) { return false; }
	return true;					
}


// http://www.paulspages.co.uk/pcp/cookielib/
// http://javascript.about.com/library/blcookie1.htm
// http://www.sameshirteveryday.com/2007/06/02/javascript-cookies-library/
// EXAMPLES ... DO NOT DELETE THIS DOCUMANTATION!
// Cookies.set('rid','1',1);
// alert(Cookies.get('rid'));
// Cookies.erase('rid');
// // Create an alias for a cookie named 'a'  
// Cookies.alias('greeting', 'a', 0);  
// // Set the value  
// Cookies.greeting(parseInt(Cookies.greeting()) + 1);  
// // Display the value  
// alert(  
//     Cookies.greeting() + 'n' +  
//     Cookies.get('a') + 'n' +  
//     Cookies.get('greeting')  
// );
var Cookies = {  
    aliases: {},  
  
    alias: function(alias, name, defaultValue)  
    {  
        Cookies.aliases[alias] = name;  
  
        Cookies[alias] = function(value, days)  
        {  
            if(value == null)  
                return Cookies.get(name, defaultValue);  
            else  
                Cookies.set(name, value, days);  
        }  
    },  
  
    set: function(name, value, days)  
    {  
        name = Cookies.aliases[name] || name;  
  
        var expires = '';  
  
        if(!isNaN(days))  
        {  
            var date = new Date();  
            date.setTime(date.getTime() + days * 24 * 60 * 60 * 1000);  
            expires = "; expires=" + date.toGMTString();  
        }  
  
        document.cookie = name + "=" + escape(value) + expires + "; path=/";  
    },  
  
    get: function(name, defaultValue)  
    {  
        name = Cookies.aliases[name] || name;  
  
        var regex = new RegExp(name + "s*=s*(.*?)(;|$)");  
        var cookies = document.cookie.toString();  
        var match = cookies.match(regex);  
  
        if(match)  
            return unescape(match[1]);  
  
        return defaultValue;  
    },  
  
    erase: function(name)  
    {  
        Cookies.set(name, '', -1);  
    }  
}  

// get the browser window width/height
function dwindow()
{
	var scr_w = screen.availWidth;
	var scr_h = screen.availHeight;

	// TODO: test this in ie6 and ie7
	var browseWidth, browseHeight;
	
	if (document.layers){
	   browseWidth=window.outerWidth;
	   browseHeight=window.outerHeight;
	}
	
	if (document.all){
	   browseWidth=document.body.clientWidth;
	   browseHeight=document.body.clientHeight;
	}

	return {width: scr_w, height:scr_h};
}

/**
 * trim a string to a given length and return it 
 *
 * @return the (truncated if necessary) string
 * @author Dan Bryant <db@leadingtone.org>
 * @copyright LearningChange LLC - Dan Bryant (Shared Source Initiave/Dual Ownership), 29 November, 2007
 * @package core.js
 * @version: 1.0
 * @perams:
 *	str = the input string
	arg = a hash with the following values:
		encode: true :: return &hellip; instead of "... "
		len: 50	:: the max length of the string to return
 **/
function trim_to(str,opts) 
{
		var opt = {
		encode: true,
		len: 50
	}

	if(opts) $.extend(opt, opts);
	
	if(str.length <= opt.len)
	{
		return str;
	}
	else
	{
		var dots = (opt.encode) ? '&hellip;' : '... ';
		return str.substring(0,opt.len) + dots;
	}
}


// string trim functions found here: http://www.somacon.com/p355.php
function trim(str) {
	return str.replace(/^\s+|\s+$/g,"");
}


function ltrim(str) {
	return str.replace(/^\s+/,"");
}

function rtrim(str) {
	return str.replace(/\s+$/,"");
}
